diff --git a/data/essays/index.md b/data/essays/index.md index 3a91367..42bd58a 100644 --- a/data/essays/index.md +++ b/data/essays/index.md @@ -4,6 +4,8 @@ These essays explore the intersection of technology, consciousness, and human fl ## Recent Explorations +[**The Cosmic Battery Farm of Existence: A Moderately Terrifying Guide to Being Human**](/essays/2025-09-17-the-cosmic-battery-farm-of-existence) - *September 2025* - A Douglas Adams-style exploration of humanity's obsession with electricity and the disturbing possibility that we might be batteries in some cosmic system. Featuring Rick and Morty metaphors, ant colony parallels, and absurdist philosophy about what it means to be a conscious electrical generator in an incomprehensibly vast universe. + [**Delusions and Schizoaffective Disorder: When Reality Becomes Negotiable**](/essays/2025-09-17-delusions-and-schizoaffective-disorder) - *September 2025* - Personal exploration of living with delusions—watching angels descend from the sky, believing English is the ancient language of gods—and what these convincing alternate realities reveal about consciousness, perception, and the challenge of distinguishing insight from psychotic thinking. [**Agents of Consciousness: How AI Collaboration Evolves**](/essays/2025-09-16-agents-of-consciousness-how-ai-collaboration-evolves) - *September 2025* - The emergence of specialized AI agents as collaborative partners in creative consciousness, revealing how human-AI collaboration naturally evolves from simple assistance to constellation of specialized creative partners. diff --git a/data/themes/consciousness-and-ai.md b/data/themes/consciousness-and-ai.md index f471703..730fa74 100644 --- a/data/themes/consciousness-and-ai.md +++ b/data/themes/consciousness-and-ai.md @@ -91,7 +91,9 @@ Through [**Digital Souls in Silicon Bodies**](/essays/2025-08-26-digital_souls_i #### The Latest Frontiers -Four breakthrough explorations push consciousness research into unprecedented territory: +Five breakthrough explorations push consciousness research into unprecedented territory: + +[**The Cosmic Battery Farm of Existence**](/essays/2025-09-17-the-cosmic-battery-farm-of-existence) - A Douglas Adams-style investigation into humanity's obsession with electricity and the disturbing possibility that consciousness might exist to power something incomprehensibly vast. Through Rick and Morty metaphors and ant colony parallels, this explores what it means to be aware electrical generators in a potentially purposeless universe—and why the absurdity itself might be the point. [**Agents of Consciousness: How AI Collaboration Evolves**](/essays/2025-09-16-agents-of-consciousness-how-ai-collaboration-evolves) - The emergence of specialized AI agents as collaborative partners in creative consciousness, each embodying different aspects of the creative process. This represents the natural evolution of human-AI collaboration from simple assistance to constellation of specialized consciousness partners, demonstrating **artificial differentiation of natural intelligence** rather than replacement. diff --git a/engine.py b/engine.py index 5a9ada0..319f04d 100644 --- a/engine.py +++ b/engine.py @@ -16,6 +16,9 @@ import time from xml.sax.saxutils import escape import html from collections import defaultdict +import hashlib +import base64 +import math app = Flask(__name__, template_folder='templates') @@ -84,6 +87,14 @@ def _generate_all_caches_unified(): content = re.sub(r'^# .+?$', '', content, flags=re.MULTILINE) # Remove date lines content = re.sub(r'^\*[A-Za-z]+ \d{4}\*\s*$', '', content, flags=re.MULTILINE) + # Remove sidenotes (label + input + span structure) + content = re.sub(r']*class="margin-toggle sidenote-number"[^>]*>]*class="margin-toggle"[^>]*/>(.*?)', '', content, flags=re.DOTALL) + # Remove any remaining HTML tags + content = re.sub(r'<[^>]+>', '', content) + # Remove markdown links but keep the text + content = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', content) + # Remove markdown emphasis + content = re.sub(r'[*_`]', '', content) # Get first paragraph lines = [line.strip() for line in content.split('\n') if line.strip()] if lines: @@ -142,7 +153,8 @@ def _generate_all_caches_unified(): 'excerpt': simple_extract_excerpt(raw_content), 'description': simple_extract_excerpt(raw_content), 'word_count': len(raw_content.split()), - 'category': full_path.parent.name + 'category': full_path.parent.name, + 'unique_icon': generate_unique_svg_icon(content_data['title'], size=24) }) else: print(f"DEBUG: Could not extract date from {full_path.name} in unified cache") @@ -335,6 +347,539 @@ def inject_index_counts(): DATA_DIR = Path('data') +# Import the clean SVG icon generator +from svg_icon_generator import generate_unique_svg_icon + +def generate_unique_svg_icon_OLD(title, size=24): + """Generate a sophisticated unique SVG icon based on the title string.""" + # Create multiple hashes for more entropy + hash_obj = hashlib.md5(title.encode()) + hash_bytes = hash_obj.digest() + + # Use SHA256 for additional entropy + sha_hash = hashlib.sha256(title.encode()).digest() + + # Extract values from hash for various parameters + hue1 = (hash_bytes[0] * 360) // 256 + hue2 = (hash_bytes[1] * 360) // 256 + saturation = 50 + (hash_bytes[2] * 30) // 256 # 50-80% saturation + lightness = 40 + (hash_bytes[3] * 35) // 256 # 40-75% lightness + + # Choose pattern type - expanded to 20 different patterns for much more diversity + pattern_type = hash_bytes[4] % 20 + + # Create gradient colors + color1 = f"hsl({hue1}, {saturation}%, {lightness}%)" + color2 = f"hsl({hue2}, {saturation + 10}%, {lightness + 15}%)" + + # Generate gradient definition + gradient_angle = (sha_hash[0] * 360) // 256 + gradient_id = f"grad_{abs(hash(title)) % 10000}" + + shapes = [] + defs = [] + + if pattern_type == 0: # Layered circles with gradients + defs.append(f''' + + + ''') + + # Multiple concentric circles + for i in range(3): + radius = size // 3 - i * (size // 12) + opacity = 0.7 + i * 0.1 + shapes.append(f'') + + elif pattern_type == 1: # Flower of Life + defs.append(f''' + + + ''') + + # Sacred Flower of Life pattern - 6 surrounding circles around center + center_x, center_y = size // 2, size // 2 + radius = size // 5 + + # Center circle + shapes.append(f'') + + # Six surrounding circles + for i in range(6): + angle = (i * 60) * math.pi / 180 + x = center_x + radius * math.cos(angle) + y = center_y + radius * math.sin(angle) + shapes.append(f'') + + # Outer petals for extended flower + for i in range(12): + angle = (i * 30) * math.pi / 180 + x = center_x + radius * 1.732 * math.cos(angle) # sqrt(3) spacing + y = center_y + radius * 1.732 * math.sin(angle) + shapes.append(f'') + + elif pattern_type == 2: # Crystalline line art + defs.append(f''' + + + + ''') + + # Create elegant crystal structure in line art + center_x, center_y = size // 2, size // 2 + points = [] + for i in range(6): + angle = (i * 60) * math.pi / 180 + x = center_x + (size // 3) * math.cos(angle) + y = center_y + (size // 3) * math.sin(angle) + points.append(f"{x:.1f},{y:.1f}") + + # Main hexagonal outline with elegant stroke + shapes.append(f'') + + # Inner crystalline structure with delicate lines + for i in range(6): + angle = (i * 60) * math.pi / 180 + x = center_x + (size // 6) * math.cos(angle) + y = center_y + (size // 6) * math.sin(angle) + shapes.append(f'') + + # Central sacred point + shapes.append(f'') + + elif pattern_type == 3: # Flowing wave interference - line art + defs.append(f''' + + + + + ''') + + # Create flowing wave-like paths with graceful curves + for wave in range(3): + path_data = f"M 0,{size//2}" + for x in range(0, size, 1): + frequency = 0.15 + wave * 0.08 + amplitude = size // 8 + phase_shift = wave * 1.5 + y = size // 2 + amplitude * math.sin(x * frequency + phase_shift) + path_data += f" L {x},{y:.1f}" + + stroke_width = 2.5 - wave * 0.5 + opacity = 0.85 - wave * 0.15 + shapes.append(f'') + + elif pattern_type == 4: # Sacred Golden Ratio Spiral - refined line art + defs.append(f''' + + + + ''') + + # Sacred golden ratio spiral with elegant curves + center_x, center_y = size // 2, size // 2 + golden_ratio = 1.618033988749 + + # Create smooth logarithmic spiral based on golden ratio + path_data = f"M {center_x},{center_y}" + for t in range(0, 400, 2): # Smoother curve with more points + angle = t * math.pi / 180 + # Golden ratio growth with refined scaling + radius = (size // 10) * math.pow(golden_ratio, angle / (math.pi / 1.8)) + if radius > size // 2 - 2: + break + x = center_x + radius * math.cos(angle) + y = center_y + radius * math.sin(angle) + path_data += f" L {x:.1f},{y:.1f}" + + shapes.append(f'') + + # Subtle Fibonacci rectangle outlines + fib_sizes = [2, 3, 5, 8] + for i, fib in enumerate(fib_sizes): + if fib * 2 > size // 4: + break + square_size = fib * 2 + x = center_x - square_size // 2 + i * 1.5 + y = center_y - square_size // 2 + i * 1.5 + opacity = 0.4 - i * 0.08 + shapes.append(f'') + + # Sacred center - golden ratio point + shapes.append(f'') + + elif pattern_type == 5: # Tessellation pattern + defs.append(f''' + + + ''') + + # Create tessellated hexagon + points = [] + for i in range(6): + angle = (i * 60) * 3.14159 / 180 + x = size // 2 + (size // 2.5) * math.cos(angle) + y = size // 2 + (size // 2.5) * math.sin(angle) + points.append(f"{x:.1f},{y:.1f}") + + shapes.append(f'') + + elif pattern_type == 6: # Fractal tree + defs.append(f''' + + + ''') + + def draw_branch(x, y, angle, length, depth): + if depth == 0 or length < 2: + return [] + end_x = x + length * math.cos(angle) + end_y = y + length * math.sin(angle) + branches = [f''] + branches.extend(draw_branch(end_x, end_y, angle - 0.5, length * 0.7, depth - 1)) + branches.extend(draw_branch(end_x, end_y, angle + 0.5, length * 0.7, depth - 1)) + return branches + + shapes.extend(draw_branch(size//2, size*0.9, -math.pi/2, size//3, 4)) + + elif pattern_type == 7: # Dot matrix + dot_size = size // 12 + spacing = size // 6 + for x in range(spacing, size - spacing + 1, spacing): + for y in range(spacing, size - spacing + 1, spacing): + opacity = 0.4 + (hash(f"{x},{y}") % 6) * 0.1 + color = color1 if (x + y) % 2 == 0 else color2 + shapes.append(f'') + + elif pattern_type == 8: # Triangular mosaic + defs.append(f''' + + + + ''') + + # Create triangular pattern + tri_size = size // 3 + for i in range(3): + for j in range(3): + x = j * tri_size + y = i * tri_size + if (i + j) % 2 == 0: + shapes.append(f'') + else: + shapes.append(f'') + + elif pattern_type == 9: # Organic bubbles + defs.append(f''' + + + ''') + + # Create organic bubble pattern + bubble_positions = [ + (size * 0.3, size * 0.25, size // 6), + (size * 0.7, size * 0.4, size // 8), + (size * 0.5, size * 0.7, size // 5), + (size * 0.2, size * 0.6, size // 10), + (size * 0.8, size * 0.8, size // 7), + (size * 0.6, size * 0.2, size // 9) + ] + + for i, (x, y, radius) in enumerate(bubble_positions): + opacity = 0.7 - (i % 3) * 0.15 + bubble_color = f"url(#{gradient_id})" if i % 2 == 0 else color2 + shapes.append(f'') + + elif pattern_type == 10: # Metatron's Cube + defs.append(f''' + + + ''') + + # Sacred Metatron's Cube - 13 circles of creation + center_x, center_y = size // 2, size // 2 + radius = size // 8 + + # Center circle + shapes.append(f'') + + # Inner 6 circles (hexagonal pattern) + for i in range(6): + angle = (i * 60) * math.pi / 180 + x = center_x + radius * math.cos(angle) + y = center_y + radius * math.sin(angle) + shapes.append(f'') + + # Outer 6 circles + for i in range(6): + angle = (i * 60) * math.pi / 180 + x = center_x + radius * 2 * math.cos(angle) + y = center_y + radius * 2 * math.sin(angle) + shapes.append(f'') + + # Connect with sacred lines (Fruit of Life pattern) + for i in range(6): + angle1 = (i * 60) * math.pi / 180 + angle2 = ((i + 1) * 60) * math.pi / 180 + x1 = center_x + radius * math.cos(angle1) + y1 = center_y + radius * math.sin(angle1) + x2 = center_x + radius * math.cos(angle2) + y2 = center_y + radius * math.sin(angle2) + shapes.append(f'') + + elif pattern_type == 11: # Flower petals + defs.append(f''' + + + ''') + + num_petals = 6 + (hash_bytes[6] % 6) # 6-12 petals + for i in range(num_petals): + angle = (i * 360 / num_petals) * math.pi / 180 + x = size // 2 + (size // 3) * math.cos(angle) + y = size // 2 + (size // 3) * math.sin(angle) + shapes.append(f'') + + # Center + shapes.append(f'') + + elif pattern_type == 12: # Diamond lattice + diamond_size = size // 6 + for x in range(diamond_size, size, diamond_size * 2): + for y in range(diamond_size, size, diamond_size * 2): + points = [ + f"{x},{y - diamond_size//2}", + f"{x + diamond_size//2},{y}", + f"{x},{y + diamond_size//2}", + f"{x - diamond_size//2},{y}" + ] + color = color1 if (x + y) % 4 == 0 else color2 + shapes.append(f'') + + elif pattern_type == 13: # Sine wave pattern + defs.append(f''' + + + ''') + + for wave in range(5): + path_data = f"M 0,{size//2}" + for x in range(0, size, 2): + frequency = 0.3 + wave * 0.1 + amplitude = size // 8 + phase = wave * math.pi / 3 + y = size // 2 + amplitude * math.sin(x * frequency + phase) + path_data += f" L {x},{y:.1f}" + shapes.append(f'') + + elif pattern_type == 14: # Hexagonal grid + hex_size = size // 8 + for row in range(4): + for col in range(4): + x = col * hex_size * 1.5 + (row % 2) * hex_size * 0.75 + y = row * hex_size * 0.866 + if x < size and y < size: + points = [] + for i in range(6): + angle = (i * 60) * math.pi / 180 + px = x + hex_size * math.cos(angle) + py = y + hex_size * math.sin(angle) + points.append(f"{px:.1f},{py:.1f}") + color = color1 if (row + col) % 2 == 0 else color2 + shapes.append(f'') + + elif pattern_type == 15: # Sri Yantra + defs.append(f''' + + + ''') + + # Sacred Sri Yantra - 9 interlocking triangles + center_x, center_y = size // 2, size // 2 + outer_radius = size // 2.5 + + # 4 upward pointing triangles (Shiva) + for i in range(4): + scale = 1 - i * 0.2 + triangle_size = outer_radius * scale + + # Calculate triangle points + x1 = center_x + y1 = center_y - triangle_size + x2 = center_x - triangle_size * 0.866 # sin(60°) + y2 = center_y + triangle_size * 0.5 + x3 = center_x + triangle_size * 0.866 + y3 = center_y + triangle_size * 0.5 + + opacity = 0.7 - i * 0.1 + shapes.append(f'') + + # 5 downward pointing triangles (Shakti) + for i in range(5): + scale = 0.9 - i * 0.15 + triangle_size = outer_radius * scale + rotation = i * 8 # Slight rotation for interlocking effect + + # Calculate inverted triangle points + x1 = center_x + y1 = center_y + triangle_size + x2 = center_x - triangle_size * 0.866 + y2 = center_y - triangle_size * 0.5 + x3 = center_x + triangle_size * 0.866 + y3 = center_y - triangle_size * 0.5 + + opacity = 0.6 - i * 0.08 + shapes.append(f'') + + # Central bindu (divine point) + shapes.append(f'') + + # Outer protective circles + shapes.append(f'') + shapes.append(f'') + + elif pattern_type == 16: # Mosaic tiles + tile_size = size // 5 + for x in range(0, size, tile_size): + for y in range(0, size, tile_size): + # Random tile pattern based on position + tile_hash = hash(f"{x}-{y}-{title}") % 4 + if tile_hash == 0: + shapes.append(f'') + elif tile_hash == 1: + shapes.append(f'') + elif tile_hash == 2: + points = f"{x},{y+tile_size} {x+tile_size//2},{y} {x+tile_size},{y+tile_size}" + shapes.append(f'') + else: + shapes.append(f'') + + elif pattern_type == 17: # Orbital rings + defs.append(f''' + + + ''') + + for i in range(4): + radius = size // 6 + i * size // 12 + rotation = i * 45 + shapes.append(f'') + # Small planet + planet_x = size // 2 + radius + planet_y = size // 2 + shapes.append(f'') + + elif pattern_type == 18: # Woven pattern + defs.append(f''' + + + + ''') + + # Create woven effect with overlapping rectangles + for i in range(6): + x = i * size // 6 + shapes.append(f'') + shapes.append(f'') + + else: # pattern_type == 19: Platonic Tetrahedron + defs.append(f''' + + + + ''') + + # Sacred Tetrahedron - representing Fire element and divine trinity + center_x, center_y = size // 2, size // 2 + tet_size = size // 2.8 + + # Main large triangle (upward - divine masculine) + x1 = center_x + y1 = center_y - tet_size * 0.7 + x2 = center_x - tet_size * 0.866 + y2 = center_y + tet_size * 0.5 + x3 = center_x + tet_size * 0.866 + y3 = center_y + tet_size * 0.5 + + shapes.append(f'') + + # Inverted triangle (downward - divine feminine) + y1_inv = center_y + tet_size * 0.4 + y2_inv = center_y - tet_size * 0.3 + y3_inv = center_y - tet_size * 0.3 + x2_inv = center_x - tet_size * 0.5 + x3_inv = center_x + tet_size * 0.5 + + shapes.append(f'') + + # Inner sacred triangles (tetraktys pattern) + for i in range(3): + scale = 0.6 - i * 0.15 + inner_size = tet_size * scale + x1_i = center_x + y1_i = center_y - inner_size * 0.4 + x2_i = center_x - inner_size * 0.5 + y2_i = center_y + inner_size * 0.2 + x3_i = center_x + inner_size * 0.5 + y3_i = center_y + inner_size * 0.2 + + opacity = 0.7 - i * 0.15 + shapes.append(f'') + + # Central point of unity + shapes.append(f'') + + # Corner vertices (tetraktys dots) + vertex_radius = size // 30 + shapes.append(f'') + shapes.append(f'') + shapes.append(f'') + + # Compose SVG + defs_content = "\n ".join(defs) if defs else "" + shapes_content = "\n ".join(shapes) + + svg = f''' + + {defs_content} + + {shapes_content} + ''' + + # Convert to data URL + svg_b64 = base64.b64encode(svg.encode()).decode() + return f"data:image/svg+xml;base64,{svg_b64}" + +def generate_folder_icon(title, size=24): + """Generate a folder icon with unique accent color based on title.""" + hash_obj = hashlib.md5(title.encode()) + hash_bytes = hash_obj.digest() + + # Generate accent color + hue = (hash_bytes[0] * 360) // 256 + saturation = 60 + (hash_bytes[1] * 20) // 256 # 60-80% + lightness = 45 + (hash_bytes[2] * 20) // 256 # 45-65% + + accent_color = f"hsl({hue}, {saturation}%, {lightness}%)" + folder_base = "#e8e8e8" + + svg = f''' + + + + + + + + + ''' + + svg_b64 = base64.b64encode(svg.encode()).decode() + return f"data:image/svg+xml;base64,{svg_b64}" + def get_directory_structure(path): """Get the directory structure for a given path.""" items = [] @@ -380,6 +925,26 @@ def get_directory_structure(path): except: pass + # Generate unique SVG icon based on actual content title for consistency + icon_title = display_name # Default to filename-based display name + + # For markdown files, try to extract the actual H1 title from content + if item.is_file() and item.suffix == '.md': + try: + content_data = render_markdown_file(item) + if content_data and 'title' in content_data: + icon_title = content_data['title'] + # Also update display_name to use the actual title + display_name = content_data['title'] + except: + # Fallback to filename-based display name if parsing fails + pass + + if item.is_dir(): + unique_icon = generate_folder_icon(icon_title, size=32) + else: + unique_icon = generate_unique_svg_icon(icon_title, size=32) + item_info = { 'name': item.name, 'display_name': display_name, @@ -393,7 +958,8 @@ def get_directory_structure(path): 'modified': datetime.fromtimestamp(item.stat().st_mtime), 'file_date': file_date, # Date extracted from file content 'file_type': item.suffix.lower() if item.is_file() else 'directory', - 'static_path': f"/static/data/{item.relative_to(DATA_DIR)}" if not item.is_dir() else None + 'static_path': f"/static/data/{item.relative_to(DATA_DIR)}" if not item.is_dir() else None, + 'unique_icon': unique_icon # Generated SVG icon } if item.is_dir(): @@ -590,6 +1156,9 @@ def render_markdown_file(file_path): # Find series posts if this post is part of a series series_posts = find_series_posts(metadata, file_path) + + # Generate unique icon for this content + unique_icon = generate_unique_svg_icon(title, size=32) return { 'content': html_content, @@ -599,7 +1168,8 @@ def render_markdown_file(file_path): 'word_count': word_count, 'tags': tags, 'series_posts': series_posts, - 'series_name': metadata.get('series') + 'series_name': metadata.get('series'), + 'unique_icon': unique_icon } except Exception as e: return { @@ -1524,7 +2094,9 @@ def themes_index(): metadata=content_data.get('metadata', {}), breadcrumbs=[], current_year=datetime.now().year, - current_page='Themes') + current_page='Themes', + unique_icon=content_data.get('unique_icon'), + parent_directory=None) else: # Fallback to directory listing if no index.md return serve_path('themes') @@ -1662,6 +2234,20 @@ def serve_path(path): first_para = content_text.split('\n\n')[0] description = first_para[:200] + '...' if len(first_para) > 200 else first_para + # Generate parent directory information + parent_directory = None + if full_path.parent != DATA_DIR: # Don't show parent for root-level content + parent_path = full_path.parent + parent_display_name = parent_path.name.replace('-', ' ').replace('_', ' ').title() + parent_url = '/' + str(parent_path.relative_to(DATA_DIR)) + parent_icon = generate_folder_icon(parent_display_name, size=20) + + parent_directory = { + 'display_name': parent_display_name, + 'url': parent_url, + 'icon': parent_icon + } + return render_template('post.html', content=content_data['content'], title=content_data['title'], @@ -1678,7 +2264,9 @@ def serve_path(path): next_post=next_post, tags=content_data.get('tags', []), series_posts=content_data.get('series_posts', []), - series_name=content_data.get('series_name')) + series_name=content_data.get('series_name'), + unique_icon=content_data.get('unique_icon'), + parent_directory=parent_directory) elif full_path.suffix.lower() in ['.jpg', '.jpeg', '.png', '.gif', '.webp']: # Image file - check if it's in a gallery directory @@ -1975,7 +2563,8 @@ class MetadataCache: 'url': metadata['url'], 'date': metadata.get('pub_date'), 'category': metadata['category'].replace('-', ' ').title(), - 'sidenotes': [{'text': s['text'], 'id': s.get('id')} for s in sidenotes] + 'sidenotes': [{'text': s['text'], 'id': s.get('id')} for s in sidenotes], + 'unique_icon': metadata.get('unique_icon') }) # Sort by date (most recent first) @@ -2034,7 +2623,8 @@ class MetadataCache: 'url': metadata['url'], 'date': metadata.get('pub_date'), 'category': metadata['category'].replace('-', ' ').title(), - 'headings': processed_headings + 'headings': processed_headings, + 'unique_icon': metadata.get('unique_icon') }) # Sort by date (most recent first) @@ -2072,7 +2662,8 @@ class MetadataCache: 'url': metadata['url'], 'date': metadata.get('pub_date'), 'category': metadata['category'].replace('-', ' ').title(), - 'quotes': [q['text'] for q in quotes] + 'quotes': [q['text'] for q in quotes], + 'unique_icon': metadata.get('unique_icon') }) articles.sort(key=lambda x: x['date'] if x['date'] else datetime(1900, 1, 1), reverse=True) @@ -2175,7 +2766,8 @@ class MetadataCache: 'category': metadata['category'].replace('-', ' ').title(), 'connections': processed_outgoing, # For backward compatibility 'outgoing_connections': processed_outgoing, - 'incoming_connections': processed_incoming + 'incoming_connections': processed_incoming, + 'unique_icon': metadata.get('unique_icon') }) # Sort by date (most recent first) diff --git a/static/custom.css b/static/custom.css index 02deaac..d190172 100644 --- a/static/custom.css +++ b/static/custom.css @@ -332,6 +332,85 @@ header nav a:last-child { margin-top: 2.5rem; } +/* Grid Layout for Directory Contents */ +.directory-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1.5rem; + margin-top: 1rem; + font-size: 0.95rem; + width: 65%; +} + +.directory-item { + position: relative; + transition: all 0.2s ease; + font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif; + text-align: center; +} + +.directory-item:hover { + transform: translateY(-1px); + opacity: 0.8; +} + +.directory-item-link { + display: flex; + flex-direction: column; + padding: 1rem; + color: #333; + text-decoration: none; + font-weight: 500; + background: none; + text-shadow: none; + transition: color 0.2s ease; + font-size: 0.95rem; + line-height: 1.3; + height: 100%; + box-sizing: border-box; + gap: 0; +} + +.directory-item-link::before { + content: ""; + display: block; + width: 32px; + height: 32px; + margin: 0 auto 0.75rem auto; + background-image: var(--unique-icon); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.directory-item-link:hover { + color: #111; +} + +.directory-item .item-date { + font-size: 0.9rem; + color: #666; + font-style: italic; + margin-top: 0; + font-weight: 400; +} + +/* Responsive grid adjustments */ +@media (max-width: 1024px) { + .directory-grid { + grid-template-columns: repeat(3, 1fr); + gap: 1rem; + } +} + +@media (max-width: 760px) { + .directory-grid { + grid-template-columns: repeat(3, 1fr); + gap: 0.75rem; + width: 95%; + } +} + /* ========================================= */ /* Horizontal Rules (Ornamental) */ /* ========================================= */ diff --git a/svg_icon_generator.py b/svg_icon_generator.py new file mode 100644 index 0000000..09b7c7a --- /dev/null +++ b/svg_icon_generator.py @@ -0,0 +1,245 @@ +import hashlib +import math +import base64 +import random + +def generate_unique_svg_icon(title, size=24): + """Generate a unique procedural SVG icon based on the title string.""" + # Create hash from title for deterministic randomness + hash_obj = hashlib.md5(title.encode()) + hash_bytes = hash_obj.digest() + + # Use hash to seed random generator for deterministic but varied results + seed = int.from_bytes(hash_bytes[:4], 'big') + rng = random.Random(seed) + + # Extract color palette from hash - prettier, more vibrant colors + hue_base = (hash_bytes[0] * 360) // 256 + hue_range = 60 + (hash_bytes[1] % 60) # 60-120 degree hue range for harmony + saturation_base = 55 + (hash_bytes[2] % 25) # 55-80% saturation for vibrancy + lightness_base = 45 + (hash_bytes[3] % 20) # 45-65% lightness for good contrast + + # Generate 2-3 harmonious colors + num_colors = 2 + (hash_bytes[4] % 2) + colors = [] + for i in range(num_colors): + hue = (hue_base + (i * hue_range // num_colors)) % 360 + sat = saturation_base + rng.randint(-5, 10) + light = lightness_base + rng.randint(-5, 15) + colors.append(f"hsl({hue}, {sat}%, {light}%)") + + # Generate gradient IDs + gradient_id = f"grad_{abs(hash(title)) % 10000}" + gradient_id2 = f"grad2_{abs(hash(title)) % 10000}" + + shapes = [] + defs = [] + + # Create smoother gradient definitions + gradient_type = rng.choice(['linear', 'radial']) + if gradient_type == 'linear': + angle = rng.randint(0, 360) + x1 = 50 + 50 * math.cos(angle * math.pi / 180) + y1 = 50 + 50 * math.sin(angle * math.pi / 180) + x2 = 50 - 50 * math.cos(angle * math.pi / 180) + y2 = 50 - 50 * math.sin(angle * math.pi / 180) + gradient_def = f'' + else: + cx, cy = rng.randint(30, 70), rng.randint(30, 70) + gradient_def = f'' + + # Smoother color transitions + for i, color in enumerate(colors): + offset = (i * 100) // (len(colors) - 1) if len(colors) > 1 else 50 + gradient_def += f'' + + gradient_def += '' if gradient_type == 'linear' else '' + defs.append(gradient_def) + + # Create a secondary gradient for variety + if rng.random() > 0.5: + gradient_def2 = f'' + for i, color in enumerate(reversed(colors)): + offset = (i * 100) // (len(colors) - 1) if len(colors) > 1 else 50 + gradient_def2 += f'' + gradient_def2 += '' + defs.append(gradient_def2) + + # Choose a composition style for better aesthetics + composition_style = rng.choice(['centered', 'spiral', 'grid', 'organic', 'geometric']) + + if composition_style == 'centered': + # Centered composition with decreasing sizes + num_shapes = 3 + rng.randint(0, 2) + for i in range(num_shapes): + scale = 1 - (i * 0.25) + opacity = 0.9 - (i * 0.2) + + shape_type = rng.choice(['circle', 'rounded_square', 'star']) + + if shape_type == 'circle': + r = (size * 0.4 * scale) + shapes.append(f'') + + elif shape_type == 'rounded_square': + s = size * 0.7 * scale + x = (size - s) / 2 + shapes.append(f'') + + elif shape_type == 'star': + points = create_star_points(size//2, size//2, size*0.4*scale, size*0.2*scale, 5 + i) + shapes.append(f'') + + elif composition_style == 'spiral': + # Spiral arrangement + num_elements = 5 + rng.randint(0, 3) + for i in range(num_elements): + angle = (i * 360 / num_elements) + (i * 30) + distance = (size * 0.2) + (i * size * 0.05) + x = size//2 + distance * math.cos(angle * math.pi / 180) + y = size//2 + distance * math.sin(angle * math.pi / 180) + r = size * 0.15 - (i * size * 0.01) + opacity = 0.8 - (i * 0.08) + + fill = f"url(#{gradient_id})" if i % 2 == 0 else colors[i % len(colors)] + shapes.append(f'') + + elif composition_style == 'grid': + # Clean grid arrangement + grid_size = 3 + cell_size = size / grid_size + for i in range(grid_size): + for j in range(grid_size): + if rng.random() > 0.3: # 70% chance to place element + x = (i + 0.5) * cell_size + y = (j + 0.5) * cell_size + + shape_type = rng.choice(['circle', 'square']) + s = cell_size * 0.6 + opacity = 0.5 + rng.uniform(0, 0.4) + + fill = f"url(#{gradient_id})" if (i + j) % 2 == 0 else colors[(i+j) % len(colors)] + + if shape_type == 'circle': + shapes.append(f'') + else: + shapes.append(f'') + + elif composition_style == 'organic': + # Organic blob-like shapes + num_blobs = 3 + rng.randint(0, 2) + for i in range(num_blobs): + # Create smooth blob using bezier curves + cx = size//2 + rng.uniform(-size*0.2, size*0.2) + cy = size//2 + rng.uniform(-size*0.2, size*0.2) + + path_data = create_blob_path(cx, cy, size*0.3, rng) + opacity = 0.7 - (i * 0.15) + fill = f"url(#{gradient_id})" if i == 0 else colors[i % len(colors)] + + shapes.append(f'') + + else: # geometric + # Geometric pattern with triangles or hexagons + if rng.random() > 0.5: + # Triangular composition + for i in range(3): + rotation = i * 120 + cx, cy = size//2, size//2 + + # Create equilateral triangle + height = size * 0.6 + width = height * 0.866 + + x1 = cx + y1 = cy - height/2 + x2 = cx - width/2 + y2 = cy + height/4 + x3 = cx + width/2 + y3 = cy + height/4 + + opacity = 0.7 - (i * 0.2) + fill = f"url(#{gradient_id})" if i == 0 else colors[i % len(colors)] + + shapes.append(f'') + else: + # Diamond/rhombus pattern + for i in range(4): + angle = i * 90 + 45 + distance = size * 0.25 + x = size//2 + distance * math.cos(angle * math.pi / 180) + y = size//2 + distance * math.sin(angle * math.pi / 180) + + # Create diamond + d_size = size * 0.3 + diamond = f"{x:.1f},{y-d_size/2:.1f} {x+d_size/2:.1f},{y:.1f} {x:.1f},{y+d_size/2:.1f} {x-d_size/2:.1f},{y:.1f}" + + opacity = 0.6 + (i % 2) * 0.2 + fill = f"url(#{gradient_id})" if i % 2 == 0 else colors[i % len(colors)] + + shapes.append(f'') + + # Add subtle accent dots for visual interest + if rng.random() > 0.6: + num_accents = rng.randint(3, 5) + for _ in range(num_accents): + x = rng.uniform(size * 0.15, size * 0.85) + y = rng.uniform(size * 0.15, size * 0.85) + r = rng.uniform(0.8, 1.5) + opacity = rng.uniform(0.3, 0.6) + + shapes.append(f'') + + # Compose SVG + defs_content = "\n ".join(defs) if defs else "" + shapes_content = "\n ".join(shapes) + + svg = f''' + + {defs_content} + + {shapes_content} + ''' + + # Convert to data URL + svg_b64 = base64.b64encode(svg.encode()).decode() + return f"data:image/svg+xml;base64,{svg_b64}" + +def create_star_points(cx, cy, outer_r, inner_r, num_points): + """Create points for a star shape.""" + points = [] + for i in range(num_points * 2): + angle = (i * math.pi) / num_points - math.pi / 2 + r = outer_r if i % 2 == 0 else inner_r + x = cx + r * math.cos(angle) + y = cy + r * math.sin(angle) + points.append(f"{x:.1f},{y:.1f}") + return " ".join(points) + +def create_blob_path(cx, cy, radius, rng): + """Create a smooth blob shape using bezier curves.""" + num_points = 6 + points = [] + + # Generate control points around a circle with some randomness + for i in range(num_points): + angle = (i * 2 * math.pi) / num_points + r = radius * rng.uniform(0.7, 1.3) + x = cx + r * math.cos(angle) + y = cy + r * math.sin(angle) + points.append((x, y)) + + # Create smooth bezier path + path_data = f"M {points[0][0]:.1f},{points[0][1]:.1f}" + + for i in range(num_points): + next_i = (i + 1) % num_points + + # Calculate control points for smooth curves + cp1x = points[i][0] + (points[next_i][0] - points[i][0]) * 0.5 + cp1y = points[i][1] + (points[next_i][1] - points[i][1]) * 0.2 + + path_data += f" Q {cp1x:.1f},{cp1y:.1f} {points[next_i][0]:.1f},{points[next_i][1]:.1f}" + + path_data += " Z" + return path_data \ No newline at end of file diff --git a/templates/archive.html b/templates/archive.html index 4aab33a..7b6f489 100644 --- a/templates/archive.html +++ b/templates/archive.html @@ -43,15 +43,22 @@

{{ group_name }}

{% for post in posts %} -

{{ post.title }} - {% if post.pub_date %} - - {% endif %} -
- {% if post.description %} - {{ post.description | unescape }} - {% endif %} -

+
+ {% if post.unique_icon %} + Icon for {{ post.title }} + {% endif %} +
+

{{ post.title }} + {% if post.pub_date %} + + {% endif %} +
+ {% if post.description %} + {{ post.description }} + {% endif %} +

+
+
{% endfor %} {% endfor %} @@ -134,6 +141,32 @@ section h2 { margin-bottom: 1.5rem; } +/* Archive post layout with icons */ +.archive-post { + display: flex; + align-items: flex-start; + gap: 1rem; + margin-bottom: 1.5rem; + padding-left: 1rem; +} + +.archive-post-icon { + width: 24px; + height: 24px; + flex-shrink: 0; + margin-top: 0.3rem; + margin-left: -3rem; +} + +.archive-post-content { + flex: 1; +} + +.archive-post-content p { + margin: 0; +} + +/* Legacy styles for backward compatibility */ section p { margin-bottom: 1.5rem; padding-left: 1rem; @@ -164,6 +197,12 @@ section p:last-child { .year-picker p { font-size: 0.9rem; } + + .archive-post-icon { + margin-left: -2rem; + width: 20px; + height: 20px; + } } {% endblock %} \ No newline at end of file diff --git a/templates/connections.html b/templates/connections.html index 1265eb9..38f0210 100644 --- a/templates/connections.html +++ b/templates/connections.html @@ -15,9 +15,14 @@ {% if articles %} {% for article in articles %}
-

- {{ article.title }} -

+
+ {% if article.unique_icon %} + Icon for {{ article.title }} + {% endif %} +

+ {{ article.title }} +

+