Add initial design system and conceptual homepage layout

- Introduced a comprehensive design system in `dev/design.json` featuring color palettes, typography, spacing, and component guidelines.
- Created a new homepage layout in `dev/index.html` utilizing Tailwind CSS for styling, including a custom cursor and grid overlay for visual structure.
- Implemented responsive design patterns and interactive elements for enhanced user experience.
This commit is contained in:
nicholai 2025-12-06 04:14:08 -07:00
parent 62855f4006
commit 26c2d0aa08
2 changed files with 1426 additions and 0 deletions

823
dev/design.json Normal file
View File

@ -0,0 +1,823 @@
{
"design_system": {
"name": "V7 Industrial Dark Mode System",
"version": "1.0",
"methodology": {
"approach": "Brutalist/Industrial Dark UI",
"philosophy": "Grid-visible, high-contrast, typography-forward design with layered content and subtle glassmorphism",
"characteristics": [
"Dark mode native (not an option, the default)",
"Visible grid structure as design element",
"Massive typography as primary visual hierarchy",
"Minimal rounded corners (sharp, industrial aesthetic)",
"Heavy use of borders and dividers",
"Layered content with overlays and blend modes",
"Subtle animations and smooth transitions",
"Opacity-based depth system",
"Technical/monospace accents"
]
},
"color_palette": {
"primary": {
"brand_dark": {
"hex": "#0B0D11",
"rgb": "11, 13, 17",
"usage": "Primary background, text on light backgrounds",
"opacity_variants": [
{
"name": "brand_dark_80",
"value": "rgba(11, 13, 17, 0.8)"
},
{
"name": "brand_dark_20",
"value": "rgba(11, 13, 17, 0.2)"
}
]
},
"brand_panel": {
"hex": "#151921",
"rgb": "21, 25, 33",
"usage": "Secondary backgrounds, panels, cards"
},
"brand_accent": {
"hex": "#FFB84C",
"rgb": "255, 184, 76",
"name": "Orange/Yellow",
"usage": "Primary accent, CTAs, highlights, interactive elements",
"opacity_variants": [
{
"name": "brand_accent_5",
"value": "rgba(255, 184, 76, 0.05)"
},
{
"name": "brand_accent_20",
"value": "rgba(255, 184, 76, 0.2)"
},
{
"name": "brand_accent_50",
"value": "rgba(255, 184, 76, 0.5)"
}
]
},
"brand_cyan": {
"hex": "#22D3EE",
"rgb": "34, 211, 238",
"usage": "Secondary accent, tags, status indicators"
},
"brand_red": {
"hex": "#E11D48",
"rgb": "225, 29, 72",
"usage": "Tertiary accent, warnings, emphasis"
}
},
"neutrals": {
"white": {
"hex": "#FFFFFF",
"opacity_scale": {
"5": "rgba(255, 255, 255, 0.05)",
"10": "rgba(255, 255, 255, 0.1)",
"20": "rgba(255, 255, 255, 0.2)",
"30": "rgba(255, 255, 255, 0.3)",
"40": "rgba(255, 255, 255, 0.4)",
"60": "rgba(255, 255, 255, 0.6)",
"80": "rgba(255, 255, 255, 0.8)",
"90": "rgba(255, 255, 255, 0.9)",
"100": "rgba(255, 255, 255, 1)"
},
"usage": "Primary text, borders with opacity, overlays"
},
"slate": {
"300": {
"hex": "#CBD5E1",
"usage": "Light text on dark"
},
"400": {
"hex": "#94A3B8",
"usage": "Body text, secondary text"
},
"500": {
"hex": "#64748B",
"usage": "Muted text, placeholders"
},
"600": {
"hex": "#475569",
"usage": "Subtle text, disabled states"
},
"700": {
"hex": "#334155",
"usage": "Borders, dividers"
},
"800": {
"hex": "#1E293B",
"usage": "Backgrounds, panels, borders"
},
"900": {
"hex": "#0F172A",
"usage": "Deep backgrounds"
}
},
"black": {
"hex": "#000000",
"opacity_variants": [
{
"name": "black_20",
"value": "rgba(0, 0, 0, 0.2)"
}
]
}
},
"gradients": {
"primary_hero": {
"type": "linear-gradient",
"direction": "to top right",
"stops": [
"brand_accent",
"orange-500 (via)",
"brand_panel"
],
"css": "bg-gradient-to-tr from-brand-accent via-orange-500 to-brand-panel"
},
"fade_top": {
"type": "linear-gradient",
"direction": "to top",
"stops": [
"brand_dark",
"transparent (via)",
"transparent"
],
"usage": "Image overlays"
},
"card_dramatic": {
"type": "linear-gradient",
"direction": "to bottom right",
"stops": [
"orange-600",
"rose-700"
],
"usage": "Feature cards"
}
}
},
"typography": {
"font_families": {
"primary": {
"name": "Inter",
"type": "sans-serif",
"weights": [
300,
400,
500,
600,
700,
800
],
"usage": "Primary UI font",
"fallback": "sans-serif"
},
"mono": {
"name": "system-ui monospace",
"usage": "Numbers, technical details, small labels",
"examples": [
"font-mono"
]
},
"available_alternatives": [
"Geist",
"Roboto",
"Montserrat",
"Poppins",
"Playfair Display",
"Instrument Serif",
"Merriweather",
"Bricolage Grotesque",
"Plus Jakarta Sans",
"Manrope",
"Space Grotesk",
"Work Sans",
"PT Serif",
"Geist Mono",
"Space Mono",
"Quicksand",
"Nunito"
]
},
"scale": {
"xs": {
"size": "0.75rem",
"line_height": "1rem",
"usage": "Labels, tags, metadata"
},
"sm": {
"size": "0.875rem",
"line_height": "1.25rem",
"usage": "Small body text"
},
"base": {
"size": "1rem",
"line_height": "1.5rem",
"usage": "Body text"
},
"lg": {
"size": "1.125rem",
"line_height": "1.75rem",
"usage": "Large body text"
},
"xl": {
"size": "1.25rem",
"line_height": "1.75rem",
"usage": "Subheadings"
},
"2xl": {
"size": "1.5rem",
"line_height": "2rem",
"usage": "Small headings"
},
"3xl": {
"size": "1.875rem",
"line_height": "2.25rem",
"usage": "Section headings"
},
"4xl": {
"size": "2.25rem",
"line_height": "2.5rem",
"usage": "Page headings"
},
"5xl": {
"size": "3rem",
"line_height": "1",
"usage": "Large headings"
},
"6xl": {
"size": "3.75rem",
"line_height": "1",
"usage": "Hero headings"
},
"7xl": {
"size": "4.5rem",
"line_height": "1",
"usage": "Display text (md)"
},
"8xl": {
"size": "6rem",
"line_height": "1",
"usage": "Display text (lg)"
},
"massive": {
"sizes": [
"12rem",
"18rem (md)",
"22rem (lg)"
],
"line_height": "none",
"usage": "Hero display, massive branding"
}
},
"weights": {
"light": 300,
"normal": 400,
"medium": 500,
"semibold": 600,
"bold": 700,
"extrabold": 800
},
"letter_spacing": {
"tighter": "-0.05em",
"tight": "-0.025em",
"normal": "0",
"wide": "0.025em",
"wider": "0.05em",
"widest": "0.1em"
},
"text_transforms": {
"uppercase": {
"usage": "Labels, navigation, tags, metadata",
"typical_size": "xs or sm",
"typical_tracking": "widest or wider",
"typical_weight": "semibold or bold"
}
},
"line_heights": {
"none": "1",
"tight": "1.25",
"relaxed": "1.625"
},
"patterns": {
"small_label": {
"size": "text-xs",
"weight": "font-bold",
"transform": "uppercase",
"tracking": "tracking-widest",
"example": "text-xs font-bold uppercase tracking-widest"
},
"hero_title": {
"size": "text-[12rem] md:text-[18rem] lg:text-[22rem]",
"weight": "font-bold",
"line_height": "leading-none",
"tracking": "tracking-tighter",
"example_modifiers": "text-transparent bg-clip-text bg-gradient-to-tr"
},
"section_heading": {
"size": "text-4xl md:text-5xl lg:text-6xl",
"weight": "font-semibold",
"tracking": "tracking-tight",
"color": "text-white"
},
"body_text": {
"size": "text-sm md:text-base",
"color": "text-slate-400",
"line_height": "leading-relaxed",
"weight": "font-normal or font-medium"
},
"mono_detail": {
"family": "font-mono",
"size": "text-xs or text-sm",
"weight": "font-medium or font-semibold",
"usage": "Numbers, codes, technical info"
}
}
},
"spacing": {
"scale": {
"0": "0px",
"1": "0.25rem",
"2": "0.5rem",
"3": "0.75rem",
"4": "1rem",
"6": "1.5rem",
"8": "2rem",
"10": "2.5rem",
"12": "3rem",
"16": "4rem",
"20": "5rem",
"24": "6rem",
"32": "8rem",
"48": "12rem"
},
"layout_padding": {
"mobile": "px-6",
"desktop": "lg:px-12",
"standard": "px-6 lg:px-12"
},
"section_spacing": {
"small": "py-8",
"medium": "py-12 md:py-16",
"large": "py-24",
"xlarge": "py-32 lg:py-48"
},
"component_spacing": {
"tight": "gap-2 or gap-3",
"normal": "gap-4 or gap-6",
"loose": "gap-8 or gap-12",
"extra_loose": "gap-12 md:gap-24"
}
},
"grid_system": {
"overlay": {
"enabled": true,
"purpose": "Visible design element",
"implementation": "Fixed position, pointer-events-none, opacity-10",
"columns": {
"mobile": 4,
"tablet": 6,
"desktop": 12
},
"styling": "border-r border-slate-500 h-full"
},
"content_grid": {
"base": "grid grid-cols-1",
"tablet": "md:grid-cols-2 or md:grid-cols-4",
"desktop": "lg:grid-cols-12 or lg:grid-cols-2",
"gap": "gap-6 or gap-8 or gap-12"
},
"column_spans": {
"usage": "lg:col-span-{number}",
"common_patterns": [
"lg:col-span-3 (sidebar)",
"lg:col-span-9 (main content)",
"lg:col-span-4 / lg:col-span-8 (2:1 ratio)",
"lg:col-span-2 / lg:col-span-5 (asymmetric)"
]
}
},
"borders": {
"widths": {
"thin": "border or border-[1px]",
"medium": "border-2",
"thick": "border-[40px] (decorative)"
},
"colors": {
"subtle": "border-white/5",
"standard": "border-white/10 or border-white/20",
"visible": "border-white/30",
"slate_system": "border-slate-700 or border-slate-800",
"accent": "border-brand-accent"
},
"positions": {
"all": "border",
"top": "border-t or border-t-2",
"right": "border-r",
"bottom": "border-b",
"left": "border-l",
"horizontal": "border-x",
"vertical": "border-y"
},
"usage_patterns": {
"section_divider": "border-t border-slate-800",
"card": "border border-white/10 or border border-slate-800",
"active_state": "border-t-2 border-brand-accent",
"hover_state": "hover:border-brand-accent transition-colors"
}
},
"effects": {
"glassmorphism": {
"background": "bg-white/3 or rgba(255, 255, 255, 0.03)",
"backdrop": "backdrop-filter: blur(10px)",
"border": "border border-white/10",
"class_example": ".glass { background: rgba(255, 255, 255, 0.03); backdrop-filter: blur(10px); }"
},
"shadows": {
"accent_glow": "shadow-lg shadow-brand-accent/20",
"cyan_glow": "shadow-lg shadow-brand-cyan/20",
"red_glow": "shadow-lg shadow-brand-red/20",
"custom_glow": "shadow-[0_0_10px_rgba(255,184,76,0.5)]",
"drop_shadow": "drop-shadow-2xl"
},
"blur": {
"small": "blur-sm",
"backdrop": "backdrop-blur-sm"
},
"blend_modes": {
"multiply": "mix-blend-multiply",
"overlay": "mix-blend-overlay",
"luminosity": "mix-blend-luminosity"
},
"opacity": {
"scale": [
0,
5,
10,
20,
30,
40,
60,
90,
100
],
"usage": "opacity-{value}",
"common": {
"invisible": "opacity-0",
"barely_visible": "opacity-5 or opacity-10",
"subtle": "opacity-20 or opacity-30",
"medium": "opacity-40",
"visible": "opacity-60 or opacity-90",
"full": "opacity-100"
}
}
},
"animations": {
"transitions": {
"fast": "duration-300 or transition-all duration-300",
"medium": "duration-500",
"slow": "duration-700 or duration-1000",
"properties": {
"all": "transition-all",
"colors": "transition-colors",
"opacity": "transition-opacity",
"transform": "transition-transform"
},
"easing": {
"standard": "ease-out",
"smooth": "ease-in-out"
}
},
"transforms": {
"scale_subtle": "scale-100 hover:scale-105 transition-transform",
"scale_medium": "group-hover:scale-105 transition-all duration-700",
"scale_bold": "group-hover:scale-110",
"translate_x": "group-hover:translate-x-1 or group-hover:translate-x-0.5",
"translate_y": "translate-y-4 group-hover:translate-y-0"
},
"hover_states": {
"opacity_fade": "opacity-0 group-hover:opacity-100 transition-opacity duration-500",
"color_shift": "hover:text-white transition-colors",
"border_accent": "hover:border-brand-accent transition-all",
"background_lift": "hover:bg-white/5 transition-colors",
"combined": "hover:bg-brand-accent hover:text-brand-dark transition-all"
},
"keyframes": {
"pulse": {
"animation": "animate-pulse",
"usage": "Status indicators, live elements"
}
},
"delays": {
"stagger": "delay-100",
"usage": "Sequential reveals"
}
},
"components": {
"buttons": {
"primary": {
"base": "bg-brand-accent text-brand-dark",
"padding": "px-8 py-4",
"typography": "text-xs font-bold uppercase tracking-widest",
"hover": "hover:bg-amber-400 transition-colors",
"full_class": "bg-brand-accent text-brand-dark text-xs font-bold uppercase px-8 py-4 tracking-widest hover:bg-amber-400 transition-colors"
},
"secondary": {
"base": "bg-white text-brand-dark",
"padding": "px-8 py-4",
"typography": "text-xs font-bold uppercase tracking-widest",
"hover": "hover:bg-slate-100 transition-colors"
},
"ghost": {
"base": "bg-transparent border border-slate-700",
"padding": "px-8 py-4 or p-4",
"typography": "text-xs font-bold uppercase tracking-widest",
"hover": "hover:border-brand-accent hover:bg-brand-accent/5 transition-all"
},
"icon": {
"size": "w-12 h-12",
"base": "border border-slate-700 flex items-center justify-center",
"hover": "hover:bg-white/5 transition-colors",
"rounded": "Can be rounded-full for circular buttons"
},
"cta_with_icon": {
"structure": "inline-flex items-center gap-3",
"example": "VIEW CASE STUDY [icon]"
}
},
"cards": {
"glass_card": {
"background": "bg-white/3 or rgba(255, 255, 255, 0.03)",
"backdrop": "backdrop-blur-sm",
"border": "border border-white/5 or border-white/10",
"padding": "p-6 or p-8",
"hover": "hover:border-brand-accent/50 transition-all"
},
"panel_card": {
"background": "bg-brand-panel",
"border": "border border-slate-800",
"padding": "p-12 lg:p-20"
},
"project_card": {
"structure": "relative overflow-hidden cursor-pointer",
"image": "absolute inset-0 bg-cover bg-center",
"overlay": "absolute inset-0 bg-brand-dark/80 group-hover:bg-brand-dark/20 transition-colors duration-500",
"gradient": "absolute inset-0 bg-gradient-to-t from-brand-dark via-transparent to-transparent",
"content": "absolute bottom-0 left-0 w-full p-8 transform translate-y-4 group-hover:translate-y-0",
"hover": "group-hover:scale-105 transition-transform duration-1000"
},
"feature_card": {
"background": "bg-gradient-to-br from-orange-600 to-rose-700",
"overlay": "absolute inset-0 opacity-20 bg-cover bg-center mix-blend-overlay",
"padding": "p-12 md:p-16",
"min_height": "min-h-[400px]"
}
},
"navigation": {
"header": {
"position": "fixed or relative",
"padding": "px-6 lg:px-12 pt-8",
"structure": "flex items-center justify-between",
"logo": "w-10 h-10 border border-white/20 flex items-center justify-center font-bold text-white tracking-tighter"
},
"nav_links": {
"base": "text-xs font-semibold tracking-widest uppercase text-slate-500",
"hover": "hover:text-white transition-colors",
"active": "text-brand-accent",
"spacing": "gap-12"
},
"footer_nav": {
"grid": "grid grid-cols-4",
"item": "text-[10px] uppercase font-bold text-slate-400 tracking-wider text-center",
"hover": "hover:text-white hover:bg-slate-800 transition-colors",
"padding": "py-4"
}
},
"tabs": {
"container": "grid grid-cols-2 md:grid-cols-4 border-b border-slate-800",
"tab_item": {
"base": "p-6 border-t-2 border-transparent cursor-pointer transition-colors group",
"inactive": "hover:border-slate-700 bg-transparent",
"active": "border-brand-accent bg-white/5"
},
"tab_label": {
"number": "text-xs block mb-2",
"number_inactive": "text-slate-600",
"number_active": "text-brand-accent",
"text": "text-lg",
"text_inactive": "font-medium text-slate-400 group-hover:text-white",
"text_active": "font-semibold text-white"
}
},
"tags": {
"accent_tag": {
"base": "text-[10px] font-bold uppercase tracking-widest",
"background": "bg-brand-accent px-2 py-1",
"text": "text-brand-dark",
"extras": "rounded-sm shadow-lg shadow-brand-accent/20"
},
"category_tag": {
"base": "bg-black/20 or bg-slate-800",
"text": "text-white or text-slate-400",
"padding": "px-3 py-2",
"typography": "text-[10px] font-bold uppercase tracking-wider"
},
"status_indicator": {
"dot": "w-2 h-2 bg-brand-accent rounded-full animate-pulse",
"glow": "shadow-[0_0_10px_rgba(255,184,76,0.5)]",
"label": "text-xs font-bold text-slate-500 tracking-widest uppercase font-mono"
}
},
"dividers": {
"line": "w-24 h-1 bg-white or bg-slate-600",
"hover_expand": "w-16 h-1 hover:w-24 transition-all",
"section": "border-t border-slate-800",
"decorative": "w-1 h-10 bg-brand-accent or w-0.5 h-12"
},
"image_treatments": {
"overlay_pattern": {
"dark_multiply": "absolute inset-0 bg-brand-dark/80 mix-blend-multiply",
"gradient_fade": "absolute inset-0 bg-gradient-to-t from-brand-dark via-transparent to-transparent opacity-90",
"texture": "absolute inset-0 opacity-20 bg-cover bg-center mix-blend-overlay"
},
"hover_zoom": "transition-transform duration-1000 group-hover:scale-105",
"aspect_ratios": "aspect-[4/3] or md:aspect-auto"
},
"metadata_display": {
"structure": "Border-t divider with label above content",
"label": "text-xs font-bold text-slate-500 uppercase mb-2",
"value": "text-white text-lg or text-sm",
"example": "<p class='text-xs font-bold text-slate-500 uppercase mb-2'>Year</p><p class='text-white text-lg'>2023 - 2024</p>"
}
},
"layout_patterns": {
"hero_section": {
"structure": "min-h-[90vh] flex flex-col justify-center",
"padding": "px-6 lg:px-12 pt-20",
"background": "Massive typography with background image at opacity-20",
"grid": "lg:grid-cols-12 with asymmetric column spans",
"title_treatment": "Text-transparent with gradient background-clip, drop-shadow",
"decorative_elements": "Blurred duplicate text layer beneath main text"
},
"two_column_split": {
"grid": "grid grid-cols-1 lg:grid-cols-2",
"borders": "border-t border-r border-b border-slate-800",
"min_height": "min-h-[600px] or min-h-[800px]",
"left_content": "Visual/image with overlays",
"right_content": "Details/text content with vertical padding"
},
"sidebar_layout": {
"grid": "lg:grid-cols-12",
"sidebar": "lg:col-span-3 border-r border-slate-800 p-8 lg:p-10 flex flex-col justify-between",
"main": "lg:col-span-9",
"sidebar_content": "Metadata, stats, navigation"
},
"masonry_grid": {
"base": "grid grid-cols-1 md:grid-cols-2",
"items": "aspect-[4/3] md:aspect-auto with hover effects",
"borders": "border-b border-r border-slate-800 pattern"
},
"full_width_section": {
"padding": "px-6 lg:px-12 py-24 or py-32 lg:py-48",
"border": "border-t border-slate-800",
"max_width": "max-w-7xl mx-auto (optional)"
}
},
"responsive_behavior": {
"breakpoints": {
"sm": "640px",
"md": "768px",
"lg": "1024px",
"xl": "1280px"
},
"patterns": {
"hide_mobile": "hidden md:block or hidden lg:flex",
"show_mobile_only": "lg:hidden",
"responsive_grid": "grid-cols-1 md:grid-cols-2 lg:grid-cols-12",
"responsive_text": "text-3xl md:text-5xl lg:text-6xl",
"responsive_spacing": "px-6 lg:px-12 or py-8 md:py-12 lg:py-16"
},
"mobile_considerations": {
"touch_targets": "Minimum 44px (h-12 w-12)",
"simplified_grids": "Single column on mobile, multi-column on tablet+",
"navigation": "Mobile controls shown below lg breakpoint",
"typography": "Scales down but maintains hierarchy"
}
},
"content_hierarchy": {
"levels": {
"1_mega_branding": {
"size": "text-[12rem] to text-[22rem]",
"treatment": "Gradient, transparent, decorative",
"purpose": "Brand presence, visual anchor"
},
"2_page_title": {
"size": "text-5xl to text-8xl",
"color": "text-white with optional text-slate-500 spans",
"weight": "font-semibold or font-bold"
},
"3_section_heading": {
"size": "text-4xl to text-6xl",
"color": "text-white",
"weight": "font-semibold"
},
"4_subsection": {
"size": "text-2xl to text-4xl",
"color": "text-white",
"weight": "font-medium to font-semibold"
},
"5_body": {
"size": "text-sm to text-lg",
"color": "text-slate-400 or text-slate-300",
"line_height": "leading-relaxed"
},
"6_metadata": {
"size": "text-xs or text-[10px]",
"transform": "uppercase",
"tracking": "tracking-widest or tracking-wider",
"color": "text-slate-500 or text-slate-600",
"weight": "font-bold or font-semibold"
}
}
},
"interactive_states": {
"hover": {
"color_shift": "hover:text-white hover:text-brand-accent",
"background": "hover:bg-white/5 hover:bg-slate-700",
"border": "hover:border-brand-accent hover:border-white",
"scale": "hover:scale-105",
"opacity": "hover:opacity-100 (from lower opacity)"
},
"active": {
"indicators": "Border-t-2 border-brand-accent, bg-white/5, text color change",
"emphasis": "Brighter text, accent color borders"
},
"disabled": {
"opacity": "opacity-40 or opacity-60",
"cursor": "cursor-default",
"colors": "text-slate-600"
},
"focus": {
"treatment": "Follow hover states, no default focus rings visible in design"
}
},
"special_effects": {
"text_stroke": {
"css": "-webkit-text-stroke: 1px rgba(255,255,255,0.1); color: transparent;",
"usage": "Outlined text effect for decorative elements"
},
"gradient_text": {
"classes": "text-transparent bg-clip-text bg-gradient-to-tr from-brand-accent via-orange-500 to-brand-panel",
"usage": "Hero titles, emphasis text"
},
"animated_background": {
"transition": "duration-700 ease-out",
"hover": "opacity changes, scale transforms",
"example": "Image backgrounds that fade/zoom on interaction"
},
"layered_depth": {
"technique": "Multiple absolute positioned divs with different opacities, blend modes",
"layers": [
"Base image (bg-cover)",
"Multiply blend overlay (bg-brand-dark/80)",
"Gradient overlay (bg-gradient-to-t opacity-90)",
"Content layer (z-10 or z-20)"
]
}
},
"accessibility_notes": {
"contrast": "High contrast maintained throughout (white on dark backgrounds)",
"focus_states": "Should be added for keyboard navigation",
"alt_text": "Images need descriptive alt text",
"aria_labels": "Interactive elements need proper labels",
"semantic_html": "Use proper heading hierarchy (currently div-heavy)",
"color_alone": "Don't rely on color alone for information"
},
"design_tokens_summary": {
"primary_background": "#0B0D11",
"secondary_background": "#151921",
"primary_accent": "#FFB84C",
"primary_text": "#FFFFFF",
"secondary_text": "#94A3B8",
"border_subtle": "rgba(255, 255, 255, 0.1)",
"border_standard": "#334155",
"font_primary": "Inter, sans-serif",
"font_mono": "monospace",
"radius_minimal": "0 or 2px (rarely used)",
"shadow_glow": "0 0 10px rgba(255,184,76,0.5)",
"transition_fast": "300ms",
"transition_medium": "500ms",
"transition_slow": "700ms"
},
"implementation_notes": {
"framework": "Tailwind CSS with custom config",
"icons": "Iconify (solar, lucide sets primarily)",
"responsive_images": "Multiple sizes served, lazyload recommended",
"performance": "Optimize large background images, consider lazy loading for below-fold content",
"browser_support": "Modern browsers (backdrop-filter, blend modes require recent versions)",
"dark_mode_only": "No light mode variant in this system"
}
}
}

603
dev/index.html Normal file
View File

@ -0,0 +1,603 @@
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nicholai Vogel | VFX Artist & Technical Generalist</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&family=Space+Mono:wght@400;700&display=swap"
rel="stylesheet">
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
<!-- Design System Configuration -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: {
dark: '#0B0D11', // Primary BG
panel: '#151921', // Secondary BG
accent: '#FFB84C', // Orange/Yellow
cyan: '#22D3EE',
border: 'rgba(255, 255, 255, 0.1)',
borderStrong: 'rgba(255, 255, 255, 0.2)'
}
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
mono: ['Space Mono', 'monospace'],
},
spacing: {
'128': '32rem',
},
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
}
}
}
}
</script>
<style>
/* Custom Utilities based on Design JSON */
body {
background-color: #0B0D11;
color: #ffffff;
overflow-x: hidden;
cursor: none;
/* Custom cursor implementation */
}
/* The Grid Overlay */
.grid-overlay {
background-size: 100px 100px;
background-image:
linear-gradient(to right, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
pointer-events: none;
z-index: 0;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #0B0D11;
}
::-webkit-scrollbar-thumb {
background: #334155;
}
::-webkit-scrollbar-thumb:hover {
background: #FFB84C;
}
/* Typography Utilities */
.text-massive {
line-height: 0.9;
letter-spacing: -0.04em;
}
.text-stroke {
-webkit-text-stroke: 1px rgba(255, 255, 255, 0.1);
color: transparent;
}
/* Animations */
.reveal-text {
opacity: 0;
transform: translateY(20px);
transition: all 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal-text.active {
opacity: 1;
transform: translateY(0);
}
/* Custom Cursor */
.cursor-dot,
.cursor-outline {
position: fixed;
top: 0;
left: 0;
transform: translate(-50%, -50%);
border-radius: 50%;
z-index: 9999;
pointer-events: none;
}
.cursor-dot {
width: 8px;
height: 8px;
background-color: #FFB84C;
}
.cursor-outline {
width: 40px;
height: 40px;
border: 1px solid rgba(255, 184, 76, 0.5);
transition: width 0.2s, height 0.2s, background-color 0.2s;
}
/* Interactive Elements */
.hover-trigger:hover~.cursor-outline,
a:hover~.cursor-outline,
button:hover~.cursor-outline {
width: 60px;
height: 60px;
background-color: rgba(255, 184, 76, 0.05);
border-color: #FFB84C;
}
</style>
</head>
<body class="antialiased selection:bg-brand-accent selection:text-brand-dark">
<!-- Custom Cursor -->
<div class="cursor-dot hidden md:block"></div>
<div class="cursor-outline hidden md:block"></div>
<!-- Fixed Grid Overlay -->
<div class="fixed inset-0 grid-overlay h-screen w-screen"></div>
<!-- 12 Column Guide (Visual Only - Low Opacity) -->
<div
class="fixed inset-0 container mx-auto px-6 lg:px-12 grid grid-cols-4 md:grid-cols-12 gap-4 pointer-events-none z-0 opacity-10 border-x border-white/5">
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
</div>
<!-- Navigation -->
<nav
class="fixed top-0 left-0 w-full z-50 px-6 lg:px-12 py-8 flex justify-between items-center backdrop-blur-sm border-b border-white/5">
<div class="flex items-center gap-4 group cursor-none-target">
<div
class="w-10 h-10 border border-brand-accent/50 flex items-center justify-center bg-brand-accent/5 group-hover:bg-brand-accent transition-colors duration-300">
<span class="font-bold text-brand-accent group-hover:text-brand-dark">NV</span>
</div>
<div class="flex flex-col">
<span class="text-xs font-mono uppercase tracking-widest text-slate-400">Status</span>
<span class="text-xs font-bold text-brand-accent animate-pulse">AVAILABLE FOR WORK</span>
</div>
</div>
<div class="hidden md:flex gap-12">
<a href="#about"
class="text-xs font-bold uppercase tracking-widest text-slate-500 hover:text-white transition-colors">01.
Profile</a>
<a href="#work"
class="text-xs font-bold uppercase tracking-widest text-slate-500 hover:text-white transition-colors">02.
Selected Works</a>
<a href="#experience"
class="text-xs font-bold uppercase tracking-widest text-slate-500 hover:text-white transition-colors">03.
History</a>
<a href="#skills"
class="text-xs font-bold uppercase tracking-widest text-slate-500 hover:text-white transition-colors">04.
Stack</a>
</div>
<a href="mailto:nicholai@nicholai.work"
class="border border-slate-600 px-6 py-3 text-xs font-bold uppercase tracking-widest hover:border-brand-accent hover:bg-brand-accent/10 transition-all cursor-none-target">
Contact
</a>
</nav>
<!-- Main Content -->
<main class="relative z-10 pt-32 lg:pt-48 pb-24">
<!-- Hero Section -->
<section class="container mx-auto px-6 lg:px-12 min-h-[70vh] flex flex-col justify-center relative">
<div class="grid grid-cols-1 md:grid-cols-12 gap-6">
<div class="col-span-12">
<p class="font-mono text-brand-accent mb-4 ml-1 reveal-text">/// TECHNICAL GENERALIST & VFX
SUPERVISOR</p>
<h1
class="text-6xl md:text-8xl lg:text-[10rem] font-bold text-massive uppercase leading-none tracking-tighter mb-8 text-white">
<span class="reveal-text block delay-100">Visual</span>
<span
class="reveal-text block delay-200 text-transparent bg-clip-text bg-gradient-to-tr from-brand-accent to-white">Alchemist</span>
</h1>
</div>
<div
class="col-span-12 md:col-span-6 lg:col-span-5 lg:col-start-8 mt-12 border-l border-brand-accent/30 pl-8 reveal-text delay-300">
<p class="text-slate-400 text-lg leading-relaxed mb-8">
I am a problem solver who loves visual effects. With 10 years of experience creating end-to-end
visual content for clients like <span class="text-white font-semibold">Post Malone</span>, <span
class="text-white font-semibold">Stinkfilms</span>, and <span
class="text-white font-semibold">Adidas</span>. Comfortable managing teams while staying
knee-deep in hands-on shot work.
</p>
<div class="flex gap-4">
<a href="#work"
class="group flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-white hover:text-brand-accent transition-colors">
<span class="w-8 h-[1px] bg-brand-accent group-hover:w-12 transition-all"></span>
View Selected Works
</a>
</div>
</div>
</div>
<!-- Hero Footer -->
<div
class="absolute bottom-0 w-full left-0 px-6 lg:px-12 pb-12 hidden md:flex justify-between items-end opacity-50">
<div class="font-mono text-xs text-slate-500">
LOC: COLORADO SPRINGS<br>
TIME: <span id="clock">00:00:00</span>
</div>
<div class="font-mono text-xs text-slate-500 text-right">
SCROLL<br>
</div>
</div>
</section>
<!-- Divider -->
<div class="w-full h-[1px] bg-white/10 my-24"></div>
<!-- Experience / Timeline -->
<section id="experience" class="container mx-auto px-6 lg:px-12 py-24">
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12">
<div class="lg:col-span-4">
<h2 class="text-4xl font-bold uppercase tracking-tight mb-2 text-stroke">Experience</h2>
<h2 class="text-4xl font-bold uppercase tracking-tight mb-8">History</h2>
<p class="text-slate-400 mb-8 max-w-sm">
From founding my own VFX house to supervising global campaigns. I bridge the gap between
creative vision and technical execution.
</p>
<a href="https://biohazardvfx.com" target="_blank"
class="inline-flex items-center gap-2 text-xs font-mono uppercase text-brand-accent hover:underline">
Visit Biohazard VFX <i data-lucide="arrow-up-right" class="w-4 h-4"></i>
</a>
</div>
<div class="lg:col-span-8 relative">
<!-- Vertical line -->
<div
class="absolute left-0 top-0 bottom-0 w-[1px] bg-gradient-to-b from-brand-accent via-slate-800 to-transparent">
</div>
<!-- Item 1 -->
<div class="pl-12 mb-20 relative reveal-text">
<div
class="absolute left-[-5px] top-2 w-2.5 h-2.5 bg-brand-dark border border-brand-accent rounded-full">
</div>
<div class="flex flex-col md:flex-row md:items-baseline gap-4 mb-4">
<h3 class="text-2xl font-bold text-white uppercase">Biohazard VFX</h3>
<span class="font-mono text-xs text-brand-accent bg-brand-accent/10 px-2 py-1">FOUNDER / VFX
SUPERVISOR</span>
<span class="font-mono text-xs text-slate-500 ml-auto">MAR 2022 - OCT 2025</span>
</div>
<p class="text-slate-400 mb-6 leading-relaxed">
Founded and led a VFX studio specializing in high-end commercial and music video work for
Post Malone, ENHYPEN, and Nike. Architected a custom pipeline combining cloud and
self-hosted infrastructure.
</p>
<ul class="space-y-2 mb-6">
<li class="flex items-start gap-3 text-sm text-slate-300">
<span class="text-brand-accent mt-1"></span>
Designed 7-plate reconciliation workflows for ENHYPEN (projection mapping live action
onto CAD).
</li>
<li class="flex items-start gap-3 text-sm text-slate-300">
<span class="text-brand-accent mt-1"></span>
Developed QA systems for AI-generated assets, transforming mid-tier output into
production-ready deliverables.
</li>
</ul>
</div>
<!-- Item 2 -->
<div class="pl-12 mb-20 relative reveal-text">
<div class="absolute left-[-5px] top-2 w-2.5 h-2.5 bg-slate-700 rounded-full"></div>
<div class="flex flex-col md:flex-row md:items-baseline gap-4 mb-4">
<h3 class="text-2xl font-bold text-white uppercase">Stinkfilms</h3>
<span class="font-mono text-xs text-slate-400 border border-slate-700 px-2 py-1">GLOBAL
PRODUCTION STUDIO</span>
<span class="font-mono text-xs text-slate-500 ml-auto">SUMMER 2024</span>
</div>
<p class="text-slate-400 mb-6 leading-relaxed">
Led Biohazard VFX team (60+ artists) alongside director Felix Brady to create a brand film
for G-Star Raw.
</p>
<div
class="border border-white/5 bg-white/5 p-6 backdrop-blur-sm hover:border-brand-accent/50 transition-colors cursor-pointer group">
<h4 class="text-sm font-bold uppercase text-white mb-2 flex justify-between">
Project: G-Star Raw Olympics Campaign
<i data-lucide="arrow-right"
class="w-4 h-4 opacity-0 group-hover:opacity-100 transition-opacity text-brand-accent"></i>
</h4>
<p class="text-xs text-slate-400 mb-4">Managed full CG environments in Blender/Houdini and
integrated AI/ML workflows (Stable Diffusion reference gen, Copycat cleanup).</p>
<a href="https://stinkfilms.com"
class="text-[10px] font-bold text-brand-accent uppercase tracking-widest">View Case
Study</a>
</div>
</div>
<!-- Item 3 -->
<div class="pl-12 relative reveal-text">
<div class="absolute left-[-5px] top-2 w-2.5 h-2.5 bg-slate-700 rounded-full"></div>
<div class="flex flex-col md:flex-row md:items-baseline gap-4 mb-4">
<h3 class="text-2xl font-bold text-white uppercase">Freelance</h3>
<span class="font-mono text-xs text-slate-400 border border-slate-700 px-2 py-1">2D/3D
ARTIST</span>
<span class="font-mono text-xs text-slate-500 ml-auto">2015 - PRESENT</span>
</div>
<p class="text-slate-400 mb-4 leading-relaxed">
Compositor for Abyss Digital and major labels (Atlantic, Interscope). Clients: David
Kushner, Opium, Lil Durk, Don Toliver.
</p>
</div>
</div>
</div>
</section>
<!-- Divider -->
<div class="container mx-auto px-6 lg:px-12">
<div class="w-full h-[1px] bg-white/10"></div>
</div>
<!-- Featured Project Section (G-Star Raw) -->
<section id="work" class="py-24">
<div class="container mx-auto px-6 lg:px-12 mb-12">
<span class="text-xs font-mono text-brand-accent mb-2 block">/// HIGHLIGHT</span>
<h2 class="text-5xl md:text-7xl font-bold uppercase text-white mb-4">G-Star Raw <span
class="text-stroke">Olympics</span></h2>
</div>
<!-- Full Width Project Card -->
<div class="w-full h-[80vh] relative group overflow-hidden border-y border-white/10">
<!-- Abstract Background representing the project -->
<div
class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2564&auto=format&fit=crop')] bg-cover bg-center transition-transform duration-1000 group-hover:scale-105">
</div>
<div class="absolute inset-0 bg-brand-dark/80 mix-blend-multiply"></div>
<div class="absolute inset-0 bg-gradient-to-t from-brand-dark via-brand-dark/20 to-transparent"></div>
<!-- Grid Overlay on image -->
<div
class="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdHRlcm4gaWQ9ImIiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PGNpcmNsZSBjeD0iMiIgY3k9IjIiIHI9IjEiIGZpbGw9InJnYmEoMjU1LDI1NSwyNTUsMC4xKSIvPjwvcGF0dGVybj48cmVjdCB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIGZpbGw9InVybCgjYikiLz48L3BhdHRlcm4+PC9kZWZzPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjYSkiLz48L3N2Zz4=')] opacity-30">
</div>
<div
class="absolute bottom-0 left-0 w-full p-6 lg:p-12 flex flex-col md:flex-row items-end justify-between">
<div
class="max-w-2xl transform translate-y-8 group-hover:translate-y-0 transition-transform duration-500">
<div class="flex gap-2 mb-4">
<span class="bg-brand-accent text-brand-dark text-[10px] font-bold uppercase px-2 py-1">VFX
Supervision</span>
<span
class="border border-white/30 text-white text-[10px] font-bold uppercase px-2 py-1">AI/ML</span>
<span
class="border border-white/30 text-white text-[10px] font-bold uppercase px-2 py-1">Houdini</span>
</div>
<p class="text-xl md:text-2xl text-white font-medium mb-6">
Managed full CG environment builds, procedural city generation, and integrated AI-generated
normal maps for relighting in Nuke.
</p>
<a href="https://f.io/7ijf23Wm"
class="inline-flex items-center gap-3 text-sm font-bold uppercase tracking-widest text-white hover:text-brand-accent transition-colors">
Watch Making Of <i data-lucide="play-circle" class="w-5 h-5"></i>
</a>
</div>
<div class="hidden md:block text-right">
<span class="block text-[10px] uppercase text-slate-500 tracking-widest mb-1">Year</span>
<span class="block text-2xl font-bold text-white mb-4">2024</span>
<span class="block text-[10px] uppercase text-slate-500 tracking-widest mb-1">Client</span>
<span class="block text-xl font-bold text-white">Stinkfilms</span>
</div>
</div>
</div>
</section>
<!-- Skills Matrix -->
<section id="skills" class="container mx-auto px-6 lg:px-12 py-24 bg-brand-panel border-y border-white/5">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12">
<div class="col-span-1 md:col-span-2 lg:col-span-4 mb-8">
<h2 class="text-4xl font-bold uppercase mb-2">Technical Arsenal</h2>
<p class="text-slate-400 font-mono text-sm">/// SOFTWARE & LANGUAGES</p>
</div>
<!-- Compositing -->
<div>
<h3 class="text-lg font-bold text-white uppercase mb-6 flex items-center gap-2">
<i data-lucide="layers" class="w-4 h-4 text-brand-accent"></i> Compositing
</h3>
<div class="flex flex-wrap gap-2">
<span class="skill-tag">Nuke/NukeX</span>
<span class="skill-tag">ComfyUI</span>
<span class="skill-tag">After Effects</span>
<span class="skill-tag">Photoshop</span>
<span class="skill-tag">Deep Compositing</span>
<span class="skill-tag">Live Action VFX</span>
</div>
</div>
<!-- 3D -->
<div>
<h3 class="text-lg font-bold text-white uppercase mb-6 flex items-center gap-2">
<i data-lucide="box" class="w-4 h-4 text-brand-accent"></i> 3D Generalist
</h3>
<div class="flex flex-wrap gap-2">
<span class="skill-tag">Houdini</span>
<span class="skill-tag">Blender</span>
<span class="skill-tag">Maya</span>
<span class="skill-tag">USD</span>
<span class="skill-tag">Solaris/Karma</span>
<span class="skill-tag">Unreal Engine</span>
<span class="skill-tag">Substance</span>
<span class="skill-tag">Procedural Gen</span>
</div>
</div>
<!-- AI/ML -->
<div>
<h3 class="text-lg font-bold text-white uppercase mb-6 flex items-center gap-2">
<i data-lucide="cpu" class="w-4 h-4 text-brand-accent"></i> AI/ML Integration
</h3>
<div class="flex flex-wrap gap-2">
<span class="skill-tag bg-brand-accent/10 border-brand-accent/50 text-brand-accent">Stable
Diffusion</span>
<span class="skill-tag">LoRA Training</span>
<span class="skill-tag">Dataset Prep</span>
<span class="skill-tag">Synthetic Data</span>
<span class="skill-tag">Prompt Engineering</span>
</div>
</div>
<!-- Dev -->
<div>
<h3 class="text-lg font-bold text-white uppercase mb-6 flex items-center gap-2">
<i data-lucide="code" class="w-4 h-4 text-brand-accent"></i> Development
</h3>
<div class="flex flex-wrap gap-2">
<span class="skill-tag">Python</span>
<span class="skill-tag">JavaScript</span>
<span class="skill-tag">React</span>
<span class="skill-tag">Docker</span>
<span class="skill-tag">Linux</span>
<span class="skill-tag">Pipeline Dev</span>
</div>
</div>
</div>
</section>
<!-- Footer / Contact -->
<footer class="container mx-auto px-6 lg:px-12 py-32 relative overflow-hidden">
<div class="grid grid-cols-1 md:grid-cols-2 gap-12 items-end">
<div>
<h2
class="text-6xl md:text-8xl font-bold uppercase leading-none tracking-tighter mb-8 text-white group cursor-pointer">
Let's<br>
<span
class="text-stroke group-hover:text-brand-accent transition-colors duration-300">Build</span><br>
Reality.
</h2>
<div class="flex flex-wrap gap-6 mt-12">
<a href="mailto:nicholai@nicholai.work" class="btn-primary">nicholai@nicholai.work</a>
<a href="tel:7196604281" class="btn-ghost">719 660 4281</a>
</div>
</div>
<div class="md:text-right">
<div class="mb-12">
<p class="text-xs font-bold uppercase text-slate-500 mb-4 tracking-widest">Social Uplink</p>
<ul class="space-y-2">
<li><a href="https://nicholai.work"
class="text-white hover:text-brand-accent text-lg font-mono">nicholai.work</a></li>
<li><a href="#"
class="text-white hover:text-brand-accent text-lg font-mono">@nicholai.exe</a></li>
<li><a href="#" class="text-white hover:text-brand-accent text-lg font-mono">LinkedIn</a>
</li>
</ul>
</div>
<div class="flex justify-end items-end gap-2 text-[10px] text-slate-600 font-mono uppercase">
<span>© 2025 Nicholai Vogel</span>
<span>/</span>
<span>V7 SYSTEM</span>
</div>
</div>
</div>
<!-- Decorative huge text bg -->
<div class="absolute -bottom-10 left-0 w-full text-center pointer-events-none opacity-5">
<span class="text-[15rem] font-bold text-white uppercase leading-none whitespace-nowrap">VOGEL</span>
</div>
</footer>
</main>
<style>
/* Component Classes */
.skill-tag {
@apply text-[10px] font-mono font-bold uppercase tracking-wider px-2 py-2 border border-slate-700 text-slate-400 hover:border-brand-accent hover:text-white transition-colors cursor-default select-none;
}
.btn-primary {
@apply bg-brand-accent text-brand-dark px-8 py-4 text-xs font-bold uppercase tracking-widest hover:bg-white transition-colors inline-block;
}
.btn-ghost {
@apply border border-slate-600 text-white px-8 py-4 text-xs font-bold uppercase tracking-widest hover:border-brand-accent hover:bg-brand-accent/5 transition-colors inline-block;
}
</style>
<script>
// Icons
lucide.createIcons();
// Clock
function updateClock() {
const now = new Date();
const timeString = now.toLocaleTimeString('en-US', {hour12: false, timeZone: 'America/Denver'});
document.getElementById('clock').textContent = timeString + " MST";
}
setInterval(updateClock, 1000);
updateClock();
// Custom Cursor Logic
const cursorDot = document.querySelector('.cursor-dot');
const cursorOutline = document.querySelector('.cursor-outline');
window.addEventListener('mousemove', (e) => {
const posX = e.clientX;
const posY = e.clientY;
// Dot follows instantly
cursorDot.style.left = `${posX}px`;
cursorDot.style.top = `${posY}px`;
// Outline follows with slight delay (handled by CSS transition, we just set position)
// But for smoother JS animation:
cursorOutline.animate({
left: `${posX}px`,
top: `${posY}px`
}, {duration: 500, fill: "forwards"});
});
// Intersection Observer for Reveal Animations
const observerOptions = {
threshold: 0.1,
rootMargin: "0px"
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('active');
}
});
}, observerOptions);
document.querySelectorAll('.reveal-text').forEach(el => {
observer.observe(el);
});
</script>
</body>
</html>