import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
import { AnimatePresence, motion } from "framer-motion";

const media = {
  logo: "./assets/branding/Logo.svg",
  faviconDark: "./assets/branding/Favicon_Dark.png",
  faviconLight: "./assets/branding/Favicon_Light.png",
  hero: "./assets/showreel/showreel_poster_desktop.webp",
  heroMobile: "./assets/showreel/showreel_poster_mobile.webp",
  reel: "./assets/showreel/showreel_desktop.mp4",
  reelMobile: "./assets/showreel/showreel_mobile.mp4",
  reelHq: "./assets/showreel/showreel_desktop_hq.mp4",
  reelMobileHq: "./assets/showreel/showreel_mobile_hq.mp4",
  reelBlurMask: "./assets/showreel/gaussian_blur_mask.webp",
  film1: "https://cdn.coverr.co/videos/coverr-modern-house-exterior-9714/1080p.mp4",
  film2: "https://cdn.coverr.co/videos/coverr-luxury-house-in-the-forest-5244/1080p.mp4",
  film3: "https://cdn.coverr.co/videos/coverr-modern-architecture-1574/1080p.mp4",
  work1: "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?q=80&w=1600&auto=format&fit=crop",
  work2: "https://images.unsplash.com/photo-1600566753190-17f0baa2a6c3?q=80&w=1600&auto=format&fit=crop",
  work3: "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?q=80&w=1600&auto=format&fit=crop",
  work4: "https://images.unsplash.com/photo-1600607687920-4e2a09cf159d?q=80&w=1600&auto=format&fit=crop",
  work5: "https://images.unsplash.com/photo-1600210492486-724fe5c67fb0?q=80&w=1600&auto=format&fit=crop",
  work6: "https://images.unsplash.com/photo-1600566753151-384129cf4e3e?q=80&w=1600&auto=format&fit=crop",
  work7: "https://images.unsplash.com/photo-1600607687644-c7171b42498f?q=80&w=1600&auto=format&fit=crop",
  work8: "https://images.unsplash.com/photo-1600573472550-8090b5e0745e?q=80&w=1600&auto=format&fit=crop",
  work9: "https://images.unsplash.com/photo-1600607688066-890987f18a86?q=80&w=1600&auto=format&fit=crop",
  work10: "https://images.unsplash.com/photo-1600566752355-35792bedcfea?q=80&w=1600&auto=format&fit=crop",
  about1: "https://images.unsplash.com/photo-1497366754035-f200968a6e72?q=80&w=1600&auto=format&fit=crop",
  about2: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?q=80&w=1600&auto=format&fit=crop",
  about3: "https://images.unsplash.com/photo-1511818966892-d7d671e672a2?q=80&w=1600&auto=format&fit=crop",
  portrait: "./assets/branding/Portrait.jpg",
  serviceStills: "https://images.unsplash.com/photo-1600585154526-990dced4db0d?q=80&w=2400&auto=format&fit=crop",
  serviceFilms: "https://images.unsplash.com/photo-1600607688969-a5bfcd646154?q=80&w=2400&auto=format&fit=crop",
  serviceCinemagraphs: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?q=80&w=2400&auto=format&fit=crop",
  serviceVirtualTours: "https://images.unsplash.com/photo-1600566753086-00f18fb6b3ea?q=80&w=2400&auto=format&fit=crop",
  serviceDevelopment: "https://images.unsplash.com/photo-1497366754035-f200968a6e72?q=80&w=2400&auto=format&fit=crop",
  serviceImmersive: "https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?q=80&w=2400&auto=format&fit=crop",
  serviceWeb: "https://images.unsplash.com/photo-1518005020951-eccb494ad742?q=80&w=2400&auto=format&fit=crop",
  serviceShotPlanning: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?q=80&w=2400&auto=format&fit=crop",
  serviceInterior: "https://images.unsplash.com/photo-1600566753190-17f0baa2a6c3?q=80&w=2400&auto=format&fit=crop",
  serviceBrand: "https://images.unsplash.com/photo-1600566753086-00f18fb6b3ea?q=80&w=2400&auto=format&fit=crop",
  journal1: "https://images.unsplash.com/photo-1600607687920-4e2a09cf159d?q=80&w=1200&auto=format&fit=crop",
  journal2: "https://images.unsplash.com/photo-1497366754035-f200968a6e72?q=80&w=1200&auto=format&fit=crop",
  journal3: "https://images.unsplash.com/photo-1600585154526-990dced4db0d?q=80&w=1200&auto=format&fit=crop",
  montiSuites: "./assets/practice/monti_suites_&_scenery/monti1.webp",
  montiSuitesHq: "./assets/practice/monti_suites_&_scenery/monti1_hq.webp",
  antasAtrium1: "./assets/practice/antas_atrium/antasatrium1.webp",
  antasAtrium1Hq: "./assets/practice/antas_atrium/antasatrium1_hq.webp",
  antasAtrium2: "./assets/practice/antas_atrium/antasatrium2.webp",
  antasAtrium2Hq: "./assets/practice/antas_atrium/antasatrium2_hq.webp",
  antasAtrium3Hq: "./assets/practice/antas_atrium/antasatrium3_hq.webp",
  antasAtrium4: "./assets/practice/antas_atrium/antasatrium4.mp4",
  antasAtrium4Hq: "./assets/practice/antas_atrium/antasatrium4_services_hq.mp4",
  beachsideHotel1: "./assets/practice/beachside_hotel/beachsidehotel1_hq.webp",
  beachsideHotel1Hq: "./assets/practice/beachside_hotel/beachsidehotel1_hq.webp",
  beachsideHotel2: "./assets/practice/beachside_hotel/beachsidehotel2.webp",
  beachsideHotel2Hq: "./assets/practice/beachside_hotel/beachsidehotel2_hq.webp",
  beachsideHotelVideoThumb: "./assets/practice/beachside_hotel/beachsidehotel_video_thumb.mp4",
  caxiasHeights1: "./assets/practice/caxias_heights/caxiasheights1.webp",
  caxiasHeights1Hq: "./assets/practice/caxias_heights/caxiasheights1_hq.webp",
  caxiasHeights2Hq: "./assets/practice/caxias_heights/caxiasheights2_hq.webp",
  ericeiraResidences1: "./assets/practice/ericeira_residences/ericeiraresidences1.webp",
  ericeiraResidences1Hq: "./assets/practice/ericeira_residences/ericeiraresidences1_hq.webp",
  ericeiraResidences2Hq: "./assets/practice/ericeira_residences/ericeiraresidences2_hq.webp",
  ericeiraResidences3Hq: "./assets/practice/ericeira_residences/ericeiraresidences3_hq.webp",
  estrelaResidence1Hq: "./assets/practice/estrela_residence/estrelaresidence1_hq.webp",
  estrelaResidence2: "./assets/practice/estrela_residence/estrelaresidence2.webp",
  estrelaResidence2Hq: "./assets/practice/estrela_residence/estrelaresidence2_hq.webp",
  pineVillasMeco1Hq: "./assets/practice/pine_villas_meco/pinevillasmeco1_hq.webp",
  pineVillasMeco2Hq: "./assets/practice/pine_villas_meco/pinevillasmeco2_hq.webp",
  pineVillasMeco3Hq: "./assets/practice/pine_villas_meco/pinevillasmeco3_hq.webp",
  pineVillasMeco4Hq: "./assets/practice/pine_villas_meco/pinevillasmeco4_hq.webp",
  pineVillasMeco5Hq: "./assets/practice/pine_villas_meco/pinevillasmeco5_hq.webp",
  pineVillasMeco6Hq: "./assets/practice/pine_villas_meco/pinevillasmeco6_hq.webp",
  pineVillasMeco7: "./assets/practice/pine_villas_meco/pinevillasmeco7.webp",
  pineVillasMeco7Hq: "./assets/practice/pine_villas_meco/pinevillasmeco7_hq.webp",
  pineVillasMeco8Hq: "./assets/practice/pine_villas_meco/pinevillasmeco8_hq.webp",
  valeRealResidences1Hq: "./assets/practice/vale_real_residences/valerealresidences1_hq.webp",
  valeRealResidences2Hq: "./assets/practice/vale_real_residences/valerealresidences2_hq.webp",
  valeRealResidences3Hq: "./assets/practice/vale_real_residences/valerealresidences3_hq.webp",
  valeRealResidences4Hq: "./assets/practice/vale_real_residences/valerealresidences4_hq.webp",
  valeRealResidences5Hq: "./assets/practice/vale_real_residences/valerealresidences5_hq.webp",
  valeRealResidences6Hq: "./assets/practice/vale_real_residences/valerealresidences6_hq.webp",
  valeRealResidences6ServicesHq: "./assets/practice/vale_real_residences/valerealresidences6_services_hq.mp4",
  valeRealResidencesVideoThumb: "./assets/practice/vale_real_residences/valerealresidences_video_thumb.mp4",
};

const tagGroups = {
  sector: ["Residential", "Commercial", "Hospitality", "Urbanism", "Civic", "Product Design"],
  intent: ["Competition", "Concept", "Branding", "Marketing", "Internal Iteration", "Study"],
};

const tagGroupByName = Object.fromEntries([
  ...tagGroups.sector.map((tag) => [tag, "sector"]),
  ...tagGroups.intent.map((tag) => [tag, "intent"]),
]);

const servicesData = [
  { title: "Stills", img: media.estrelaResidence1Hq, copy: "Photographic architectural images with controlled composition, material accuracy and a clear visual position for each project." },
  { title: "Films", img: media.antasAtrium2Hq, video: media.antasAtrium4Hq, videoMobile: media.antasAtrium4, copy: "Cinematic architectural films built around pacing, atmosphere, spatial rhythm and carefully planned camera movement." },
  { title: "Cinemagraphs", img: media.valeRealResidences6Hq, video: media.valeRealResidences6ServicesHq, copy: "Static camera shots with selected animated elements such as moving vegetation, curtains, water, light shifts or time-lapse details." },
  { title: "Virtual Tours", img: media.serviceVirtualTours, copy: "Navigable 360 degrees or interactive spatial presentations for sales, design validation and remote project communication.", learnMore: true },
  { title: "Project Development", img: media.valeRealResidences1Hq, copy: "Long-term look development for architecture studios, including material tests, massing and structure studies, landscaping, site integration and surrounding context exploration.", learnMore: true },
  { title: "Immersive Experiences", img: media.serviceImmersive, copy: "Real-time and spatial experiences for presentations, launches, exhibitions or interactive client-facing environments.", learnMore: true },
  { title: "Shot Planning", img: media.serviceShotPlanning, copy: "Planning for in-loco photography used in visualization composites, including location mapping, camera position, lens logic, timing and lighting conditions.", learnMore: true },
  { title: "Interior Design", img: media.valeRealResidences4Hq, copy: "Interior concepts, mood direction, styling logic, material palettes and spatial atmosphere development for residential and commercial spaces." },
];

const humanValueCards = [
  { title: "Originality & Pushing the Boundaries of Creativity", kicker: "Abstraction beyond interpolation", copy: "AI is strongest when interpolating from what already exists and making short leaps from familiar patterns. Meaningful creative work often depends on abstraction: naming what is not obvious yet, pushing beyond the average of the dataset, and shaping images with a point of view clients can actually own." },
  { title: "Rigor", kicker: "Every decision has to survive scrutiny", copy: "Good visual work is not only a beautiful frame. It is scale, proportion, material behavior, camera logic, lighting continuity, references, constraints, and a thousand small checks that keep an image from becoming impressive but wrong." },
  { title: "Consistency & Reliability", kicker: "A project needs a steady hand", copy: "Clients do not only need one strong image. They need a visual language that holds across angles, deliverables, revisions, deadlines, and changing project information. That kind of continuity comes from process, judgment, and care." },
  { title: "Taste & Curation", kicker: "Knowing what to leave out", copy: "Tools can generate options quickly, but taste is the discipline of choosing. It is knowing which references matter, which details weaken the image, when restraint is stronger than spectacle, and how to make the work feel specific rather than generic." },
  { title: "Real-World Context and Nuanced Field Experience", kicker: "Images live inside real decisions", copy: "Architectural visualization is tied to budgets, planning, marketing, client expectations, competitions, design intent, and technical ambiguity. Human experience helps translate those pressures into images that serve the project instead of only looking polished." },
  { title: "Accountability", kicker: "Someone has to stand behind the work", copy: "When something needs to be clarified, corrected, defended, or improved, accountability matters. A human collaborator can explain decisions, take responsibility for revisions, and adapt the process when the brief changes." },
  { title: "Trust and Relationship", kicker: "The work gets better when context compounds", copy: "The strongest collaborations build memory. Over time, a human partner understands how a client thinks, what the project values, which risks matter, and where the image needs to carry more than surface appeal." },
];

const socialLinks = {
  instagram: "https://www.instagram.com/afoviz",
  youtube: "https://www.youtube.com/@afoviz",
};

const siteMetadata = {
  root: {
    title: "Afonso Gon\u00e7alves | Visualization, Portfolio & Personal",
    description: "A curated gateway to my professional and personal work. Explore architectural visualisation services, selected portfolio projects, and the ideas, interests, and experiences that shape my creative practice.",
  },
  visualization: {
    title: "AFOVIZ | CGI & Architectural Visualization",
    description: "Freelance architectural visualisation artist creating high-end CGI imagery for residential, commercial, and hospitality projects. Combining architectural rigor with creative direction to craft distinctive visuals and compelling visual narratives.",
  },
  portfolio: {
    title: "Afonso Gon\u00e7alves | Selected Portfolio",
    description: "Selected CGI and visual work by Afonso Gon\u00e7alves, focused on technical workflows, image-making skills, and production range.",
  },
  personal: {
    title: "Afonso Gon\u00e7alves | Thoughts & Adventures",
    description: "A personal corner of the internet dedicated to curiosity, creativity, nature, technology, travel, books, and the ongoing process of learning about the world.",
  },
};

const canonicalOrigin = "https://afoviz.com";

function metadataForPath(sitePath) {
  if (sitePath === "/visualization") return siteMetadata.visualization;
  if (sitePath === "/portfolio") return siteMetadata.portfolio;
  if (sitePath === "/personal") return siteMetadata.personal;
  return siteMetadata.root;
}

function canonicalUrlForPath(sitePath) {
  return `${canonicalOrigin}${sitePath === "/" ? "/" : sitePath}`;
}

function updateMetaTag(selector, attributeName, attributeValue, content) {
  let tag = document.head.querySelector(selector);
  if (!tag) {
    tag = document.createElement("meta");
    tag.setAttribute(attributeName, attributeValue);
    document.head.appendChild(tag);
  }
  tag.setAttribute("content", content);
}

function updateCanonical(url) {
  let tag = document.head.querySelector('link[rel="canonical"]');
  if (!tag) {
    tag = document.createElement("link");
    tag.rel = "canonical";
    document.head.appendChild(tag);
  }
  tag.href = url;
}

const portfolioAboutCopy = "I'm a multidisciplinary CG generalist artist with an audiovisual and multimedia background, working across real-estate, film, design and real-time pipelines. My practice is shaped by multiple factors, ranging from a passion for the intricacies and oddities of botany, the myriad of design choices that compose an appealing shot, and the development of deliverables that are as rigorous and grounded as they are magical and emotionally binding.";

const resumeData = {
  name: "Afonso Gon\u00e7alves",
  role: "Architectural Visualization Artist",
  location: "Lisbon, Portugal",
  source: "afoviz.com/portfolio",
  intro: portfolioAboutCopy,
  experience: [
    { period: "2024-Present", title: "Freelance Architectural Visualization Artist", place: "", detail: "" },
    { period: "2024-2026", title: "Full-Time Generalist CG Artist", place: "Druid", detail: "Closely involved in the development of high-quality architectural visualizations for large-scale project pipelines, from extensive public service infrastructure to high-end residential developments and office buildings, for architectural firms, real estate developers and interior design agencies. Worked on projects for some of the biggest players in the Iberian Peninsula's residential development market." },
    { period: "2021-2025", title: "Game Art Design", place: "Freelance", detail: "Created digital promotional artwork for online game marketplaces, including the best-selling video game Minecraft, strategically using design principles and trend data to attract customers and convey product essence, leading to Microsoft editorial board features and record-breaking sales across multiple online studios." },
    { period: "2019-2023", title: "3D Hard-Surface Modeling & Texture Artist", place: "Freelance", detail: "Created high-fidelity 3D assets and immersive digital environments for commercial use in virtual meetings, event mockups and product showcases, optimizing for both real-time and path-traced rendering. Collaborated with multidisciplinary teams to deliver assets that met technical specifications, utilizing industry-standard tools for modeling, texturing and optimization." },
    { period: "2019", title: "Intern Video Editor", place: "WTS Films", detail: "Edited engaging video content optimized for web formats." },
  ],
  proficiency: [
    "3ds Max (Corona Render, V-Ray, Chaos Scatter, Forest Pack, RailClone)",
    "Blender (Cycles, Eevee)",
    "Substance Painter",
    "Marvelous Designer",
    "Marmoset Toolbag",
    "Unreal Engine",
    "World Creator",
    "EmberGen",
    "Blackmagic Fusion & Resolve",
    "Adobe Creative Suite",
    "AutoCAD & TrueView (project reading, interpretation and miscellaneous clean-up tasks)",
  ],
  aiNote: "I choose to adopt AI technology with an underscoring of data privacy, good will and critical thinking, only whenever it's logical to do so as a means of streamlining workflow without disrupting creative process and end result.",
  education: [
    { period: "2024", title: "3ds Max Exterior & Interior Visualizations 2.0", place: "AVA", detail: "" },
    { period: "2020-2023", title: "Bachelor of Arts: Audiovisual & Multimedia", place: "University of Social Communications, Lisbon, Portugal", detail: "Proven audiovisual and multimedia background encompassing a diverse set of skills, from a broad array of 3D pipelines to design and some art direction. Extensive knowledge of artistic and communication principles to convey engaging artwork that resonates with the audience." },
    { period: "2020", title: "Hard Surface Modeling in Blender", place: "CG Masters", detail: "" },
    { period: "2019", title: "X-Particles 4 for Cinema 4D", place: "LinkedIn Learning", detail: "" },
    { period: "2019", title: "Learning Cinema 4D R19", place: "LinkedIn Learning", detail: "" },
    { period: "2019", title: "Unreal Essential Training", place: "LinkedIn Learning", detail: "" },
  ],
  certificates: [
    { period: "2024", title: "C2 English Proficiency Certificate", place: "EF SET" },
  ],
};

const pickedProjectIds = ["monti-suites-scenery", "antas-atrium", "beachside-hotel", "ericeira-residences", "pine-villas-meco", "vale-real-residences", "estrela-residence", "caxias-heights"];

const workItems = [
  { id: "monti-suites-scenery", type: "image", title: "Monti Suites & Scenery", src: media.montiSuites, hqSrc: media.montiSuitesHq, date: "2025-01-01", tags: ["Hospitality", "Concept"], location: "Alentejo, Portugal", year: "2025", description: "A hospitality concept study shaped around atmosphere, material warmth and a calm sense of place." },
  { id: "antas-atrium", type: "before", title: "Antas Atrium", before: media.antasAtrium2, after: media.antasAtrium1, src: media.antasAtrium1, hqSrc: media.antasAtrium1Hq, projectComparison: { before: media.antasAtrium2Hq, after: media.antasAtrium1Hq }, galleryHq: [media.antasAtrium2Hq, media.antasAtrium3Hq], date: "2026-01-01", tags: ["Commercial", "Residential", "Marketing"], location: "Porto, Portugal", year: "2026", description: "A commercial concept study exploring atmosphere, composition and spatial clarity through a before-and-after visual development pair." },
  { id: "beachside-hotel", type: "video", title: "Beachside Hotel", src: media.beachsideHotelVideoThumb, poster: media.beachsideHotel2, hqSrc: media.beachsideHotel2Hq, projectComparison: { before: media.beachsideHotel1Hq, after: media.beachsideHotel2Hq }, date: "2026-02-14", tags: ["Hospitality", "Marketing"], location: "Lagoa, Portugal", year: "2026", description: "A hospitality marketing image set for a beachside hotel, shaped around warm atmosphere, destination appeal and a clear sense of place." },
  { id: "caxias-heights", type: "image", title: "Caxias Heights", src: media.caxiasHeights1, hqSrc: media.caxiasHeights1Hq, galleryHq: [media.caxiasHeights2Hq], date: "2024-01-28", tags: ["Residential", "Marketing"], location: "Caxias, Portugal", year: "2024", description: "A residential marketing image set focused on elevated views, warm atmosphere and a clear sense of coastal living." },
  { id: "ericeira-residences", type: "image", title: "Ericeira Residences", src: media.ericeiraResidences1, hqSrc: media.ericeiraResidences1Hq, galleryHq: [media.ericeiraResidences2Hq, media.ericeiraResidences3Hq], date: "2026-01-01", tags: ["Residential", "Marketing"], location: "Ericeira, Portugal", year: "2026", description: "A residential marketing image set for Ericeira Residences, shaped around coastal atmosphere, clear spatial storytelling and destination appeal." },
  { id: "estrela-residence", type: "image", title: "Estrela Residence", src: media.estrelaResidence2, hqSrc: media.estrelaResidence2Hq, galleryHq: [media.estrelaResidence1Hq], date: "2026-01-01", tags: ["Residential", "Internal Iteration"], location: "Lisbon, Portugal", year: "2026", description: "A residential internal iteration study for Estrela Residence, developed to refine atmosphere, material presence and visual direction." },
  { id: "pine-villas-meco", type: "image", title: "Pine Villas Meco", src: media.pineVillasMeco7, hqSrc: media.pineVillasMeco7Hq, thumbnailPosition: "72% center", galleryHq: [media.pineVillasMeco1Hq, media.pineVillasMeco2Hq, media.pineVillasMeco3Hq, media.pineVillasMeco4Hq, media.pineVillasMeco5Hq, media.pineVillasMeco6Hq, media.pineVillasMeco8Hq], date: "2026-01-01", tags: ["Residential", "Marketing", "Internal Iteration"], location: "Meco, Portugal", year: "2026", description: "A residential marketing and iteration image set for villas shaped by coastal light, pine landscape and warm exterior atmosphere." },
  { id: "vale-real-residences", type: "video", title: "Vale Real Residences", src: media.valeRealResidencesVideoThumb, hqSrc: media.valeRealResidences2Hq, projectComparison: { before: media.valeRealResidences1Hq, after: media.valeRealResidences2Hq }, galleryHq: [media.valeRealResidences1Hq, media.valeRealResidences3Hq, media.valeRealResidences4Hq, media.valeRealResidences5Hq], date: "2025-01-01", tags: ["Residential", "Marketing", "Internal Iteration"], location: "Vale do Lobo, Portugal", year: "2025", description: "A residential marketing and internal iteration study developed to explore atmosphere, presentation clarity and visual direction for Vale Real Residences." },
];

const portfolioWorkflowTags = ["TECHNICAL MODELING", "CREATIVE MODELING", "TEXTURING", "LIGHTING", "SET DRESSING", "COMPOSITING", "POST-PRODUCTION", "VFX"];

const portfolioWorkflowTagsById = {
  "monti-suites-scenery": ["TECHNICAL MODELING", "CREATIVE MODELING", "LIGHTING", "SET DRESSING", "COMPOSITING", "POST-PRODUCTION"],
  "antas-atrium": ["TECHNICAL MODELING", "LIGHTING", "SET DRESSING"],
  "beachside-hotel": ["TECHNICAL MODELING", "LIGHTING", "SET DRESSING"],
  "caxias-heights": ["TECHNICAL MODELING", "SET DRESSING"],
  "ericeira-residences": ["TECHNICAL MODELING", "LIGHTING", "SET DRESSING"],
  "estrela-residence": ["TECHNICAL MODELING", "LIGHTING", "SET DRESSING"],
  "pine-villas-meco": ["TECHNICAL MODELING", "LIGHTING", "SET DRESSING"],
  "vale-real-residences": ["TECHNICAL MODELING", "LIGHTING", "SET DRESSING"],
};

const portfolioItems = workItems.map((item) => ({
  ...item,
  tags: portfolioWorkflowTagsById[item.id] || [],
  portfolioSourceTags: item.tags,
}));

const portfolioHeroImages = [
  media.pineVillasMeco8Hq,
  media.montiSuitesHq,
  media.antasAtrium2Hq,
  media.estrelaResidence1Hq,
  media.beachsideHotel2Hq,
];

const blogPosts = [
  {
    slug: "the-utmost-importance-of-human-reasoning",
    title: "The Utmost Importance of Human Reasoning",
    copy: "Why beautiful imagery, realistic imagery and meaningful project representation are not the same thing.",
    image: media.montiSuites,
    body: [
      "In architectural visualization, and increasingly in visual media as a whole, we're reaching a point where creating visually appealing imagery is becoming extremely accessible. That accessibility can't come without ramifications, of course.",
      "We're already seeing a lot of it in the form of AI-generated content produced with very little human intervention and very little grounded human input, through the so-called fully 'agentic' workflows. More often than not, the result is content that feels generic, detached from the very thing it is supposed to represent, and in some cases can even be used in misleading or unethical ways. At the same time, there are undeniably good aspects to being able to accelerate the creation of compelling imagery. We're getting closer to generating increasingly convincing results, increasingly quickly, and as someone working in CG, I can absolutely see the value in that.",
      "These generative systems, as the name implies, generate outputs. They generate them by navigating what is called a 'latent space', and finding statistically plausible relationships between concepts. Making a prompt more complex allows you to search more precisely through that latent space, but it doesn't fundamentally change what the model knows. You're refining the search rather than creating novel understanding. The output may become more specific, more refined and more convincing, but it remains constrained by the knowledge that already exists within the model.",
      "What I think is getting lost somewhat is the distinction between beautiful imagery and realistic imagery. It's becoming increasingly easy to create beautiful-looking imagery. Creating imagery that is truly realistic is a completely different challenge, and I think it's important to separate those two things because they're often treated as though they're interchangeable.",
      "While progress is certainly, and thankfully, being made in generating more realistic imagery faster through more intelligent denoising algorithms and generative AI-enabled path tracing workflows, it feels as though that side of the equation is developing at a slower pace than our ability to purely and simply generate imagery that is superficially aesthetically pleasing with little human control. That makes sense. There are thousands of edge cases, nuances and variables involved. More than that, realism extends far beyond optics.",
      "Optical realism is certainly part of the equation, but so are physical factors, technical factors and human contextual factors. Any one of these can completely break an image if it is represented incorrectly. In architectural visualization, structural realism is a good example. The technical realities of how things are built and laid out, how materials behave, how people interact with spaces, how those spaces function in relation to one another, and how a project actually works in the real world are not secondary concerns. They're a fundamental part of what is being represented.",
      "I sometimes feel that these considerations are increasingly being thrown under the bus in favor of producing imagery that is visually interesting as quickly as possible. Now, there are absolutely situations where complete precision is unnecessary. Some deliverables are conceptual by nature, such as architectural competition work, and exist primarily to communicate atmosphere, mood or intent. In those cases, a certain degree of abstraction is perfectly acceptable, and a fast result is encouraged.",
      "But for most purposes that require an understanding of a project in detail, optical realism can become secondary. If the purpose of the image is to communicate how a project will actually exist in the world, how people will move through it, interact with it, inhabit it and experience it, then there are countless considerations that need to be accounted for. At that point, the deliverable cannot by nature simply be a pretty image. It needs to be a representation of something real.",
      "Not only do these considerations need to be captured correctly, they also need to be captured consistently across every deliverable. Whether we're talking about still imagery, film, virtual tours, real-time experiences, configurators or any other medium, the underlying logic of the project cannot change from one output to the next. If that logic starts breaking down between deliverables, then what is being represented stops being the project itself and starts becoming a collection of loosely related interpretations of it.",
      "When mutations are introduced into the process, regardless of whether those mutations are performed by humans, AI, or some combination of both, preserving that underlying logic becomes fully reliant on maintaining access to ground truth. Meaningful mutations can only happen without degradation when they're grounded in reality. Whether those mutations are performed through traditional workflows, through artificial intelligence, or through some combination of both, there ultimately needs to be a human in the loop with access to the raw materials of the project itself.",
      "Without access to the drawings, the structural elements that compose the project, the design intent, the technical realities, the conversations surrounding the project, the constraints it operates under, and the broader context in which it exists, you're no longer operating from ground truth and are instead working from approximations of approximations. In an iterative process such as this one, approximations add up. A good analogy I always circle back to is photocopying a photocopy multiple times. Repeat the process enough times, and the baseline loses grounding. The context becomes more and more vague to the point where any relation to reality is merely anecdotal.",
      "A lot of the current conversation around AI seems to miss this distinction. If all you want is something visually appealing, statistically pleasing, generic enough to become passable to a broad audience, and perhaps even semi-convincingly realistic at first glance, then many workflows can already be heavily automated. That much is becoming increasingly obvious and shouldn't be ignored. If, however, you want something that stands out among the crowd and does not sacrifice rigor, consistency, structural integrity or intent, then human involvement becomes imperative.",
      "The reason I think this matters is because almost everyone now has access to these tools. Yes, there are differences in budget, model access, computational resources and iteration speed, but broadly speaking, the ability to generate visually appealing imagery in and of itself is no longer particularly rare. As that becomes democratized, beauty alone becomes less of a differentiator.",
      "The differentiator increasingly becomes the ability to go beyond the latent space. If you're not searching beyond that latent space, if you're not adding your own human abstraction capabilities to the process, and if you're not introducing knowledge that does not already exist inside the model, then you'll only ever be producing statistically average outcomes. That's not a criticism of the technology. It's simply how the technology works.",
      "This is also why I think realism is often misunderstood. Realism is frequently reduced to the recreation of superficial appearances, when in reality a truly realistic result is not something that merely looks real at first glance but something that remains convincing under scrutiny because the decisions behind it are grounded in a deep knowledge of context and reality rather than merely resembling it. That reality is visual, certainly, but it is also physical, technical, human and contextual.",
      "A common argument is that these systems are continuously improving and will continue improving. I don't disagree. Their latent spaces will expand, they will become denser, and they will become capable of representing increasingly specific ideas and increasingly complex relationships. They will know more things, represent more relationships, and produce increasingly convincing results, but they will still only know what has been encoded into them.",
      "Projects, clients, regulations, technologies and cultural contexts are all continuously changing. The context surrounding a project is never static, which means that understanding a project requires continuous engagement with a reality that exists beyond the model itself. A model can represent knowledge about reality, but it does not participate in reality itself.",
      "For that reason, I don't think the role of the human disappears as these tools improve. If anything, the value shifts increasingly towards judgment, intentionality, interpretation, abstraction and understanding. The tools become more capable, but the importance of the person directing them does not diminish at the same rate, because the thing being represented is never just an image. It's a project, a set of constraints, a set of intentions, and ultimately a piece of reality that exists independently of whatever representation is produced.",
      "Realism is therefore not simply about recreating reality. It's about understanding reality well enough to represent it faithfully. If the objective is to produce work that holds up under scrutiny, remains consistent across multiple deliverables, accurately communicates intent and stands apart from an increasingly large sea of statistically average outputs, then human involvement is not a limitation on the process. It's the primary source of value within it.",
    ],
  },
];

const personalPhotos = [
  { title: "Slow Evening Light", place: "Lisbon", date: "Spring 2026", image: media.about2, note: "A parking spot for pictures that earned a second look. Nothing over-explained, nothing pretending to be a campaign." },
  { title: "Rooms Between Errands", place: "Everyday Notes", date: "Winter 2026", image: media.about3, note: "Fragments from ordinary days: rooms, objects, streets, food, people, and the odd thing that refused to disappear." },
  { title: "Quiet Architecture", place: "Portugal", date: "Ongoing", image: media.journal2, note: "Photography gets to be looser here. Less portfolio armor, more paying attention." },
];

const personalSongs = [
  { title: "Miss Understood", artist: "Headache, Vegyn", date: "Shared recently", image: "https://image-cdn-fa.spotifycdn.com/image/ab67616d00001e02696d4405302ff4b95dad5c75", link: "https://open.spotify.com/track/7emOUo5DMccU8cg5X4uG79?si=54676106307a4a86", note: "This one keeps circling back because it sounds a little bruised in the right way. Not clean, not polite, better for it." },
  { title: "Another track", artist: "Another artist", date: "For later", image: "https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?q=80&w=900&auto=format&fit=crop", link: "https://open.spotify.com", note: "A listening log for whatever survives repeat plays. Cover, link, date, and the honest reason it stayed around." },
];

const personalNotes = [
  { title: "On making a smaller internet", date: "Draft", copy: "A place for thoughts that do not need to dress up as content. Notes, rambles, travel scraps, and the occasional useful complaint." },
  { title: "What I keep noticing", date: "Draft", copy: "Short reflections on images, music, work, taste, places, and the small things that keep tapping on the glass." },
  { title: "Field notes", date: "Draft", copy: "Loose entries before they become polished: sketches, lists, references, unfinished thinking, and ideas still covered in dust." },
];

const personalFeedPosts = [
  { id: "smaller-internet", type: "note", date: "Pinned thought", title: "On making a smaller internet", copy: "I am tired of every corner of the internet pretending to be a stage. This place is meant to feel smaller, rougher, and more mine." },
  { id: "slow-evening-light", type: "photos", date: "Spring 2026", title: "Slow evening light", images: [media.about2, media.journal2], copy: "No campaign. No grand idea. Just two loose images where the light did something good and I bothered to keep looking." },
  { id: "song-placeholder", type: "song", date: "Shared recently", title: "Miss Understood", artist: "Headache, Vegyn", image: "https://image-cdn-fa.spotifycdn.com/image/ab67616d00001e02696d4405302ff4b95dad5c75", link: "https://open.spotify.com/track/7emOUo5DMccU8cg5X4uG79?si=54676106307a4a86", copy: "This one keeps circling back because it sounds a little bruised in the right way. Not clean, not polite, better for it." },
  { id: "rooms-between-errands", type: "photos", date: "Winter 2026", title: "Rooms between errands", images: [media.about3], copy: "Ordinary corners from ordinary days. Rooms, streets, objects, and whatever was stubborn enough to keep my attention." },
  { id: "field-notes", type: "note", date: "Draft", title: "Field notes", copy: "Unfinished thoughts go here before they get dressed up. Half-built ideas, references, complaints, and small things that might become useful later." },
];

const contactProjectTypes = ["Stills", "Films", "Cinemagraphs", "Virtual Tours", "Project Development", "Immersive Experiences", "Web Experiences", "Shot Planning", "Interior Design", "Brand Identity"];

const contactResourceGroups = [
  {
    title: "Project Scope",
    items: [
      ["Deliverable Type", { bullets: ["Flat image", "Animation (specify duration)", "Cinemagraph", "Interactive (360\u00ba panorama, VR/AR, stereoscopic image)", "Scene/Project File", "Other"] }],
      ["Deliverable Amount", { bullets: ["From 1 to 5", "From 5 to 10", "From 10 to 20", "More than 20"] }],
      ["Deliverable Goal", { bullets: ["Encourage the purchase of a product", "Highlight the technical features of a product", "Convey emotions or tell a story"] }],
      ["Deliverable Resolution", "Specify output size and aspect ratios. Low resolution is 1280px-1920px wide for mobile web formats and small print. Medium resolution is 2000px-3500px wide for web formats. High resolution is 4000px-15000px wide for large format print."],
      ["Intended Use", { bullets: ["Commercial presentation to clients/investors", "Participation in a competition", "Development of a training course or learning resource", "Publication in magazines or books", "Production of marketing materials", "Use in exhibitions or trade shows", "Personal/private use", "Other"] }],
    ],
  },
  {
    title: "Workflow & Review",
    items: [
      ["Design Flexibility", { bullets: ["I would greatly appreciate your input", "I have a general idea of what I want, but your input would be very appreciated", "My vision is sufficiently clear, but I might ask for some input", "I have a detailed design. I strictly need you to bring it to life"] }],
      ["References", { bullets: ["I have reference imagery and documentation that I'll send over to convey my vision", "I'll describe in detail the main features of the desired deliverables (e.g., colors, weather, lighting, landscape, overall styling, camera shot, and/or any other relevant aspects) to convey my vision"] }],
      ["Sharing Permissions", { bullets: ["I allow it", "I allow it, once I've shared project details on my media platforms", "I wish exclusivity over the images"] }],
    ],
  },
  {
    title: "REVIEW STAGES",
    items: [
      ["Major Review", { intro: "At this stage, clients can request significant changes. These involve substantial modifications that greatly alter the project's direction or core elements. Such changes typically require considerable time and resources to implement. Examples include:", bullets: ["Dramatic alterations to the lighting setup (e.g., change the lighting from daylight to evening/night with artificial lights)", "Significant changes to the shot composition (e.g., move the camera to achieve a completely different angle, aspect ratio, or lens)", "Modifications to the main 3D model body (e.g., updates to the room layout, overall measurements, room connections, or window positions)", "Major shifts in the overall style of materials, furniture, and decoration (e.g., transition from a Nordic interior style to a Neo-classic interior style)"] }],
      ["Mid-Term Review", { intro: "At this stage, requests for major changes are no longer accepted; only minor changes can be requested. These involve modifications that require less time to implement. Examples include:", bullets: ["Changing or removing one element (e.g., altering the type of sofa, lamp material, or plant size/type)", "Changing one or two materials (e.g., replace the wooden floor with a concrete floor or convert the fabric sofa to a leather sofa)", "Adding, changing, or removing decorative elements (e.g., plants, table decorations, books, frames, or pottery)", "Adjusting the positions of main furniture items, which may require rearranging connected decorations"] }],
      ["Final Review", { intro: "At this stage, clients may request minor fine-tuning of details. These are minor adjustments that can be completed in minimal time. Examples include:", bullets: ["Modifications to the color, saturation, or contrast of existing elements, usually handled through post-production adjustments without reopening the scene or re-rendering the image", "Removal or alteration of small decorative elements, usually handled by re-rendering a small part of the image and overlaying it onto the previous image"] }],
      ["Additional Reviews", "Any added reviews can be arranged per request."],
    ],
  },
];

const revealViewport = { once: true, amount: 0.08 };
const revealTransition = { duration: 0.42, ease: "easeOut" };
const fontStack = '"Nimbus Sans Local", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
const filterControlBaseClass = "force-bold-ui work-filter-control cursor-pointer rounded-[0.7rem] border px-3 py-1.5 text-[10px] font-normal uppercase tracking-[0.055em] backdrop-blur-md transition-colors duration-[300ms]";
const boldUiTextStyle = { fontWeight: 700 };

function runDevAssertions() {
  if (typeof console === "undefined") return;
  console.assert(workItems.length >= 8, "Practice gallery should represent the authored project set");
  console.assert(workItems.some((item) => item.type === "video"), "Practice gallery should include animated thumbnails");
  console.assert(workItems.some((item) => item.type === "before"), "Practice gallery should include before/after thumbnails");
  console.assert(new Set(workItems.map((item) => item.id)).size === workItems.length, "Every work item id should be unique");
  console.assert(workItems.every((item) => item.tags.every((tag) => tagGroupByName[tag])), "Every work tag should belong to a defined tag group");
  console.assert(pickedProjectIds.every((id) => workItems.some((item) => item.id === id)), "Every picked project should exist");
  console.assert(servicesData.length === 8, "Services section should contain eight services");
  console.assert(contactProjectTypes.length === 10, "Contact project type buttons should contain ten options");
}
runDevAssertions();

function PlaceholderLogo({ className = "", logoClassName = "h-[34px] w-[56px]", showText = true }) {
  return <div className={`inline-flex items-center gap-2.5 ${className}`} aria-label="Afonso Goncalves logo"><svg aria-hidden="true" viewBox="0 0 84.48 55.82" className={`block shrink-0 fill-current ${logoClassName}`}><path d="M5.49 51.82c-.49 0-.93-.23-1.22-.63-.28-.4-.35-.9-.19-1.36L20.1 4.99c.21-.59.77-.99 1.4-.99h7.33c.63 0 1.19.4 1.4.99L46.1 49.83c.16.46.09.96-.19 1.36-.28.4-.73.63-1.22.63H5.49Z" /><path d="M28.46 6 43.96 49.82H6.22L21.86 6h6.6m.37-4H21.5c-1.47 0-2.79.93-3.29 2.32L2.21 49.16c-.81 2.27.87 4.66 3.29 4.66h39.2c2.41 0 4.09-2.38 3.29-4.65L32.11 4.32C31.62 2.93 30.3 2 28.82 2Z" /><path d="M55.66 51.82c-.63 0-1.19-.4-1.4-.99L38.39 5.99c-.16-.46-.09-.96.19-1.36.28-.4.73-.63 1.22-.63H79c.49 0 .93.23 1.22.63.28.4.35.9.19 1.36L64.4 50.83c-.21.59-.77.99-1.4.99h-7.33Z" /><path d="M78.27 6 62.63 49.82h-6.6L40.52 6h37.75m.72-4h-39.2c-2.41 0-4.09 2.38-3.29 4.65l15.87 44.84c.49 1.39 1.81 2.32 3.29 2.32h7.33c1.47 0 2.79-.93 3.29-2.32L82.28 6.66C83.09 4.39 81.41 2 78.99 2Z" /></svg>{showText && <span className="hidden font-serif text-xl font-normal normal-case tracking-[0.08em] sm:inline md:text-2xl">Afonso Gon&ccedil;alves</span>}</div>;
}

function Icons({ name, size = 16 }) {
  const common = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true };
  if (name === "close") return <svg {...common}><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>;
  if (name === "arrow") return <svg {...common}><path d="M7 17 17 7" /><path d="M8 7h9v9" /></svg>;
  if (name === "right") return <svg {...common}><path d="M5 12h14" /><path d="m13 6 6 6-6 6" /></svg>;
  if (name === "left") return <svg {...common}><path d="M19 12H5" /><path d="m11 6-6 6 6 6" /></svg>;
  if (name === "down") return <svg {...common}><path d="M12 5v14" /><path d="m6 13 6 6 6-6" /></svg>;
  if (name === "chevron") return <svg {...common} strokeWidth={2} className="block"><path d="m6 9 6 6 6-6" /></svg>;
  if (name === "compare") return <svg {...common}><path d="M8 7 3 12l5 5" /><path d="M3 12h18" /><path d="m16 7 5 5-5 5" /></svg>;
  if (name === "check") return <svg {...common}><path d="m5 12 4 4L19 6" /></svg>;
  if (name === "menu") return <svg {...common}><path d="M4 7h16" /><path d="M4 12h16" /><path d="M4 17h16" /></svg>;
  if (name === "mail") return <svg {...common}><rect x="3" y="5" width="18" height="14" rx="2" /><path d="m3 7 9 7 9-7" /></svg>;
  if (name === "instagram") return <svg {...common}><rect x="4" y="4" width="16" height="16" rx="4" /><circle cx="12" cy="12" r="3.2" /><path d="M17 7.2h.01" /></svg>;
  if (name === "youtube") return <svg {...common}><path d="M21 12s0-3.3-.42-4.72a2.6 2.6 0 0 0-1.84-1.84C17.32 5 12 5 12 5s-5.32 0-6.74.44a2.6 2.6 0 0 0-1.84 1.84C3 8.7 3 12 3 12s0 3.3.42 4.72a2.6 2.6 0 0 0 1.84 1.84C6.68 19 12 19 12 19s5.32 0 6.74-.44a2.6 2.6 0 0 0 1.84-1.84C21 15.3 21 12 21 12Z" /><path d="m10 9 5 3-5 3Z" /></svg>;
  if (name === "note") return <svg {...common}><path d="M6 3h9l3 3v15H6Z" /><path d="M15 3v4h4" /><path d="M9 11h6" /><path d="M9 15h5" /></svg>;
  if (name === "photo") return <svg {...common}><rect x="3" y="5" width="18" height="14" rx="2.5" /><circle cx="8.5" cy="10" r="1.4" /><path d="m6 17 4.5-4.5 3 3 2-2L19 17" /></svg>;
  if (name === "music") return <svg {...common}><path d="M9 18V6l10-2v12" /><circle cx="6.5" cy="18" r="2.5" /><circle cx="16.5" cy="16" r="2.5" /></svg>;
  if (name === "moon") return <svg {...common}><path d="M21 12.8A8.5 8.5 0 1 1 11.2 3a6.6 6.6 0 0 0 9.8 9.8Z" /></svg>;
  return <svg {...common}><circle cx="12" cy="12" r="4" /><path d="M12 2v2" /><path d="M12 20v2" /><path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" /><path d="M2 12h2" /><path d="M20 12h2" /><path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" /></svg>;
}

function Reveal({ children, className = "", delay = 0 }) {
  return <motion.div initial={{ opacity: 0, y: 18 }} whileInView={{ opacity: 1, y: 0 }} viewport={revealViewport} transition={{ ...revealTransition, delay }} className={className}>{children}</motion.div>;
}

function LineText({ as: Tag = "p", children, className = "", delay = 0 }) {
  const text = typeof children === "string" || typeof children === "number" ? String(children) : "";
  const containerRef = useRef(null);
  const wordRefs = useRef([]);
  const [visible, setVisible] = useState(false);
  const words = text.trim().split(/\s+/).filter(Boolean);
  useEffect(() => {
    const container = containerRef.current;
    if (!container) return undefined;
    const updateLines = () => {
      const offsets = [];
      wordRefs.current.forEach((word) => {
        if (!word) return;
        const top = Math.round(word.offsetTop);
        if (!offsets.includes(top)) offsets.push(top);
        word.style.setProperty("--line-delay", `${delay * 1000 + Math.max(0, offsets.indexOf(top)) * 115}ms`);
      });
    };
    updateLines();
    const resizeObserver = new ResizeObserver(updateLines);
    resizeObserver.observe(container);
    return () => resizeObserver.disconnect();
  }, [delay, text]);
  useEffect(() => {
    const container = containerRef.current;
    if (!container) return undefined;
    const observer = new IntersectionObserver(([entry]) => {
      if (!entry.isIntersecting) return;
      setVisible(true);
      observer.disconnect();
    }, { rootMargin: "0px 0px -8% 0px", threshold: 0.12 });
    observer.observe(container);
    return () => observer.disconnect();
  }, []);
  if (!text) return <Tag className={className}>{children}</Tag>;
  return <Tag ref={containerRef} className={`line-text-reveal ${visible ? "is-visible" : ""} ${className}`}>{words.map((word, index) => <React.Fragment key={`${word}-${index}`}><span ref={(node) => { wordRefs.current[index] = node; }} className="line-text-word"><span className="line-text-word-inner">{word}</span></span>{index < words.length - 1 && " "}</React.Fragment>)}</Tag>;
}

function LiquidGlassDefs() {
  return <style>{`
@font-face{font-family:"Nimbus Sans Local";src:url("/assets/other/NimbusSanL-Reg.otf") format("opentype");font-weight:400;font-style:normal;font-display:swap;}
@font-face{font-family:"Nimbus Sans Local";src:url("/assets/other/NimbusSanL-Bol.otf") format("opentype");font-weight:700;font-style:normal;font-display:swap;}
@font-face{font-family:"Nimbus Sans Local";src:url("/assets/other/NimbusSanL-RegIta.otf") format("opentype");font-weight:400;font-style:italic;font-display:swap;}
@font-face{font-family:"Nimbus Sans Local";src:url("/assets/other/NimbusSanL-BolIta.otf") format("opentype");font-weight:700;font-style:italic;font-display:swap;}
:root{--theme-transition-duration:300ms;--sans-font:${fontStack};--serif-font:"Instrument Serif",Georgia,"Times New Roman",serif;--cursor-solid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='7.5' fill='%23f4f4f4'/%3E%3C/svg%3E") 12 12, auto;--cursor-hollow:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='10' fill='none' stroke='%23f4f4f4' stroke-width='2.4'/%3E%3C/svg%3E") 16 16, pointer;scrollbar-gutter:stable;}
html,body,#root{background-color:#efe9df;transition:background-color var(--theme-transition-duration) ease;}
html.dark,html.dark body,html.dark #root{background-color:#11100e;}
html{scrollbar-gutter:stable;scrollbar-width:none;}
body{overflow-x:hidden;}
body,button,input,textarea,select{font-family:var(--sans-font);}
button,.force-bold-ui,.work-filter-control,.tag-static,.tag-mobile-static,.project-card-cta{font-family:var(--sans-font)!important;font-weight:700!important;font-synthesis-weight:none;font-synthesis-style:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}
.font-serif{font-family:var(--serif-font)!important;font-weight:400;}
.font-serif[class*="tracking-[-0."]{letter-spacing:-0.012em!important;}
*{scrollbar-width:none;}
*::-webkit-scrollbar{display:none;width:0;height:0;}
.tag-glass{position:absolute;inset:0;border-radius:inherit;overflow:hidden;isolation:isolate;opacity:0;background:rgba(255,255,255,.18);-webkit-backdrop-filter:blur(14px) saturate(160%);backdrop-filter:blur(14px) saturate(160%);box-shadow:0 10px 30px rgba(0,0,0,.14);transition:opacity var(--theme-transition-duration) ease,background-color var(--theme-transition-duration) ease;}
.tag-glass.intent{background:rgba(0,0,0,.34);}
.group:hover .tag-glass,.tag-static .tag-glass{opacity:1;}
.tag-mobile-static .tag-glass{opacity:1;}
@media (min-width:768px){.tag-mobile-static .tag-glass{opacity:0;}.group:hover .tag-mobile-static .tag-glass{opacity:1;}}
@media (max-width:767px){.portfolio-thumbnail-tags{right:.8rem;top:.8rem;gap:.4rem;}.portfolio-thumbnail-tag{min-height:1.4rem!important;border-radius:.56rem!important;padding:.2rem .6rem!important;font-size:8px!important;letter-spacing:.044em!important;}}
.tag-glass::before{content:"";position:absolute;inset:0;z-index:2;border-radius:inherit;pointer-events:none;box-shadow:inset 1px 1px 0 rgba(255,255,255,.42),inset -1px -1px 0 rgba(255,255,255,.08),inset 0 0 14px rgba(255,255,255,.10);}
.tag-glass::after{content:"";position:absolute;inset:0;z-index:1;border-radius:inherit;pointer-events:none;background:rgba(255,255,255,.045);}
.project-page-shell > :not(.project-lightbox){transition:filter var(--theme-transition-duration) ease;}
.project-page-shell.is-lightbox-open > :not(.project-lightbox){filter:blur(10px) brightness(.72);}
.lightbox-page-shell > :not(.project-lightbox){transition:filter var(--theme-transition-duration) ease;}
.lightbox-page-shell.is-lightbox-open > :not(.project-lightbox){filter:blur(10px) brightness(.72);}
.project-lightbox .lightbox-loader-card{color:rgba(0,0,0,.72);}
.dark .project-lightbox .lightbox-loader-card,html.dark .project-lightbox .lightbox-loader-card{color:rgba(239,233,223,.94);}
.lightbox-loader-bar{position:relative;height:1px;width:min(14rem,48vw);overflow:hidden;border-radius:999px;background:rgba(0,0,0,.28);}
.lightbox-loader-bar::after{content:"";position:absolute;inset:0;width:42%;border-radius:inherit;background:currentColor;animation:lightbox-loader-slide 1.15s ease-in-out infinite;}
.dark .lightbox-loader-bar,html.dark .lightbox-loader-bar{background:rgba(239,233,223,.30);}
.lightbox-loader-card{position:relative;isolation:isolate;}
.lightbox-loader-card::before{content:"";position:absolute;inset:-3.5rem -5rem;border-radius:999px;background:radial-gradient(circle at 50% 50%,rgba(0,0,0,.16),rgba(0,0,0,.06) 38%,transparent 72%);filter:blur(24px);z-index:-1;}
.dark .lightbox-loader-card::before,html.dark .lightbox-loader-card::before{background:radial-gradient(circle at 50% 50%,rgba(239,233,223,.24),rgba(130,140,165,.15) 42%,transparent 72%);}
@keyframes lightbox-loader-slide{0%{transform:translateX(-120%);}50%{transform:translateX(92%);}100%{transform:translateX(260%);}}
html.lightbox-open .scroll-progress,html.form-open .scroll-progress{opacity:0!important;}
.inquiry-curtain,.inquiry-curtain *{overflow-anchor:none;}
.contact-curtain,.contact-curtain *{overflow-anchor:none;}
.contact-field{display:grid;height:72px;grid-template-rows:20px 44px;row-gap:.5rem;align-items:start;}
.contact-field-label{display:block;min-height:20px;line-height:20px;}
.work-media-zoom,.project-media-zoom{display:block;width:100%;transform:translate3d(0,0,0) scale(1);transition:transform var(--theme-transition-duration) ease;will-change:transform;}
.work-card:hover .work-media-zoom,.project-image-card:hover .project-media-zoom{transform:translate3d(0,0,0) scale(1.03);}
.journal-dropcap{float:left;padding:.08em .16em 0 0;font-family:var(--serif-font);font-size:5.4rem;line-height:.72;color:inherit;}
@media (max-width:767px){.journal-dropcap{font-size:4.35rem;line-height:.74;}}
.line-text-word{display:inline-block;overflow:hidden;padding:.14em .07em .24em;margin:-.14em -.07em -.24em;vertical-align:bottom;}
.line-text-word-inner{display:inline-block;opacity:0;transform:translateY(108%);transition-property:transform,opacity;transition-duration:.92s,.72s;transition-timing-function:cubic-bezier(.22,1,.36,1),ease;transition-delay:var(--line-delay,0ms);will-change:transform,opacity;}
.line-text-reveal.is-visible .line-text-word-inner{opacity:1;transform:translateY(0);}
html.theme-switching *,html.theme-switching *::before,html.theme-switching *::after{transition-delay:0ms!important;}
@media (prefers-reduced-motion:reduce){.line-text-word-inner{opacity:1;transform:none;transition:none;}}
.tone-transition{transition:color var(--theme-transition-duration) ease,background-color var(--theme-transition-duration) ease,border-color var(--theme-transition-duration) ease,box-shadow var(--theme-transition-duration) ease;}
.force-bold-ui,.force-bold-ui *,.work-filter-control,.work-filter-control *{font-family:var(--sans-font)!important;font-weight:700!important;text-shadow:.012em 0 0 currentColor;transition-property:color,background-color,border-color,box-shadow,opacity;transition-duration:var(--theme-transition-duration);transition-timing-function:ease;}
.tag-mobile-static span,.tag-static span,.project-card-cta,.force-bold-ui{font-family:var(--sans-font)!important;font-weight:700!important;text-shadow:.012em 0 0 currentColor;}
.nav-menu-item,.nav-menu-item *,.work-filter-control,.work-filter-control *,.work-filter-option,.work-filter-option *,.tag-mobile-static,.tag-mobile-static *,.tag-static,.tag-static *{font-weight:400!important;text-shadow:none!important;}
.tag-mobile-static,.tag-mobile-static *,.tag-static,.tag-static *{font-weight:400!important;text-shadow:none!important;}
.human-carousel-track{border-color:rgba(0,0,0,.38);}
.dark .human-carousel-track{border-color:rgba(239,233,223,.30);}
.human-carousel-panel{background-color:rgba(255,255,255,.075);border:1px solid rgba(255,255,255,.22);box-shadow:0 0 0 .75px rgba(255,255,255,.18),0 14px 30px rgba(0,0,0,.16);-webkit-backdrop-filter:blur(16px) saturate(155%);backdrop-filter:blur(16px) saturate(155%);transition:background-color var(--theme-transition-duration) ease,border-color var(--theme-transition-duration) ease,box-shadow var(--theme-transition-duration) ease;}
.dark .human-carousel-panel{background-color:rgba(0,0,0,.30);border-color:rgba(255,255,255,.18);box-shadow:0 0 0 .75px rgba(255,255,255,.16),0 14px 30px rgba(0,0,0,.22);}
.human-carousel-panel .contact-glow{inset:-52%;filter:blur(30px);opacity:.72;}
.dark .human-carousel-panel .contact-glow{opacity:.9;}
.human-carousel-panel::after{content:"";position:absolute;inset:0;z-index:1;border-radius:inherit;pointer-events:none;background:linear-gradient(135deg,rgba(255,255,255,.11),rgba(255,255,255,.018) 48%,rgba(255,255,255,.052));box-shadow:inset 1px 1px 0 rgba(255,255,255,.44),inset -1px -1px 0 rgba(255,255,255,.08),inset 5px 0 12px rgba(255,255,255,.045),inset -5px 0 12px rgba(0,0,0,.06);opacity:.78;}
.nav-icon-stable{color:inherit;line-height:0;transform:translateZ(0);backface-visibility:hidden;transition:color var(--theme-transition-duration) ease,opacity var(--theme-transition-duration) ease;}
.nav-contact-icon{color:#efe9df!important;}
.practice-masonry{display:grid;grid-auto-flow:row dense;grid-auto-rows:1px;grid-template-columns:minmax(0,1fr);gap:16px;}
.practice-masonry-item{min-width:0;}
.practice-card-reveal{opacity:0;transform:translate3d(0,22px,0);transition:opacity .42s cubic-bezier(.22,1,.36,1),transform .42s cubic-bezier(.22,1,.36,1);transition-delay:var(--practice-card-delay,0ms);will-change:opacity,transform;}
.practice-card-reveal[data-revealed="true"]{opacity:1;transform:translate3d(0,0,0);}
.practice-masonry .work-card img,.practice-masonry .work-card video{max-height:clamp(285px,32vw,560px);object-fit:cover;}
.viewport-hero{height:100vh;min-height:100svh;}
@supports (height:100dvh){.viewport-hero{height:100dvh;min-height:100dvh;}}
.portfolio-hero-image{opacity:0;transform:scale(1);transform-origin:center;will-change:opacity,transform;}
.portfolio-hero-image.is-zooming{animation:portfolio-hero-zoom 5.65s linear forwards;}
@keyframes portfolio-hero-zoom{from{transform:scale(1);}to{transform:scale(1.09);}}
.hero-text-gradient-bottom{position:absolute;left:-34vw;right:-34vw;bottom:-30vh;height:92vh;pointer-events:none;background:linear-gradient(to top,rgba(0,0,0,.60) 0%,rgba(0,0,0,.38) 28%,rgba(0,0,0,.16) 52%,rgba(0,0,0,.045) 70%,transparent 88%);filter:blur(22px);transform:translateZ(0);}
.hero-text-gradient-corner{position:absolute;left:-42vw;bottom:-38vh;width:140vw;height:104vh;pointer-events:none;background:radial-gradient(ellipse at bottom left,rgba(0,0,0,.58) 0%,rgba(0,0,0,.36) 30%,rgba(0,0,0,.17) 51%,rgba(0,0,0,.052) 68%,transparent 84%);filter:blur(24px);transform:translateZ(0);}
.about-expanded-panel{font-family:var(--serif-font);letter-spacing:-.01em;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.28) transparent;}
.dark .about-expanded-panel{scrollbar-color:rgba(239,233,223,.28) transparent;}
.about-expanded-panel::-webkit-scrollbar{width:6px;}
.about-expanded-panel::-webkit-scrollbar-track{background:transparent;}
.about-expanded-panel::-webkit-scrollbar-thumb{border-radius:999px;background:rgba(0,0,0,.24);}
.dark .about-expanded-panel::-webkit-scrollbar-thumb{background:rgba(239,233,223,.24);}
@media (min-width:768px){.practice-masonry{grid-template-columns:repeat(2,minmax(0,1fr));}}
@media (min-width:1024px){.practice-masonry{grid-template-columns:repeat(3,minmax(0,1fr));}}
.custom-cursor{position:fixed;left:0;top:0;z-index:9999;width:18px;height:18px;border-radius:999px;pointer-events:none;mix-blend-mode:difference;background:#fff;border:2px solid #fff;transform:translate(-50%,-50%);transition:width .18s ease,height .18s ease,background-color .18s ease,border-width .18s ease,opacity .18s ease;}
.custom-cursor.is-hovering{width:28px;height:28px;background:transparent;border-width:2px;}
.custom-cursor.is-over-glass{background:#fff;border-color:#fff;}
.custom-cursor.is-over-glass.is-hovering{background:transparent;border-color:#fff;}
@media (pointer:fine){*,*::before,*::after{cursor:none!important;}}
@media (pointer:coarse){.custom-cursor{display:none;}}
.liquid-glass-shell{position:relative;overflow:hidden;isolation:isolate;background:rgba(255,255,255,.055);-webkit-backdrop-filter:blur(14px) saturate(160%);backdrop-filter:blur(14px) saturate(160%);box-shadow:0 14px 45px rgba(0,0,0,.18);transition:background-color var(--theme-transition-duration) ease,box-shadow var(--theme-transition-duration) ease;}
.nav-glass{position:relative;overflow:hidden;isolation:isolate;contain:paint;background-color:rgba(255,255,255,.075);border:1px solid rgba(255,255,255,.22);box-shadow:0 0 0 .75px rgba(255,255,255,.18),0 14px 30px rgba(0,0,0,.16);transform:translateZ(0);-webkit-transform:translateZ(0);backface-visibility:hidden;will-change:transform,backdrop-filter;-webkit-backdrop-filter:blur(16px) saturate(155%);backdrop-filter:blur(16px) saturate(155%);transition:background-color var(--theme-transition-duration) ease,border-color var(--theme-transition-duration) ease,box-shadow var(--theme-transition-duration) ease;}
.nav-glass.nav-glass-dark{background-color:rgba(0,0,0,.30);border-color:rgba(255,255,255,.18);box-shadow:0 0 0 .75px rgba(255,255,255,.16),0 14px 30px rgba(0,0,0,.22);}
.nav-glass:hover{box-shadow:0 0 0 .75px rgba(255,255,255,.20),0 16px 34px rgba(0,0,0,.18);}
.nav-glass.nav-glass-dark:hover{box-shadow:0 0 0 .75px rgba(255,255,255,.18),0 16px 34px rgba(0,0,0,.24);}
.nav-contact-glow{overflow:hidden;}
.nav-contact-glow .contact-glow{position:absolute;inset:-46%;z-index:1;border-radius:999px;pointer-events:none;opacity:.94;background:conic-gradient(from 125deg,transparent 0deg,rgba(255,255,255,.04) 56deg,rgba(255,255,255,.36) 108deg,rgba(209,226,255,.16) 146deg,transparent 214deg,rgba(255,255,255,.16) 286deg,transparent 360deg);filter:blur(10px);mix-blend-mode:screen;animation:contact-glow-orbit 9s linear infinite;}
.nav-contact-glow .contact-glow::after{content:"";position:absolute;inset:20%;border-radius:999px;background:radial-gradient(circle at 35% 30%,rgba(255,255,255,.26),transparent 48%),radial-gradient(circle at 76% 68%,rgba(239,233,223,.18),transparent 42%);animation:contact-glow-drift 5.8s ease-in-out infinite alternate;}
.nav-contact-glow:not(.nav-glass-dark) .contact-glow{opacity:1;background:conic-gradient(from 125deg,transparent 0deg,rgba(255,255,255,.06) 56deg,rgba(255,255,255,.50) 108deg,rgba(209,226,255,.22) 146deg,transparent 214deg,rgba(255,255,255,.22) 286deg,transparent 360deg);}
.nav-contact-glow:not(.nav-glass-dark) .contact-glow::after{background:radial-gradient(circle at 35% 30%,rgba(255,255,255,.45),transparent 48%),radial-gradient(circle at 76% 68%,rgba(239,233,223,.31),transparent 42%);}
.human-carousel-panel.nav-contact-glow .contact-glow{inset:-52%;filter:blur(30px);opacity:.72;background:conic-gradient(from 210deg,transparent 0deg,rgba(255,255,255,.07) 54deg,rgba(255,255,255,.34) 106deg,rgba(220,231,255,.12) 148deg,transparent 222deg,rgba(255,255,255,.13) 292deg,transparent 360deg);mix-blend-mode:soft-light;animation:human-glow-orbit 63s linear infinite;}
.dark .human-carousel-panel.nav-contact-glow .contact-glow{opacity:.133;background:conic-gradient(from 210deg,transparent 0deg,rgba(255,255,255,.045) 54deg,rgba(255,255,255,.28) 106deg,rgba(220,231,255,.16) 148deg,transparent 222deg,rgba(255,255,255,.11) 292deg,transparent 360deg);mix-blend-mode:screen;}
.human-carousel-panel.nav-contact-glow .contact-glow::after{inset:24%;background:radial-gradient(circle at 34% 30%,rgba(255,255,255,.24),transparent 50%),radial-gradient(circle at 78% 66%,rgba(239,233,223,.16),transparent 48%);animation:human-glow-drift 24s ease-in-out infinite alternate;}
.dark .human-carousel-panel.nav-contact-glow .contact-glow::after{background:radial-gradient(circle at 34% 30%,rgba(255,255,255,.15),transparent 50%),radial-gradient(circle at 78% 66%,rgba(239,233,223,.13),transparent 48%);}
.human-approach-trigger.nav-contact-glow .contact-glow{animation-duration:13.5s;}
.human-approach-trigger.nav-contact-glow .contact-glow::after{animation-duration:8.7s;}
.dark .human-approach-trigger.nav-contact-glow .contact-glow{opacity:.66;}
.human-value-modal{color:#efe9df!important;background:#0b0a09!important;}
.human-value-modal .human-carousel-panel{background-color:rgba(255,255,255,.055)!important;border-color:rgba(239,233,223,.16)!important;}
.human-value-modal :where(h2,h3,p,span,button){color:#efe9df!important;}
.human-value-modal .human-carousel-panel p{color:rgba(239,233,223,.74)!important;}
.human-value-modal .human-carousel-track{border-color:rgba(239,233,223,.32)!important;}
.human-value-modal .human-carousel-track span{background:#efe9df!important;}
.ecosystem-card-glow{position:absolute;inset:auto -30% -34% -30%;z-index:2;height:62%;border-radius:999px;pointer-events:none;opacity:.54;filter:blur(30px);background:transparent;transform:translate3d(0,18%,0) scale(.92);transition:opacity .48s ease,transform .62s cubic-bezier(.22,1,.36,1);}
.ecosystem-card-glow::before,.ecosystem-card-glow::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;transition:opacity .58s ease;}
.ecosystem-card-glow::before{opacity:1;background:radial-gradient(ellipse at 50% 50%,rgba(0,0,0,.54),rgba(0,0,0,.26) 42%,transparent 72%);}
.ecosystem-card-glow::after{opacity:0;background:radial-gradient(ellipse at 50% 50%,rgba(255,255,255,.52),rgba(255,255,255,.24) 42%,transparent 72%);}
.ecosystem-card:hover .ecosystem-card-glow{opacity:.88;transform:translate3d(0,4%,0) scale(1.08);}
.ecosystem-card:hover .ecosystem-card-glow::before{opacity:0;}
.ecosystem-card:hover .ecosystem-card-glow::after{opacity:1;}
.dark .ecosystem-card-glow{opacity:.44;}
.dark .ecosystem-card-glow::before{opacity:0;}
.dark .ecosystem-card-glow::after{opacity:1;}
.dark .ecosystem-card:hover .ecosystem-card-glow{opacity:.86;transform:translate3d(0,4%,0) scale(1.08);}
.dark .ecosystem-card:hover .ecosystem-card-glow::before{opacity:1;}
.dark .ecosystem-card:hover .ecosystem-card-glow::after{opacity:0;}
.ecosystem-card-image{transform:scale(1.01);transition:transform .72s cubic-bezier(.22,1,.36,1),filter .72s cubic-bezier(.22,1,.36,1),opacity .45s ease;}
.ecosystem-card:hover .ecosystem-card-image{transform:scale(1.07);filter:saturate(1.12) contrast(1.04);}
.ecosystem-index,.ecosystem-index *{transition-property:color,background-color,border-color,box-shadow,opacity;transition-duration:var(--theme-transition-duration);transition-timing-function:ease;}
.ecosystem-index .ecosystem-card{transition:transform .42s cubic-bezier(.22,1,.36,1),border-color var(--theme-transition-duration) ease,box-shadow var(--theme-transition-duration) ease,background-color var(--theme-transition-duration) ease;}
.ecosystem-index .ecosystem-card-glow{transition:opacity .48s ease,transform .62s cubic-bezier(.22,1,.36,1);}
.ecosystem-index .ecosystem-card-image{transition:transform .72s cubic-bezier(.22,1,.36,1),filter .72s cubic-bezier(.22,1,.36,1),opacity .45s ease;}
.ecosystem-index .ecosystem-card-details{transition:max-height .38s cubic-bezier(.22,1,.36,1),opacity .28s ease,padding-top .38s cubic-bezier(.22,1,.36,1);}
.ecosystem-card-arrow{transition:transform .36s cubic-bezier(.22,1,.36,1),background-color var(--theme-transition-duration) ease,color var(--theme-transition-duration) ease,box-shadow var(--theme-transition-duration) ease;}
.ecosystem-card:hover .ecosystem-card-arrow{transform:translateX(.25rem);}
.personal-media{position:relative;overflow:hidden;background:rgba(0,0,0,.035);transition:background-color var(--theme-transition-duration) ease;}
.dark .personal-media{background:rgba(255,255,255,.045);}
.personal-media img{display:block;height:100%;width:100%;transform:scale(1);transition:transform .95s cubic-bezier(.22,1,.36,1),filter .95s cubic-bezier(.22,1,.36,1),opacity .45s ease;}
.personal-media::after{content:"";position:absolute;inset:0;pointer-events:none;background:rgba(0,0,0,0);transition:background-color .75s cubic-bezier(.22,1,.36,1);}
.personal-media:hover img{transform:scale(1.035);}
.personal-media:hover::after{background:rgba(0,0,0,.12);}
.dark .personal-media:hover::after{background:rgba(255,255,255,.10);}
.personal-site,.personal-site section,.personal-site article,.personal-site div,.personal-site a,.personal-site p,.personal-site span,.personal-site h1,.personal-site h2,.personal-site h3,.personal-site button,.personal-site svg,.personal-site path{transition-property:color,background-color,border-color,box-shadow,fill,stroke!important;transition-duration:var(--theme-transition-duration)!important;transition-timing-function:ease!important;}
.personal-site .personal-media img{transition:transform .95s cubic-bezier(.22,1,.36,1),filter .95s cubic-bezier(.22,1,.36,1),opacity .45s ease!important;}
.personal-site .personal-media::after{transition:background-color .75s cubic-bezier(.22,1,.36,1)!important;}
.line-text-reveal{transition:color var(--theme-transition-duration) ease,background-color var(--theme-transition-duration) ease,border-color var(--theme-transition-duration) ease!important;}
.theme-sync .line-text-word{color:inherit!important;transition:none!important;}
.theme-sync .line-text-word-inner{color:inherit!important;transition-property:transform,opacity!important;transition-duration:.92s,.72s!important;transition-timing-function:cubic-bezier(.22,1,.36,1),ease!important;transition-delay:var(--line-delay,0ms)!important;}
@media (max-width:767px){.ecosystem-card-details{max-height:4rem!important;opacity:1!important;padding-top:.9rem!important;}.ecosystem-card .ecosystem-card-glow{opacity:.50;transform:translate3d(0,6%,0) scale(1.02);}.dark .ecosystem-card .ecosystem-card-glow{opacity:.40;}.ecosystem-card:hover{transform:translateY(-7px) scale(1.018);box-shadow:0 30px 82px rgba(0,0,0,.20);}.ecosystem-card:hover .ecosystem-card-glow{opacity:.88;transform:translate3d(0,0,0) scale(1.12);}.dark .ecosystem-card:hover .ecosystem-card-glow{opacity:.86;}.ecosystem-card:hover .ecosystem-card-image{transform:scale(1.055);filter:saturate(1.08) brightness(.88);}.dark .ecosystem-card:hover .ecosystem-card-image{filter:saturate(1.1) brightness(1.14);}.ecosystem-card:hover .ecosystem-card-arrow{transform:none;}}
@keyframes contact-glow-orbit{to{transform:rotate(360deg);}}
@keyframes contact-glow-drift{0%{transform:translate3d(-5%,-3%,0) scale(.92);}100%{transform:translate3d(6%,4%,0) scale(1.08);}}
@keyframes human-glow-orbit{to{transform:rotate(360deg);}}
@keyframes human-glow-drift{0%{transform:translate3d(-3%,-2%,0) scale(.96);}100%{transform:translate3d(4%,3%,0) scale(1.06);}}
@media (prefers-reduced-motion:reduce){.nav-contact-glow .contact-glow,.nav-contact-glow .contact-glow::after,.portfolio-hero-image.is-zooming{animation:none;}}
.nav-glass::before{content:"";position:absolute;inset:0;z-index:0;border-radius:inherit;pointer-events:none;background:rgba(255,255,255,.06);transform:translateZ(0);transition:background-color var(--theme-transition-duration) ease,opacity var(--theme-transition-duration) ease;}
.nav-glass.nav-glass-dark::before{background:rgba(0,0,0,.18);}
.nav-glass::after{content:"";position:absolute;inset:0;z-index:1;border-radius:inherit;pointer-events:none;background:linear-gradient(135deg,rgba(255,255,255,.11),rgba(255,255,255,.018) 48%,rgba(255,255,255,.052));box-shadow:inset 1px 1px 0 rgba(255,255,255,.44),inset -1px -1px 0 rgba(255,255,255,.08),inset 5px 0 12px rgba(255,255,255,.045),inset -5px 0 12px rgba(0,0,0,.06);opacity:.78;}
.nav-glass-content{position:relative;z-index:2;}
.nav-menu-item{transition:background-color .2s ease,opacity .2s ease;}
.nav-menu-item:hover{background:rgba(255,255,255,.10);}
.liquid-glass-shell.glass-tone-dark{background:rgba(0,0,0,.28);box-shadow:0 14px 45px rgba(0,0,0,.22);}
.liquid-glass-shell.glass-tone-dark::after{background:rgba(0,0,0,.10);}
.liquid-glass-shell.glass-tone-dark::before{box-shadow:inset 1px 1px 0 rgba(255,255,255,.28),inset -1px -1px 0 rgba(255,255,255,.06),inset 0 0 14px rgba(255,255,255,.08);}
.liquid-glass-shell::before{content:"";position:absolute;inset:0;z-index:2;border-radius:inherit;pointer-events:none;box-shadow:inset 1px 1px 0 rgba(255,255,255,.42),inset -1px -1px 0 rgba(255,255,255,.08),inset 0 0 14px rgba(255,255,255,.10);transition:box-shadow var(--theme-transition-duration) ease;}
.liquid-glass-shell::after{content:"";position:absolute;inset:0;z-index:1;border-radius:inherit;pointer-events:none;background:rgba(255,255,255,.045);transition:background-color var(--theme-transition-duration) ease,opacity var(--theme-transition-duration) ease;}
.liquid-glass-compact{-webkit-backdrop-filter:blur(12px) saturate(155%);backdrop-filter:blur(12px) saturate(155%);}
.liquid-glass-compact:hover{background:rgba(255,255,255,.12);box-shadow:0 18px 55px rgba(0,0,0,.28);}
.liquid-glass-compact.glass-tone-dark:hover{background:rgba(0,0,0,.36);}
.liquid-glass-compact:hover::before{box-shadow:inset 1px 1px 0 rgba(255,255,255,.62),inset -1px -1px 0 rgba(255,255,255,.12),inset 0 0 18px rgba(255,255,255,.20);}
.liquid-glass-compact:hover::after{background:rgba(255,255,255,.085);}
.liquid-glass-compact:active{background:rgba(255,255,255,.14);}
.site-load-veil{position:fixed;inset:0;z-index:9998;pointer-events:none;background:#efe9df;transition:opacity .5s cubic-bezier(.22,1,.36,1),visibility .5s cubic-bezier(.22,1,.36,1);}
.dark .site-load-veil{background:#11100e;}
.site-load-veil.is-hidden{opacity:0;visibility:hidden;}
html.theme-switching,html.theme-switching body,html.theme-switching #root,html.theme-switching body .theme-sync,html.theme-switching body .theme-sync :where(*:not(.custom-cursor):not(.line-text-word):not(.line-text-word-inner):not(img):not(video)),html.theme-switching body .theme-sync :where(*:not(.custom-cursor):not(.line-text-word):not(.line-text-word-inner):not(img):not(video))::before,html.theme-switching body .theme-sync :where(*:not(.custom-cursor):not(.line-text-word):not(.line-text-word-inner):not(img):not(video))::after{transition-property:color,background-color,border-color,box-shadow,text-decoration-color,fill,stroke!important;transition-duration:var(--theme-transition-duration)!important;transition-timing-function:ease!important;transition-delay:0ms!important;}
html.theme-switching body .theme-sync .line-text-reveal{transition-property:color,background-color,border-color!important;transition-duration:var(--theme-transition-duration)!important;transition-timing-function:ease!important;transition-delay:0ms!important;}
html.theme-switching body .theme-sync .line-text-word{color:inherit!important;transition:none!important;transition-delay:0ms!important;}
html.theme-switching body .theme-sync .line-text-word-inner{color:inherit!important;transition:none!important;transition-delay:0ms!important;}
html.theme-switching body .theme-sync svg,html.theme-switching body .theme-sync svg *{transition-property:color,fill,stroke!important;transition-duration:var(--theme-transition-duration)!important;transition-timing-function:ease!important;transition-delay:0ms!important;}
html.theme-switching body .theme-sync .contact-glow,html.theme-switching body .theme-sync .contact-glow::after{animation-play-state:paused!important;}
`}</style>;
}

function LiquidGlassRefraction() {
  return null;
}

function PageLoadVeil({ hidden }) {
  return <div className={`site-load-veil ${hidden ? "is-hidden" : ""}`} aria-hidden="true" />;
}

function preloadImages(sources = []) {
  const uniqueSources = [...new Set(sources.filter((src) => src && !/\.(mp4|webm|mov)(\?|#|$)/i.test(src)))];
  if (!uniqueSources.length) return Promise.resolve();
  return Promise.all(uniqueSources.map((src) => new Promise((resolve) => {
    const image = new Image();
    image.onload = resolve;
    image.onerror = resolve;
    image.src = src;
    image.decode?.().then(resolve).catch(() => {});
  }))).then(() => undefined);
}

function preloadImagesStrict(sources = []) {
  const uniqueSources = [...new Set(sources.filter((src) => src && !/\.(mp4|webm|mov)(\?|#|$)/i.test(src)))];
  if (!uniqueSources.length) return Promise.resolve();
  return Promise.all(uniqueSources.map((src) => new Promise((resolve, reject) => {
    const image = new Image();
    image.onload = async () => {
      try {
        await image.decode?.();
      } catch {
        // The browser may already have decoded cached images; a completed load is enough.
      }
      resolve(src);
    };
    image.onerror = reject;
    image.src = src;
  }))).then(() => undefined);
}

function LoadAwareImage({ eager = false, revealOnLoad = true, className = "", onLoad, ...props }) {
  const [loaded, setLoaded] = useState(false);
  const imageRef = useRef(null);
  const notifiedRef = useRef(false);
  useEffect(() => {
    notifiedRef.current = false;
    setLoaded(false);
    if (imageRef.current) delete imageRef.current.dataset.loaded;
  }, [props.src]);
  const markLoaded = async (event) => {
    const image = event?.currentTarget || imageRef.current;
    if (!image) return;
    try {
      await image.decode?.();
    } catch {
      // Decode can reject for cached or browser-managed images; load state is still usable.
    }
    if (notifiedRef.current) return;
    image.dataset.loaded = "true";
    setLoaded(true);
    notifiedRef.current = true;
    onLoad?.({ currentTarget: image, target: image });
  };
  useEffect(() => {
    const image = imageRef.current;
    let cancelled = false;
    let frame = 0;
    if (!image) return undefined;
    const markReady = () => {
      if (!cancelled) markLoaded({ currentTarget: image, target: image });
    };
    const waitForReady = () => {
      if (cancelled) return;
      if (image.complete && image.naturalWidth > 0) {
        markReady();
        return;
      }
      frame = requestAnimationFrame(waitForReady);
    };
    if (image.complete && image.naturalWidth > 0) {
      markReady();
      return () => {
        cancelled = true;
      };
    }
    frame = requestAnimationFrame(waitForReady);
    return () => {
      cancelled = true;
      cancelAnimationFrame(frame);
    };
  }, [props.src]);
  const loadClassName = revealOnLoad ? ` transition-opacity duration-[360ms] ${loaded ? "" : "opacity-0"}` : "";
  return <img ref={imageRef} {...props} loading={eager ? "eager" : "lazy"} decoding="async" onLoad={markLoaded} className={`${className}${loadClassName}`} />;
}

function getMediaPreloadSources(item) {
  if (!item) return [];
  if (typeof item === "string") return [item];
  return [item.src, item.poster].filter(Boolean);
}

function getWorkThumbnailPreloadSources(item) {
  if (!item) return [];
  if (item.type === "before") return [item.before, item.after].filter(Boolean);
  return [item.src, item.poster].filter(Boolean);
}

function getProjectPreloadSources(project) {
  if (!project) return [];
  const hero = project.hqSrc || (project.type === "video" ? project.poster : project.src);
  const gallerySources = (project.galleryHq || project.gallery || []).slice(0, 2).flatMap(getMediaPreloadSources);
  return [hero, ...gallerySources, project.projectComparison?.before, project.projectComparison?.after];
}

function getInitialPracticePreloadSources() {
  return pickedProjectIds
    .map((id) => workItems.find((item) => item.id === id))
    .filter(Boolean)
    .slice(0, 3)
    .flatMap(getWorkThumbnailPreloadSources);
}

function useHideOnScroll(pinned = false) {
  const [hidden, setHidden] = useState(false);
  const lastY = useRef(0);
  const pinnedRef = useRef(pinned);
  useEffect(() => {
    pinnedRef.current = pinned;
    if (pinned) {
      setHidden(false);
      lastY.current = window.scrollY;
    }
  }, [pinned]);
  useEffect(() => {
    const onScroll = () => {
      const y = window.scrollY;
      if (pinnedRef.current) {
        setHidden(false);
        lastY.current = y;
        return;
      }
      setHidden(y > lastY.current && y > 80);
      lastY.current = y;
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return hidden;
}

function ScrollProgressIndicator() {
  const [progress, setProgress] = useState(0);
  const [passedSections, setPassedSections] = useState({});
  const [milestonePositions, setMilestonePositions] = useState({});
  const milestones = [
    ["practice", "Practice"],
    ["about", "About"],
    ["services", "Services"],
    ["blog", "Journal"],
  ];
  useEffect(() => {
    const updateProgress = () => {
      const scrollable = Math.max(1, document.documentElement.scrollHeight - window.innerHeight);
      const currentTop = window.scrollY;
      const nextProgress = Math.min(1, Math.max(0, window.scrollY / scrollable));
      const sectionStarts = Object.fromEntries(milestones.map(([id]) => {
        const section = document.getElementById(id);
        if (!section) return [id, 0];
        const scrollMarginTop = parseFloat(window.getComputedStyle(section).scrollMarginTop) || 0;
        const sectionTop = section.getBoundingClientRect().top + window.scrollY;
        return [id, Math.max(0, sectionTop - scrollMarginTop)];
      }));
      const nextPositions = Object.fromEntries(milestones.map(([id]) => [id, Math.min(1, Math.max(0, (sectionStarts[id] ?? 0) / scrollable))]));
      setProgress(nextProgress);
      setMilestonePositions(nextPositions);
      setPassedSections(Object.fromEntries(milestones.map(([id]) => [id, currentTop + 1 >= (sectionStarts[id] ?? 0)])));
    };
    updateProgress();
    window.addEventListener("scroll", updateProgress, { passive: true });
    window.addEventListener("resize", updateProgress);
    const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(updateProgress);
    observer?.observe(document.body);
    return () => {
      window.removeEventListener("scroll", updateProgress);
      window.removeEventListener("resize", updateProgress);
      observer?.disconnect();
    };
  }, []);
  return <div className="scroll-progress pointer-events-none fixed bottom-24 right-2 top-24 z-[90] w-px bg-black/10 mix-blend-difference transition-opacity duration-[300ms] dark:bg-[#efe9df]/10 md:right-3" aria-hidden="true"><motion.div className="absolute left-0 top-0 w-px origin-top bg-[#efe9df]" style={{ height: `${Math.max(8, progress * 100)}%` }} transition={{ duration: 0.18, ease: "easeOut" }} />{milestones.map(([id]) => <span key={id} className={`absolute left-1/2 h-2 w-2 -translate-x-1/2 rounded-full border border-[#efe9df] transition-all duration-[300ms] ${passedSections[id] ? "bg-[#efe9df] scale-100" : "bg-transparent scale-75"}`} style={{ top: `${(milestonePositions[id] ?? 0) * 100}%` }} />)}</div>;
}

function CustomCursor() {
  const [state, setState] = useState({ x: -40, y: -40, hovering: false, visible: false, overGlass: false });
  useEffect(() => {
    const move = (event) => {
      const target = event.target;
      const hovering = Boolean(target?.closest?.('a,button,[role="button"],[role="slider"],input,textarea,select,label'));
      const overGlass = Boolean(target?.closest?.(".liquid-glass-shell,.nav-glass,.nav-tone-ignore"));
      const useNativeCursor = false;
      setState({ x: event.clientX, y: event.clientY, hovering, visible: !useNativeCursor, overGlass });
    };
    const leave = () => setState((current) => ({ ...current, visible: false }));
    window.addEventListener("mousemove", move);
    window.addEventListener("pointermove", move);
    window.addEventListener("mouseleave", leave);
    return () => {
      window.removeEventListener("mousemove", move);
      window.removeEventListener("pointermove", move);
      window.removeEventListener("mouseleave", leave);
    };
  }, []);
  return <div className={`custom-cursor ${state.hovering ? "is-hovering" : ""} ${state.overGlass ? "is-over-glass" : ""}`} style={{ transform: `translate(${state.x}px, ${state.y}px) translate(-50%, -50%)`, opacity: state.visible ? 1 : 0 }} />;
}

function SmoothLink({ href, children, className = "", onClick }) {
  const handleClick = (event) => {
    event.preventDefault();
    if (onClick) {
      onClick();
      return;
    }
    document.querySelector(href)?.scrollIntoView({ behavior: "smooth", block: "start" });
  };
  return <a href={href} onClick={handleClick} className={className}>{children}</a>;
}

function NavbarLink({ href, children, className = "", onNavigate }) {
  return <SmoothLink href={href} className={`transition-opacity hover:opacity-70 ${className}`} onClick={() => {
    onNavigate?.();
    const target = document.querySelector(href);
    if (!target) return;
    const top = target.getBoundingClientRect().top + window.scrollY;
    window.scrollTo({ top, behavior: "smooth" });
  }}>{children}</SmoothLink>;
}

function ThemeToggle({ isDark, onToggle }) {
  const glassToneClass = !isDark ? "nav-glass-dark" : "";
  return <button type="button" onClick={onToggle} className={`group nav-glass relative grid h-14 w-14 shrink-0 cursor-pointer place-items-center rounded-[1.35rem] ${glassToneClass}`} aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}><AnimatePresence mode="wait" initial={false}><motion.span key={isDark ? "sun" : "moon"} initial={{ opacity: 0, rotate: -35, scale: 0.72 }} animate={{ opacity: 1, rotate: 0, scale: 1 }} exit={{ opacity: 0, rotate: 35, scale: 0.72 }} transition={{ duration: 0.3, ease: "easeOut" }} className="tone-transition nav-glass-content pointer-events-none text-[#efe9df] drop-shadow-[0_1px_8px_rgba(0,0,0,.42)]"><Icons name={isDark ? "sun" : "moon"} /></motion.span></AnimatePresence></button>;
}

function ContactToggle({ isDark, onContact }) {
  const glassToneClass = !isDark ? "nav-glass-dark" : "";
  return <button type="button" onClick={onContact} className={`group nav-glass nav-contact-glow relative grid h-14 w-14 shrink-0 cursor-pointer place-items-center rounded-[1.35rem] ${glassToneClass}`} aria-label="Contact"><span className="contact-glow" aria-hidden="true" /><span className="tone-transition nav-glass-content nav-icon-stable nav-contact-icon drop-shadow-[0_1px_8px_rgba(0,0,0,.42)]"><Icons name="mail" size={17} /></span></button>;
}

function GlassButton({ children, onClick, className = "", ariaLabel, forceDarkTint = false }) {
  const glassToneClass = forceDarkTint ? "nav-glass-dark" : "";
  return <button type="button" onClick={onClick} aria-label={ariaLabel} className={`group nav-glass relative inline-grid h-14 cursor-pointer place-items-center rounded-[1.35rem] ${glassToneClass} ${className}`}><span className="tone-transition nav-glass-content inline-flex translate-y-px items-center justify-center text-center leading-none text-[#efe9df] drop-shadow-[0_1px_8px_rgba(0,0,0,.42)]">{children}</span></button>;
}

const visualizationNavItems = [
  { href: "#practice", label: "Practice" },
  { href: "#about", label: "About" },
  { href: "#services", label: "Services" },
  { href: "#blog", label: "Journal" },
];

const portfolioNavItems = [
  { href: "#portfolio-work", label: "Portfolio" },
  { href: "#about", label: "About" },
];

const personalNavItems = [
  { href: "#feed", label: "Feed" },
];

const navChromeTransition = { duration: 0.34, ease: [0.22, 1, 0.36, 1] };
const navChromeMotion = (hidden) => ({ y: hidden ? -110 : 0 });
const navChromeStyle = (hidden) => ({ willChange: "transform", pointerEvents: hidden ? "none" : "auto" });

function Navbar({ onContact, onResume, onLogoClick, forceVisible = false, forceHidden = false, onForceVisibleConsumed, isDark, onToggleTheme, navItems = visualizationNavItems, showContact = true, showResume = true, showMenu = true, showSocial = true }) {
  const [pinned, setPinned] = useState(false);
  const [menuOpen, setMenuOpen] = useState(false);
  const scrollHidden = useHideOnScroll(pinned || forceVisible);
  const hidden = forceHidden || scrollHidden;
  const navGlassToneClass = !isDark ? "nav-glass-dark" : "";
  const navTextClass = "text-[#efe9df] drop-shadow-[0_1px_8px_rgba(0,0,0,.42)]";
  const logoOnlyNav = !showMenu && !showSocial;
  useEffect(() => {
    if (!forceVisible) return undefined;
    const release = () => { if (window.scrollY > 80) onForceVisibleConsumed?.(); };
    window.addEventListener("scroll", release, { passive: true });
    window.addEventListener("wheel", release, { once: true, passive: true });
    window.addEventListener("touchmove", release, { once: true, passive: true });
    return () => {
      window.removeEventListener("scroll", release);
      window.removeEventListener("wheel", release);
      window.removeEventListener("touchmove", release);
    };
  }, [forceVisible, onForceVisibleConsumed]);
  const keepVisibleForNavigation = () => setPinned(true);
  useEffect(() => {
    if (!pinned) return undefined;
    const pinnedStartY = window.scrollY;
    const releaseOnUserScroll = () => setPinned(false);
    const releaseOnScroll = () => {
      if (Math.abs(window.scrollY - pinnedStartY) > 4) setPinned(false);
    };
    window.addEventListener("scroll", releaseOnScroll, { passive: true });
    window.addEventListener("wheel", releaseOnUserScroll, { once: true, passive: true });
    window.addEventListener("touchmove", releaseOnUserScroll, { once: true, passive: true });
    return () => {
      window.removeEventListener("scroll", releaseOnScroll);
      window.removeEventListener("wheel", releaseOnUserScroll);
      window.removeEventListener("touchmove", releaseOnUserScroll);
    };
  }, [pinned]);
  useEffect(() => {
    if (!menuOpen) return undefined;
    document.documentElement.classList.add("nav-menu-open");
    const closeOnScroll = () => setMenuOpen(false);
    window.addEventListener("scroll", closeOnScroll, { once: true, passive: true });
    window.addEventListener("wheel", closeOnScroll, { once: true, passive: true });
    window.addEventListener("touchmove", closeOnScroll, { once: true, passive: true });
    return () => {
      document.documentElement.classList.remove("nav-menu-open");
      window.removeEventListener("scroll", closeOnScroll);
      window.removeEventListener("wheel", closeOnScroll);
      window.removeEventListener("touchmove", closeOnScroll);
    };
  }, [menuOpen]);
  const handleMobileNavigate = () => {
    setMenuOpen(false);
    keepVisibleForNavigation();
  };
  const handleLogoClick = () => {
    setMenuOpen(false);
    if (onLogoClick) {
      onLogoClick();
      return;
    }
    document.querySelector("#home")?.scrollIntoView({ behavior: "smooth", block: "start" });
  };
  const navMotion = navChromeMotion(hidden);
  const navStyle = navChromeStyle(hidden);
  return <header className="nav-tone-ignore pointer-events-none fixed inset-x-0 top-4 z-40 px-4 text-[#efe9df] max-[640px]:px-2 md:px-6">
    <motion.div initial={false} animate={navMotion} transition={navChromeTransition} style={navStyle} className={`relative mx-auto ${logoOnlyNav ? "w-[5.4rem]" : "w-[20.4rem] max-[640px]:mx-0 max-[640px]:w-[8.25rem] max-[340px]:w-[7.5rem]"}`}>
      <nav className={`group nav-glass pointer-events-auto relative h-14 w-full rounded-[1.35rem] px-5 max-[640px]:px-0 ${navGlassToneClass}`}>
        <div className={`tone-transition nav-glass-content relative h-full text-[10px] font-medium uppercase tracking-[0.14em] sm:tracking-[0.18em] md:text-xs md:tracking-[0.22em] ${navTextClass}`}>
          {showMenu && <button type="button" onClick={() => setMenuOpen((current) => !current)} className="tone-transition absolute left-[1.6rem] top-1/2 grid h-9 w-9 -translate-y-1/2 cursor-pointer place-items-center rounded-[0.85rem] hover:bg-current/10 hover:opacity-100 max-[640px]:left-3" aria-label={menuOpen ? "Close menu" : "Open menu"} aria-expanded={menuOpen}>
            <span className="nav-icon-stable"><Icons name={menuOpen ? "close" : "menu"} size={17} /></span>
          </button>}
          <button type="button" className={`pointer-events-auto absolute left-1/2 top-0 flex h-full -translate-x-1/2 cursor-pointer items-center justify-center ${logoOnlyNav ? "" : "max-[640px]:hidden"}`} onClick={handleLogoClick} aria-label="Open index">
            <PlaceholderLogo className="h-full items-center justify-center" logoClassName="h-[30px] w-[49px]" showText={false} />
          </button>
          {showSocial && <div className="absolute left-[calc(50%+3rem)] top-1/2 flex -translate-y-1/2 items-center justify-start gap-2 max-[640px]:left-14 max-[640px]:gap-0">
            <a href={socialLinks.instagram} target="_blank" rel="noreferrer" className="tone-transition grid h-9 w-9 cursor-pointer place-items-center rounded-[0.85rem] hover:bg-current/10 hover:opacity-100 max-[640px]:w-7" aria-label="Instagram"><span className="nav-icon-stable"><Icons name="instagram" size={15} /></span></a>
            <a href={socialLinks.youtube} target="_blank" rel="noreferrer" className="tone-transition grid h-9 w-9 cursor-pointer place-items-center rounded-[0.85rem] hover:bg-current/10 hover:opacity-100 max-[640px]:w-7" aria-label="YouTube"><span className="nav-icon-stable"><Icons name="youtube" size={16} /></span></a>
          </div>}
        </div>
      </nav>
      {showMenu && <motion.div initial={false} animate={menuOpen ? { opacity: 1, y: 0 } : { opacity: 0, y: 8 }} transition={{ duration: 0.22, ease: "easeOut" }} aria-hidden={!menuOpen} style={{ willChange: "opacity, transform", pointerEvents: menuOpen ? "auto" : "none" }} className={`nav-glass !absolute left-0 top-[calc(100%+0.6rem)] w-[min(15rem,calc(100vw-2rem))] rounded-[1.2rem] p-2 ${navGlassToneClass}`}>
          <div className={`tone-transition nav-glass-content grid gap-1 text-[11px] font-normal uppercase tracking-[0.14em] ${navTextClass}`}>
            {navItems.map((item) => <NavbarLink key={item.href} href={item.href} className="nav-menu-item rounded-[0.85rem] px-4 py-3" onNavigate={handleMobileNavigate}>{item.label}</NavbarLink>)}
            {showResume && <button type="button" onClick={() => { setMenuOpen(false); onResume?.(); }} className="nav-menu-item rounded-[0.85rem] px-4 py-3 text-left uppercase">R&eacute;sume</button>}
          </div>
      </motion.div>}
    </motion.div>
    {!logoOnlyNav && <div className="pointer-events-none fixed left-1/2 top-4 hidden -translate-x-1/2 max-[640px]:block">
      <motion.button initial={false} animate={navMotion} transition={navChromeTransition} style={navStyle} type="button" className="pointer-events-auto grid h-14 w-[clamp(4.5rem,20.4vw,5.4rem)] cursor-pointer place-items-center max-[340px]:w-[3.75rem]" onClick={handleLogoClick} aria-label="Open index">
        <span className={`nav-glass grid h-14 w-[clamp(4.5rem,20.4vw,5.4rem)] place-items-center rounded-[1.35rem] max-[340px]:w-[3.75rem] ${navGlassToneClass}`}><span className="nav-glass-content"><PlaceholderLogo className="h-full items-center justify-center" logoClassName="h-[30px] w-[49px]" showText={false} /></span></span>
      </motion.button>
    </div>}
    {showContact && <motion.div initial={false} animate={navMotion} transition={navChromeTransition} style={navStyle} className="pointer-events-auto absolute right-[5.25rem] top-0 max-[640px]:right-[4.5rem] md:left-[calc(50%+10.95rem)] md:right-auto"><ContactToggle isDark={isDark} onContact={() => { setMenuOpen(false); onContact?.(); }} /></motion.div>}
    <motion.div initial={false} animate={navMotion} transition={navChromeTransition} style={navStyle} className="pointer-events-auto absolute right-4 top-0 shrink-0 max-[640px]:right-2 md:right-6"><ThemeToggle isDark={isDark} onToggle={onToggleTheme} /></motion.div>
  </header>;
}

function Hero() {
  const [isMobile, setIsMobile] = useState(false);
  const [useHighQualityVideo, setUseHighQualityVideo] = useState(false);
  const [scrollBlur, setScrollBlur] = useState(0);
  const videoRef = useRef(null);
  const playbackTimeRef = useRef(0);
  const playbackProgressRef = useRef(0);
  const playbackWatchdogRef = useRef({ lastTime: 0, stalledTicks: 0 });
  const rememberPlaybackTime = () => {
    const video = videoRef.current;
    if (!video || !Number.isFinite(video.currentTime)) return;
    playbackTimeRef.current = video.currentTime;
    if (Number.isFinite(video.duration) && video.duration > 0) playbackProgressRef.current = video.currentTime / video.duration;
  };
  useEffect(() => {
    const query = window.matchMedia("(max-width: 767px)");
    const update = () => {
      rememberPlaybackTime();
      setIsMobile(query.matches);
    };
    update();
    query.addEventListener?.("change", update);
    return () => query.removeEventListener?.("change", update);
  }, []);
  useEffect(() => {
    const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
    const updateQuality = () => {
      rememberPlaybackTime();
      if (!connection) {
        setUseHighQualityVideo(window.innerWidth >= 1280);
        return;
      }
      const effectiveType = connection.effectiveType || "";
      const downlink = Number(connection.downlink || 0);
      const isFastConnection = effectiveType === "4g" && downlink >= 8;
      setUseHighQualityVideo(!connection.saveData && isFastConnection);
    };
    updateQuality();
    connection?.addEventListener?.("change", updateQuality);
    window.addEventListener("resize", updateQuality);
    return () => {
      connection?.removeEventListener?.("change", updateQuality);
      window.removeEventListener("resize", updateQuality);
    };
  }, []);
  useEffect(() => {
    let frame = 0;
    const updateBlur = () => {
      frame = 0;
      const progress = Math.min(1, Math.max(0, window.scrollY / Math.max(1, window.innerHeight * 0.72)));
      setScrollBlur(progress * 10);
    };
    const onScroll = () => {
      if (frame) return;
      frame = requestAnimationFrame(updateBlur);
    };
    updateBlur();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", updateBlur);
    return () => {
      if (frame) cancelAnimationFrame(frame);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", updateBlur);
    };
  }, []);
  useEffect(() => {
    const timer = window.setInterval(rememberPlaybackTime, 180);
    return () => window.clearInterval(timer);
  }, []);
  useEffect(() => {
    const keepShowreelAlive = () => {
      const video = videoRef.current;
      if (!video) return;
      video.loop = true;
      if (video.ended) {
        video.currentTime = 0;
        video.play?.().catch?.(() => {});
        return;
      }
      if (video.paused) {
        video.play?.().catch?.(() => {});
        return;
      }
      const current = Number.isFinite(video.currentTime) ? video.currentTime : 0;
      const watch = playbackWatchdogRef.current;
      if (Math.abs(current - watch.lastTime) < 0.035) watch.stalledTicks += 1;
      else watch.stalledTicks = 0;
      watch.lastTime = current;
      if (watch.stalledTicks < 4 || video.readyState < 2) return;
      const bufferedEnd = video.buffered.length ? video.buffered.end(video.buffered.length - 1) : 0;
      if (bufferedEnd - current > 0.5) {
        video.currentTime = Math.min((Number.isFinite(video.duration) ? video.duration : current + 0.05) - 0.05, current + 0.05);
        video.play?.().catch?.(() => {});
        watch.stalledTicks = 0;
      }
    };
    const timer = window.setInterval(keepShowreelAlive, 1000);
    return () => window.clearInterval(timer);
  }, []);
  const activeReel = isMobile ? (useHighQualityVideo ? media.reelMobileHq : media.reelMobile) : (useHighQualityVideo ? media.reelHq : media.reel);
  const storePlaybackTime = (event) => {
    const video = event.currentTarget;
    if (!Number.isFinite(video.currentTime)) return;
    playbackTimeRef.current = video.currentTime;
    if (Number.isFinite(video.duration) && video.duration > 0) playbackProgressRef.current = video.currentTime / video.duration;
  };
  const blurMaskStyle = {
    WebkitMaskImage: `url(${media.reelBlurMask})`,
    maskImage: `url(${media.reelBlurMask})`,
    WebkitMaskMode: "luminance",
    maskMode: "luminance",
    WebkitMaskSize: "cover",
    maskSize: "cover",
    WebkitMaskPosition: "center",
    maskPosition: "center",
    WebkitMaskRepeat: "no-repeat",
    maskRepeat: "no-repeat",
    WebkitBackdropFilter: "blur(10px)",
    backdropFilter: "blur(10px)",
  };
  return <motion.section id="home" data-nav-tone="light" initial={{ opacity: 0.92, scale: 1.012, filter: "blur(4px)" }} animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }} transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1] }} className="viewport-hero relative scroll-mt-0 overflow-hidden bg-black"><div className="absolute inset-0 will-change-transform" style={{ filter: `blur(${scrollBlur}px)`, transform: `scale(${1 + scrollBlur * 0.003})` }}><picture><source media="(max-width: 767px)" srcSet={media.heroMobile} /><img src={media.hero} alt="" className="absolute inset-0 h-full w-full object-cover" /></picture><video ref={videoRef} key={activeReel} src={activeReel} className="absolute inset-0 h-full w-full object-cover" autoPlay muted loop playsInline preload="auto" poster={isMobile ? media.heroMobile : media.hero} onTimeUpdate={storePlaybackTime} onSeeking={storePlaybackTime} onPause={storePlaybackTime} />{!isMobile && <div aria-hidden="true" className="pointer-events-none absolute inset-0 hidden bg-black/[0.01] md:block" style={blurMaskStyle} />}</div><div className="absolute inset-0 bg-black/20" /><div className="hero-text-gradient-bottom" /><div className="hero-text-gradient-corner" /><div className="absolute bottom-10 left-5 right-5 flex items-end justify-between text-[#efe9df] md:left-6 md:right-6"><h1 className="max-w-[96vw] font-serif text-[14.6vw] leading-[0.82] tracking-[-0.06em] md:max-w-6xl md:text-[8vw]"><LineText as="span" className="block whitespace-nowrap">Visual stories</LineText><LineText as="span" className="block whitespace-nowrap" delay={0.04}>for architecture.</LineText></h1></div></motion.section>;
}

function CtaLink({ href, children }) {
  return <SmoothLink href={href} className="mt-8 inline-flex items-center gap-2 border-b border-black/20 pb-1 text-sm font-semibold uppercase tracking-[0.08em] text-black/55 transition hover:border-black/45 hover:text-black/80 dark:border-[#efe9df]/25 dark:text-[#efe9df]/65 dark:hover:border-[#efe9df]/55 dark:hover:text-[#efe9df]">{children} <Icons name="arrow" size={14} /></SmoothLink>;
}

function Intro() {
  const [expanded, setExpanded] = useState(false);
  const [humanModalOpen, setHumanModalOpen] = useState(false);
  const aboutTextClass = "font-serif text-4xl leading-tight tracking-[-0.03em] md:text-6xl";
  return (
    <section id="about" data-nav-tone="dark" className="scroll-mt-24 border-t border-black/10 bg-[#efe9df] px-5 py-10 transition-colors duration-[300ms] dark:border-[#efe9df]/10 dark:bg-[#11100e] md:px-[7.75vw] md:py-12">
      <div className="grid gap-10 lg:grid-cols-[minmax(0,1.08fr)_minmax(18rem,.52fr)] lg:items-start">
        <div className="max-w-5xl">
          <Reveal>
            <LineText className="text-xs font-medium uppercase tracking-[0.24em]">About</LineText>
          </Reveal>
          <Reveal className="mt-8 w-full md:mt-10" delay={0.04}>
            <LineText as="h2" className={aboutTextClass}>I&apos;m a Portuguese creative dedicated to creating impactful visuals that don&apos;t simply turn heads, but also hold up under tight scrutiny.</LineText>
            <AnimatePresence initial={false}>
              {expanded && (
                <motion.div
                  initial={{ height: 0, opacity: 0 }}
                  animate={{ height: "auto", opacity: 1 }}
                  exit={{ height: 0, opacity: 0 }}
                  transition={{ height: { duration: 0.42, ease: [0.76, 0, 0.24, 1] }, opacity: { duration: 0.22, ease: "easeOut" } }}
                  className="overflow-hidden"
                >
                  <div className="about-expanded-panel mt-8 max-h-[26rem] overflow-y-auto rounded-[1.15rem] border border-black/10 bg-black/[0.035] p-5 text-base leading-8 text-black/68 transition-colors duration-[300ms] dark:border-[#efe9df]/10 dark:bg-[#efe9df]/[0.055] dark:text-[#efe9df]/70 md:max-h-[30rem] md:p-6 md:text-lg md:leading-9">
                    <div className="space-y-5">
                      <p>In a world where content is king and art is but an afterthought, I find joy in swimming against the current and creating purposeful media that showcases clients&apos; projects in their best light.</p>
                      <p>Driven by a deep respect for architectural rigor, I bring a thorough and disciplined understanding of the many creative processes within architectural visualization. I approach each project with technical clarity and measured artistic control, making sure every deliverable serves the design, communicates intent accurately, and presents the project at its highest possible standard.</p>
                      <p>My work spans a wide spectrum of clients and project typologies, from architectural practices and real-estate developers to design studios and private clients. Experience includes competitions, conceptual studies, planning submissions, marketing imagery, and fully developed visual narratives, with workflows and levels of detail adapted to each project&apos;s scope and intent.</p>
                    </div>
                  </div>
                </motion.div>
              )}
            </AnimatePresence>
            <button type="button" onClick={() => setExpanded((current) => !current)} className="mt-8 cursor-pointer border-b border-black/25 pb-1 text-xs font-semibold uppercase tracking-[0.12em] text-black/55 transition hover:border-black/55 hover:text-black dark:border-[#efe9df]/25 dark:text-[#efe9df]/60 dark:hover:border-[#efe9df]/60 dark:hover:text-[#efe9df]" aria-expanded={expanded}>{expanded ? "Read less" : "Read more"}</button>
          </Reveal>
        </div>
        <Reveal className="grid gap-4 lg:pt-12" delay={0.08}>
          <div className="relative overflow-hidden bg-black">
            <LoadAwareImage src={media.portrait} alt="Portrait of Afonso Goncalves" className="aspect-[4/5] w-full object-cover opacity-90" />
            <div className="absolute inset-0 bg-gradient-to-t from-black/30 via-transparent to-transparent" />
          </div>
          <button type="button" onClick={() => setHumanModalOpen(true)} className="human-approach-trigger group nav-glass nav-glass-dark nav-contact-glow relative min-h-16 cursor-pointer overflow-hidden rounded-[1.25rem] px-5 py-4 text-left text-[#efe9df]">
            <span className="contact-glow" aria-hidden="true" />
            <span className="nav-glass-content relative z-10 flex items-center justify-between gap-5">
              <span>
                <span className="block text-[10px] font-semibold uppercase tracking-[0.18em] text-[#efe9df]/55">Human judgment</span>
                <span className="mt-1 block font-serif text-2xl leading-none tracking-[-0.04em]">Learn more about the human first approach.</span>
              </span>
              <span className="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-[#efe9df]/85 text-black transition-transform duration-300 group-hover:translate-x-1"><Icons name="right" size={16} /></span>
            </span>
          </button>
        </Reveal>
      </div>
      <HumanValueModal open={humanModalOpen} onClose={() => setHumanModalOpen(false)} />
    </section>
  );
}

function HumanValueCarousel({ embedded = false } = {}) {
  const [activeIndex, setActiveIndex] = useState(0);
  const [paused, setPaused] = useState(false);
  const [slideProgress, setSlideProgress] = useState(0);
  const [wrapMode, setWrapMode] = useState(null);
  const progressRef = useRef(0);
  const wrapTimersRef = useRef([]);
  const slideDuration = 5200;
  useEffect(() => {
    progressRef.current = slideProgress;
  }, [slideProgress]);
  useEffect(() => {
    if (paused || wrapMode) return undefined;
    let frame = 0;
    const start = performance.now() - progressRef.current * slideDuration;
    const tick = (now) => {
      const nextProgress = Math.min(1, (now - start) / slideDuration);
      if (nextProgress >= 1) {
        if (activeIndex === humanValueCards.length - 1) startWrapTransition("forward", 0);
        else goToCard(activeIndex + 1);
        return;
      }
      progressRef.current = nextProgress;
      setSlideProgress(nextProgress);
      frame = requestAnimationFrame(tick);
    };
    frame = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(frame);
  }, [paused, wrapMode, activeIndex]);
  useEffect(() => () => wrapTimersRef.current.forEach((timer) => window.clearTimeout(timer)), []);
  const startWrapTransition = (direction, nextIndex) => {
    wrapTimersRef.current.forEach((timer) => window.clearTimeout(timer));
    setWrapMode(direction);
    const switchTimer = window.setTimeout(() => {
      progressRef.current = 0;
      setSlideProgress(0);
      setActiveIndex(nextIndex);
    }, 300);
    const revealTimer = window.setTimeout(() => setWrapMode(null), 520);
    wrapTimersRef.current = [switchTimer, revealTimer];
  };
  const goToCard = (index) => {
    const nextIndex = (index + humanValueCards.length) % humanValueCards.length;
    if (activeIndex === humanValueCards.length - 1 && nextIndex === 0) {
      startWrapTransition("forward", nextIndex);
      return;
    }
    if (activeIndex === 0 && nextIndex === humanValueCards.length - 1) {
      startWrapTransition("backward", nextIndex);
      return;
    }
    progressRef.current = 0;
    setSlideProgress(0);
    setActiveIndex(nextIndex);
  };
  const goToNextCard = () => goToCard(activeIndex + 1);
  const goToPreviousCard = () => goToCard(activeIndex - 1);
  const activeCard = humanValueCards[activeIndex];
  const navButtonClass = "absolute top-1/2 z-40 hidden h-12 w-12 -translate-y-1/2 cursor-pointer place-items-center text-black/62 transition hover:text-black dark:text-[#efe9df]/70 dark:hover:text-[#efe9df] md:grid";
  const pauseCarousel = () => { setSlideProgress(progressRef.current); setPaused(true); };
  const carousel = <Reveal className="grid w-full gap-8 md:grid-cols-12 md:items-stretch lg:block"><div className="md:col-span-4 lg:mb-8"><LineText className="mb-6 text-xs font-medium uppercase tracking-[0.24em] text-black/45 dark:text-[#efe9df]/45">Human judgment</LineText><LineText as="h2" className="font-serif text-4xl leading-[0.95] tracking-[-0.05em] md:text-6xl">A human first approach.</LineText></div><div className="md:col-span-8"><div onMouseEnter={pauseCarousel} onMouseLeave={() => setPaused(false)} onFocus={pauseCarousel} onBlur={() => setPaused(false)} className="human-carousel-panel nav-contact-glow relative min-h-[560px] overflow-hidden rounded-[1.25rem] p-5 sm:min-h-[520px] md:min-h-[470px] md:p-8 lg:min-h-[520px]"><span className="contact-glow" aria-hidden="true" /><button type="button" onClick={goToPreviousCard} className="absolute bottom-12 left-0 top-0 z-10 w-1/2 cursor-pointer md:hidden" aria-label="Previous carousel card" /><button type="button" onClick={goToNextCard} className="absolute bottom-12 right-0 top-0 z-10 w-1/2 cursor-pointer md:hidden" aria-label="Next carousel card" /><button type="button" onClick={goToPreviousCard} className={`${navButtonClass} left-4 md:left-5`} aria-label="Previous carousel card"><Icons name="left" size={20} /></button><button type="button" onClick={goToNextCard} className={`${navButtonClass} right-4 md:right-5`} aria-label="Next carousel card"><Icons name="right" size={20} /></button><AnimatePresence mode="wait"><motion.article key={activeCard.title} initial={{ opacity: 0, y: 18 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -18 }} transition={{ duration: 0.42, ease: [0.22, 1, 0.36, 1] }} className="relative z-20 flex min-h-[500px] flex-col justify-center gap-8 px-4 pb-12 pt-4 sm:min-h-[460px] sm:px-8 md:min-h-[390px] md:px-14 lg:min-h-[440px] lg:px-10 xl:px-14"><div><p className="mb-4 text-xs font-semibold uppercase tracking-[0.2em] text-black/45 dark:text-[#efe9df]/45">{activeCard.kicker}</p><h3 className="max-w-3xl font-serif text-[2.35rem] leading-[0.92] tracking-[-0.05em] sm:text-4xl md:text-[3.05rem] lg:text-[3.1rem] xl:text-[3.4rem]">{activeCard.title}</h3></div><p className="max-w-3xl text-sm leading-6 text-black/66 dark:text-[#efe9df]/66 sm:text-[15px] md:text-base md:leading-7">{activeCard.copy}</p></motion.article></AnimatePresence><div className={`absolute bottom-5 left-5 right-5 z-30 grid grid-cols-7 gap-1.5 transition-opacity duration-300 md:bottom-8 md:left-8 md:right-8 ${wrapMode ? "opacity-0" : "opacity-100"}`} aria-label="Carousel slides">{humanValueCards.map((card, index) => { const fill = index < activeIndex ? 100 : index === activeIndex ? slideProgress * 100 : 0; return <button key={card.title} type="button" onClick={() => goToCard(index)} className="group h-3 cursor-pointer rounded-full py-1" aria-label={`Show ${card.title}`} aria-pressed={activeIndex === index}><span className="human-carousel-track block h-1.5 overflow-hidden rounded-full border bg-transparent"><span className="block h-full rounded-full bg-black dark:bg-[#efe9df]" style={{ width: `${fill}%` }} /></span></button>; })}</div></div></div></Reveal>;
  if (embedded) return <div className="lg:pt-0">{carousel}</div>;
  return <section data-nav-tone="dark" className="border-b border-black/10 bg-[#efe9df] px-5 pb-10 pt-6 transition-colors duration-[300ms] dark:border-[#efe9df]/10 dark:bg-[#11100e] md:px-[7.75vw] md:pb-12 md:pt-8">{carousel}</section>;
}

function HumanValueModal({ open, onClose }) {
  return <AnimatePresence>{open && <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.24, ease: "easeOut" }} className="fixed inset-0 z-[115] overflow-y-auto bg-black/55 px-5 py-6 text-[#efe9df] backdrop-blur-md md:px-6" role="dialog" aria-modal="true" aria-label="Human first approach"><button type="button" className="absolute inset-0 h-full w-full cursor-pointer" onClick={onClose} aria-label="Close human first approach backdrop" /><motion.div initial={{ opacity: 0, scale: 0.96, y: 18 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.96, y: 18 }} transition={{ duration: 0.32, ease: [0.22, 1, 0.36, 1] }} className="human-value-modal relative mx-auto my-10 max-w-6xl overflow-hidden rounded-[1.35rem] border border-white/18 bg-[#0b0a09] p-5 text-[#efe9df] shadow-[0_30px_90px_rgba(0,0,0,.35)] md:p-8"><button type="button" onClick={onClose} className="group nav-glass nav-glass-dark !absolute right-4 top-4 z-[120] grid h-14 w-14 cursor-pointer place-items-center rounded-[1.35rem]" aria-label="Close human first approach"><span className="nav-glass-content text-[#efe9df] drop-shadow-[0_1px_8px_rgba(0,0,0,.42)]"><Icons name="close" size={18} /></span></button><div className="pt-16 md:pt-10"><HumanValueCarousel embedded /></div></motion.div></motion.div>}</AnimatePresence>;
}

function BeforeAfter({ before, after, zoomOnHover = false, preserveAspect = false, eager = false, revealOnLoad = true, onReady }) {
  const [value, setValue] = useState(50);
  const wrapperRef = useRef(null);
  const draggingRef = useRef(false);
  const updateValueFromPointer = (clientX) => {
    const rect = wrapperRef.current?.getBoundingClientRect();
    if (!rect) return;
    setValue(Math.max(0, Math.min(100, ((clientX - rect.left) / rect.width) * 100)));
  };
  const startDrag = (event) => {
    event.preventDefault();
    event.stopPropagation();
    draggingRef.current = true;
    updateValueFromPointer(event.clientX);
    event.currentTarget.setPointerCapture?.(event.pointerId);
  };
  const continueDrag = (event) => {
    if (!draggingRef.current) return;
    event.preventDefault();
    updateValueFromPointer(event.clientX);
  };
  const stopDrag = (event) => {
    if (!draggingRef.current) return;
    event?.preventDefault?.();
    event?.stopPropagation?.();
    draggingRef.current = false;
  };
  const firstImageClass = preserveAspect ? "block h-auto w-full" : "absolute inset-0 h-full w-full object-cover";
  const overlayImageClass = preserveAspect ? "absolute inset-0 h-full w-full object-contain" : "absolute inset-0 h-full w-full object-cover";
  const frameClass = preserveAspect ? "relative block w-full" : "relative h-full min-h-[280px] md:min-h-[420px]";
  return <div ref={wrapperRef} className={`pointer-events-auto select-none overflow-hidden bg-black md:pointer-events-none ${frameClass} ${zoomOnHover ? "work-media-zoom" : ""}`} aria-label="Before and after comparison"><LoadAwareImage src={before} alt="Before version" draggable="false" eager={eager} revealOnLoad={revealOnLoad} onLoad={onReady} className={firstImageClass} /><LoadAwareImage src={after} alt="After version" draggable="false" eager={eager} revealOnLoad={revealOnLoad} className={`${overlayImageClass} opacity-100 transition-opacity duration-[300ms] md:opacity-0 md:group-hover:opacity-100`} style={{ clipPath: `inset(0 ${100 - value}% 0 0)` }} /><div className="absolute top-0 h-full w-px bg-[#efe9df]/90 opacity-100 transition-opacity duration-[300ms] md:opacity-0 md:group-hover:opacity-100" style={{ left: `${value}%` }} /><div className="absolute top-1/2 flex h-10 w-10 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-[#efe9df]/70 bg-black/25 text-[#efe9df] opacity-100 backdrop-blur-sm transition-opacity duration-[300ms] md:opacity-0 md:group-hover:opacity-100" style={{ left: `${value}%` }}><Icons name="compare" size={17} /></div><div className="absolute top-0 z-40 h-full w-14 -translate-x-1/2 touch-none cursor-ew-resize md:pointer-events-none md:group-hover:pointer-events-auto" style={{ left: `${value}%` }} role="slider" aria-label="Before and after slider handle" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(value)} tabIndex={0} onPointerDown={startDrag} onPointerMove={continueDrag} onPointerUp={stopDrag} onPointerCancel={stopDrag} onClick={(event) => { event.preventDefault(); event.stopPropagation(); }} onKeyDown={(event) => { if (event.key === "ArrowLeft") setValue((current) => Math.max(0, current - 5)); if (event.key === "ArrowRight") setValue((current) => Math.min(100, current + 5)); }} /></div>;
}

function ProjectComparisonImage({ before, after, title, onExpand }) {
  const [value, setValue] = useState(50);
  const wrapperRef = useRef(null);
  const draggingRef = useRef(false);
  const updateValueFromPointer = (clientX) => {
    const rect = wrapperRef.current?.getBoundingClientRect();
    if (!rect) return;
    setValue(Math.max(0, Math.min(100, ((clientX - rect.left) / rect.width) * 100)));
  };
  const startDrag = (event) => {
    event.preventDefault();
    event.stopPropagation();
    draggingRef.current = true;
    updateValueFromPointer(event.clientX);
    event.currentTarget.setPointerCapture?.(event.pointerId);
  };
  const continueDrag = (event) => {
    if (!draggingRef.current) return;
    event.preventDefault();
    updateValueFromPointer(event.clientX);
  };
  const stopDrag = (event) => {
    if (!draggingRef.current) return;
    event?.preventDefault?.();
    event?.stopPropagation?.();
    draggingRef.current = false;
  };
  return <div ref={wrapperRef} className="relative block w-full select-none overflow-hidden bg-black text-left" aria-label={`${title} comparison image`}><LoadAwareImage src={before} alt={`${title} comparison frame A`} draggable="false" eager className="block h-auto w-full" /><LoadAwareImage src={after} alt={`${title} comparison frame B`} draggable="false" eager className="absolute inset-0 h-full w-full object-contain" style={{ clipPath: `inset(0 ${100 - value}% 0 0)` }} /><button type="button" onClick={() => onExpand(after)} className="absolute inset-y-0 left-0 z-20 cursor-pointer" style={{ width: `${value}%` }} aria-label={`Expand ${title} comparison frame B`} /><button type="button" onClick={() => onExpand(before)} className="absolute inset-y-0 right-0 z-20 cursor-pointer" style={{ width: `${100 - value}%` }} aria-label={`Expand ${title} comparison frame A`} /><span className="pointer-events-none absolute top-0 z-30 h-full w-px bg-[#efe9df]/90 shadow-[0_0_18px_rgba(0,0,0,.42)]" style={{ left: `${value}%` }} /><span className="pointer-events-none absolute top-1/2 z-40 flex h-11 w-11 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-[#efe9df]/75 bg-black/30 text-[#efe9df] shadow-[0_10px_32px_rgba(0,0,0,.28)] backdrop-blur-sm" style={{ left: `${value}%` }}><Icons name="compare" size={17} /></span><span className="absolute top-0 z-50 h-full w-14 -translate-x-1/2 touch-none cursor-ew-resize" style={{ left: `${value}%` }} role="slider" aria-label="Project comparison slider handle" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(value)} tabIndex={0} onPointerDown={startDrag} onPointerMove={continueDrag} onPointerUp={stopDrag} onPointerCancel={stopDrag} onClick={(event) => { event.preventDefault(); event.stopPropagation(); }} onKeyDown={(event) => { if (event.key === "ArrowLeft") setValue((current) => Math.max(0, current - 5)); if (event.key === "ArrowRight") setValue((current) => Math.min(100, current + 5)); }} /></div>;
}

function normalizeProjectGalleryMedia(item, index, title) {
  if (typeof item === "string") return { id: `${title}-image-${index}`, type: "image", src: item };
  return { id: item.id || `${title}-${item.type || "image"}-${index}`, type: item.type || "image", ...item };
}

function ProjectGalleryImage({ media, title, index, onExpand }) {
  return <button type="button" onClick={() => onExpand(media.src)} className="group project-image-card mx-auto block w-fit max-w-full cursor-pointer overflow-hidden bg-black text-left"><span className="project-media-zoom"><LoadAwareImage src={media.src} alt={`${title} frame ${index + 1}`} eager={index < 2} className="block h-auto max-h-[min(82vh,920px)] w-auto max-w-full" /></span></button>;
}

function ProjectGalleryVideo({ media, title, index }) {
  return <div className="mx-auto w-fit max-w-full overflow-hidden bg-black"><video src={media.src} poster={media.poster} aria-label={`${title} film ${index + 1}`} className="block h-auto max-h-[min(82vh,920px)] w-auto max-w-full" autoPlay muted loop playsInline controls preload="metadata" /></div>;
}

function ProjectGalleryMedia({ media, title, index, onExpand }) {
  if (media.type === "video") return <ProjectGalleryVideo media={media} title={title} index={index} />;
  return <ProjectGalleryImage media={media} title={title} index={index} onExpand={onExpand} />;
}

function ProjectGalleryFlow({ media, project, featuredMedia, onExpand }) {
  return <div className={media.length === 1 ? "grid gap-4" : "columns-1 gap-4 md:columns-2"}>{media.map((item, index) => <Reveal key={item.id} className={media.length === 1 ? "" : "mb-4 break-inside-avoid"}><ProjectGalleryMedia media={item} title={project.title} index={index + (featuredMedia ? 1 : 0)} onExpand={onExpand} /></Reveal>)}</div>;
}

function ProjectGallery({ project, heroImage, showProjectVideo, secondaryMedia, onExpand }) {
  const comparisonSources = [project.projectComparison?.before, project.projectComparison?.after].filter(Boolean);
  const heroMedia = showProjectVideo
    ? { id: `${project.id}-hero-video`, type: "video", src: project.src, poster: project.poster }
    : { id: `${project.id}-hero-image`, type: "image", src: heroImage };
  const normalizedMedia = [heroMedia, ...secondaryMedia]
    .filter((item) => item?.src && !comparisonSources.includes(item.src));
  const featuredMedia = project.projectComparison ? null : normalizedMedia[0];
  const flowingMedia = project.projectComparison ? normalizedMedia : normalizedMedia.slice(1);
  return <section data-nav-tone="light" className="px-5 pb-28 md:px-6"><div className="grid w-full gap-4">{project.projectComparison && <Reveal><ProjectComparisonImage before={project.projectComparison.before} after={project.projectComparison.after} title={project.title} onExpand={onExpand} /></Reveal>}{featuredMedia && <Reveal><ProjectGalleryMedia media={featuredMedia} title={project.title} index={0} onExpand={onExpand} /></Reveal>}{flowingMedia.length > 0 && <ProjectGalleryFlow media={flowingMedia} project={project} featuredMedia={featuredMedia} onExpand={onExpand} />}</div></section>;
}

function ProjectTags({ tags, size = "default" }) {
  const isCompact = size === "compact";
  const isPortfolio = size === "portfolio";
  const wrapperClass = isCompact ? "tag-mobile-static pointer-events-none absolute right-3 top-3 z-30 flex flex-wrap justify-end gap-1.5" : `tag-mobile-static pointer-events-none absolute right-4 top-4 z-30 flex flex-wrap justify-end gap-2 ${isPortfolio ? "portfolio-thumbnail-tags" : ""}`;
  const pillClass = isCompact ? "force-bold-ui relative inline-flex min-h-[1.05rem] items-center overflow-hidden rounded-[0.42rem] px-[0.42rem] py-[0.16rem] text-[6px] font-bold uppercase leading-none tracking-[0.04em]" : `force-bold-ui relative inline-flex min-h-7 items-center overflow-hidden rounded-[0.7rem] px-3 py-1 text-[10px] font-bold uppercase leading-none tracking-[0.055em] ${isPortfolio ? "portfolio-thumbnail-tag" : ""}`;
  return <div className={wrapperClass}>{tags.map((tag) => { const isIntent = tagGroupByName[tag] === "intent"; return <span key={tag} style={boldUiTextStyle} className={pillClass}><span className={`tag-glass ${isIntent ? "intent" : "sector"}`} /><span className={`relative z-10 translate-y-px opacity-100 transition-opacity duration-[300ms] md:opacity-0 md:group-hover:opacity-100 ${isIntent ? "text-[#efe9df]/95" : "text-[#efe9df]"}`}>{tag}</span></span>; })}</div>;
}

function WorkCard({ item, index, span, onSelectProject, tagSize = "default" }) {
  const [mediaReady, setMediaReady] = useState(false);
  const [videoReady, setVideoReady] = useState(false);
  const [inView, setInView] = useState(false);
  const [revealed, setRevealed] = useState(false);
  const cardRef = useRef(null);
  const handleSelect = () => onSelectProject(item);
  useEffect(() => {
    setMediaReady(false);
    setVideoReady(false);
    setInView(false);
    setRevealed(false);
  }, [item.id, item.src, item.before, item.after]);
  useEffect(() => {
    if (inView) return undefined;
    let frame = 0;
    const checkInView = () => {
      const rect = cardRef.current?.getBoundingClientRect();
      if (!rect) return;
      const threshold = Math.min(rect.height * 0.18, 96);
      if (rect.top < window.innerHeight - threshold && rect.bottom > threshold) setInView(true);
    };
    const scheduleCheck = () => {
      window.cancelAnimationFrame(frame);
      frame = window.requestAnimationFrame(checkInView);
    };
    scheduleCheck();
    window.addEventListener("scroll", scheduleCheck, { passive: true });
    window.addEventListener("resize", scheduleCheck);
    return () => {
      window.cancelAnimationFrame(frame);
      window.removeEventListener("scroll", scheduleCheck);
      window.removeEventListener("resize", scheduleCheck);
    };
  }, [inView]);
  useEffect(() => {
    let frame = 0;
    let cancelled = false;
    const checkReady = () => {
      if (cancelled) return;
      if (item.type === "video") {
        const video = cardRef.current?.querySelector("video");
        if (video?.readyState >= 2) {
          setVideoReady(true);
          setMediaReady(true);
          return;
        }
      } else {
        const images = [...(cardRef.current?.querySelectorAll("img") || [])];
        if (images.length > 0 && images.every((image) => image.dataset.loaded === "true")) {
          setMediaReady(true);
          return;
        }
      }
      frame = window.requestAnimationFrame(checkReady);
    };
    frame = window.requestAnimationFrame(checkReady);
    return () => {
      cancelled = true;
      window.cancelAnimationFrame(frame);
    };
  }, [item.id, item.src, item.before, item.after, item.type]);
  useEffect(() => {
    if (inView && mediaReady) setRevealed(true);
  }, [inView, mediaReady]);
  return <div ref={cardRef} role="button" tabIndex={0} data-revealed={revealed ? "true" : "false"} onClick={handleSelect} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); handleSelect(); } }} style={{ "--practice-card-delay": `${Math.min(index * 25, 120)}ms`, opacity: revealed ? 1 : 0, transform: revealed ? "translate3d(0,0,0)" : "translate3d(0,22px,0)" }} className={`practice-card-reveal group work-card relative w-full cursor-pointer overflow-hidden bg-black text-left outline-none focus-visible:ring-2 focus-visible:ring-black/40 ${span}`}>{item.type === "image" && <div className="work-media-zoom"><LoadAwareImage src={item.src} alt={item.title} eager={index < 3} revealOnLoad={false} className="block h-auto w-full" style={{ objectPosition: item.thumbnailPosition || "center" }} /></div>}{item.type === "video" && <div className="work-media-zoom relative">{item.poster && <LoadAwareImage src={item.poster} alt="Video fallback" eager={index < 3} revealOnLoad={false} className="absolute inset-0 h-full w-full object-cover" />}<video src={item.src} poster={item.poster} autoPlay muted loop playsInline className={`relative z-10 block h-auto w-full transition-opacity duration-[420ms] ${videoReady ? "opacity-100" : "opacity-0"}`} aria-label={item.title} preload={index < 3 ? "auto" : "metadata"} onLoadedData={() => { setVideoReady(true); setMediaReady(true); }} onCanPlay={() => { setVideoReady(true); setMediaReady(true); }} /></div>}{item.type === "before" && <BeforeAfter before={item.before} after={item.after} zoomOnHover preserveAspect eager={index < 3} revealOnLoad={false} />}{item.type !== "before" && <div className="absolute inset-0 bg-black/10" />}<div className="pointer-events-none absolute inset-x-0 top-0 z-20 h-28 bg-gradient-to-b from-black/30 via-black/10 to-transparent opacity-100 transition-opacity duration-[300ms] md:opacity-0 md:group-hover:opacity-100" /><ProjectTags tags={item.tags} size={tagSize} /><div className="pointer-events-none absolute inset-x-0 bottom-0 z-20 h-28 bg-gradient-to-t from-black/55 via-black/24 to-transparent opacity-100 transition-opacity duration-[300ms] md:opacity-0 md:group-hover:opacity-100" /><div className="absolute bottom-4 left-4 z-30 text-[#efe9df] opacity-100 transition-opacity duration-[300ms] md:opacity-0 md:group-hover:opacity-100"><p className="font-serif text-3xl tracking-[-0.03em]">{item.title}</p><p style={boldUiTextStyle} className="force-bold-ui project-card-cta mt-1 text-xs font-bold uppercase tracking-[0.055em] text-[#b7b3aa]">View project</p></div></div>;
}

function PracticeMasonryItem({ children }) {
  const itemRef = useRef(null);
  useEffect(() => {
    const item = itemRef.current;
    const grid = item?.parentElement;
    if (!item || !grid) return undefined;
    const fitItem = () => {
      const styles = window.getComputedStyle(grid);
      const rowHeight = Number.parseFloat(styles.gridAutoRows) || 1;
      const rowGap = Number.parseFloat(styles.rowGap) || 0;
      const contentHeight = item.firstElementChild?.getBoundingClientRect().height || item.scrollHeight;
      const span = Math.ceil((contentHeight + rowGap) / (rowHeight + rowGap));
      item.style.gridRowEnd = `span ${Math.max(span, 1)}`;
    };
    fitItem();
    const observer = new ResizeObserver(fitItem);
    observer.observe(item.firstElementChild || item);
    return () => observer.disconnect();
  }, []);
  return <div ref={itemRef} className="practice-masonry-item">{children}</div>;
}

function TagDropdown({ label, group, tags, activeTags, onToggleTag, onClearGroup, openDropdown, setOpenDropdown, isDark }) {
  const selectedCount = tags.filter((tag) => activeTags.includes(tag)).length;
  const isOpen = openDropdown === group;
  const [dropdownSide, setDropdownSide] = useState("left");
  const wrapperRef = useRef(null);
  const triggerClass = group === "intent" ? (isDark ? "border-[#efe9df]/20 bg-[rgba(239,233,223,0.10)] text-[#efe9df]/70 hover:text-[#efe9df]" : "border-black/20 bg-black/[0.09] text-black/65 hover:text-black") : (isDark ? "border-[#efe9df]/15 bg-[rgba(239,233,223,0.07)] text-[#efe9df]/70 hover:text-[#efe9df]" : "border-black/15 bg-black/[0.055] text-black/65 hover:text-black");
  useEffect(() => {
    if (!isOpen) return undefined;
    const updateDropdownSide = () => {
      const rect = wrapperRef.current?.getBoundingClientRect();
      if (!rect) return;
      const menuWidth = Math.min(224, window.innerWidth - 32);
      setDropdownSide(rect.left + menuWidth > window.innerWidth - 16 ? "right" : "left");
    };
    updateDropdownSide();
    window.addEventListener("resize", updateDropdownSide);
    return () => window.removeEventListener("resize", updateDropdownSide);
  }, [isOpen]);
  useEffect(() => {
    if (!isOpen) return undefined;
    const handlePointerDown = (event) => { if (!wrapperRef.current?.contains(event.target)) setOpenDropdown(null); };
    window.addEventListener("pointerdown", handlePointerDown);
    return () => window.removeEventListener("pointerdown", handlePointerDown);
  }, [isOpen, setOpenDropdown]);
  return <div ref={wrapperRef} className="relative"><button type="button" style={boldUiTextStyle} onClick={() => setOpenDropdown(isOpen ? null : group)} className={`force-bold-ui work-filter-control flex cursor-pointer items-center gap-2 rounded-[0.7rem] border px-3 py-1.5 text-[10px] font-normal uppercase tracking-[0.055em] backdrop-blur-md transition-colors duration-[300ms] ${triggerClass}`} aria-expanded={isOpen}><span>{label}</span>{selectedCount > 0 && <span className="transition-colors duration-[300ms] text-black/35 dark:text-[#efe9df]/35">{selectedCount}</span>}<span className={`grid h-3 w-3 place-items-center transition-[color,transform] duration-[300ms] ${isOpen ? "rotate-180" : "rotate-0"} ${isDark ? "text-[#efe9df]/45" : "text-black/45"}`}><Icons name="chevron" size={11} /></span></button><AnimatePresence>{isOpen && <motion.div initial={{ opacity: 0, y: 6 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 6 }} transition={{ duration: 0.18, ease: "easeOut" }} className={`absolute top-[calc(100%+0.45rem)] z-50 flex min-w-56 max-w-[calc(100vw-2rem)] flex-col gap-1.5 rounded-[1rem] border p-2 shadow-[0_18px_60px_rgba(0,0,0,0.12)] backdrop-blur-2xl transition-colors duration-[300ms] ${dropdownSide === "right" ? "right-0 left-auto" : "left-0 right-auto"} md:left-auto md:right-0 ${isDark ? "border-[#efe9df]/15 bg-[#1d1b18]/95" : "border-black/10 bg-[#efe9df]/90"}`}>{tags.map((tag) => { const isActive = activeTags.includes(tag); return <button type="button" key={tag} style={boldUiTextStyle} onClick={() => onToggleTag(tag)} className={`force-bold-ui work-filter-option flex w-full cursor-pointer items-center justify-between rounded-[0.7rem] border px-3 py-2 text-left text-[10px] font-normal uppercase tracking-[0.055em] transition-colors duration-[300ms] ${isActive ? (isDark ? "border-[#efe9df]/35 bg-[#efe9df] text-black" : "border-black/40 bg-black text-[#efe9df]") : (isDark ? "border-[#efe9df]/10 bg-[rgba(239,233,223,0.07)] text-[#efe9df]/70 hover:bg-[rgba(239,233,223,0.12)] hover:text-[#efe9df]" : "border-black/10 bg-black/[0.055] text-black/82 hover:bg-black/[0.085] hover:text-black")}`}><span>{tag}</span>{isActive && <span><Icons name="check" size={12} /></span>}</button>; })}<button type="button" style={boldUiTextStyle} onClick={() => onClearGroup(group)} className={`force-bold-ui work-filter-option mt-1 flex w-full cursor-pointer items-center justify-between rounded-[0.7rem] border px-3 py-2 text-left text-[10px] font-normal uppercase tracking-[0.055em] transition-colors duration-[300ms] ${isDark ? "border-[#efe9df]/22 bg-[rgba(239,233,223,0.18)] text-[#efe9df]/92 hover:bg-[rgba(239,233,223,0.25)]" : "border-black/22 bg-black/[0.14] text-black/78 hover:bg-black/[0.2]"}`}><span>Clear</span>{selectedCount > 0 && <span>{selectedCount}</span>}</button></motion.div>}</AnimatePresence></div>;
}

function workFilterButtonClass(active, isDark) {
  if (active) return isDark ? "border-[#efe9df]/35 bg-[#efe9df] text-black" : "border-black/40 bg-black text-[#efe9df]";
  return isDark ? "border-[#efe9df]/15 bg-[rgba(239,233,223,0.07)] text-[#efe9df]/70 hover:bg-[rgba(239,233,223,0.11)] hover:text-[#efe9df]" : "border-black/15 bg-black/[0.055] text-black/82 hover:bg-black/[0.085] hover:text-black";
}

function PracticeGallery({ onSelectProject, isDark }) {
  const [sortMode, setSortMode] = useState(null);
  const [activeTags, setActiveTags] = useState([]);
  const [baseMode, setBaseMode] = useState("picks");
  const [openDropdown, setOpenDropdown] = useState(null);
  const [workNavDirection, setWorkNavDirection] = useState("down");
  const [workNavVisible, setWorkNavVisible] = useState(false);
  const sectionRef = useRef(null);
  useEffect(() => {
    if (!openDropdown) return undefined;
    const closeOnScroll = () => setOpenDropdown(null);
    window.addEventListener("scroll", closeOnScroll, { once: true, passive: true });
    window.addEventListener("wheel", closeOnScroll, { once: true, passive: true });
    window.addEventListener("touchmove", closeOnScroll, { once: true, passive: true });
    return () => {
      window.removeEventListener("scroll", closeOnScroll);
      window.removeEventListener("wheel", closeOnScroll);
      window.removeEventListener("touchmove", closeOnScroll);
    };
  }, [openDropdown]);
  useEffect(() => {
    let lastY = window.scrollY;
    const updateWorkNav = () => {
      const section = sectionRef.current;
      if (!section || baseMode !== "all") {
        setWorkNavVisible(false);
        lastY = window.scrollY;
        return;
      }
      const rect = section.getBoundingClientRect();
      const sectionExitLine = window.innerHeight - 120;
      const inSection = rect.top < sectionExitLine && rect.bottom > sectionExitLine;
      setWorkNavVisible(inSection);
      const y = window.scrollY;
      if (Math.abs(y - lastY) > 2) setWorkNavDirection(y < lastY ? "up" : "down");
      lastY = y;
    };
    updateWorkNav();
    window.addEventListener("scroll", updateWorkNav, { passive: true });
    window.addEventListener("resize", updateWorkNav);
    return () => {
      window.removeEventListener("scroll", updateWorkNav);
      window.removeEventListener("resize", updateWorkNav);
    };
  }, [baseMode]);
  const toggleTag = (tag) => setActiveTags((current) => (current.includes(tag) ? current.filter((item) => item !== tag) : [...current, tag]));
  const clearGroup = (group) => setActiveTags((current) => current.filter((tag) => tagGroupByName[tag] !== group));
  const jumpWithinWork = () => {
    const section = sectionRef.current;
    if (!section) return;
    section.scrollIntoView({ behavior: "smooth", block: workNavDirection === "down" ? "end" : "start" });
  };
  const visibleItems = useMemo(() => {
    const baseItems = baseMode === "picks" ? pickedProjectIds.map((id) => workItems.find((item) => item.id === id)).filter(Boolean) : workItems;
    const filtered = activeTags.length > 0 ? baseItems.filter((item) => activeTags.every((tag) => item.tags.includes(tag))) : baseItems;
    if (baseMode === "picks" || !sortMode) return filtered;
    return [...filtered].sort((a, b) => (sortMode === "az" ? a.title.localeCompare(b.title) : new Date(b.date).getTime() - new Date(a.date).getTime()));
  }, [activeTags, sortMode, baseMode]);
  useEffect(() => {
    const thumbnailSources = visibleItems.slice(0, 3).flatMap(getWorkThumbnailPreloadSources);
    preloadImages(thumbnailSources);
  }, [visibleItems]);
  const filterKey = `${baseMode}-${sortMode}-${activeTags.slice().sort().join("|") || "none"}`;
  return <section ref={sectionRef} id="practice" data-nav-tone="light" className="relative scroll-mt-24 px-5 pb-28 pt-16 md:px-6 md:pt-24 dark:text-[#efe9df]"><div className="mb-8 flex flex-col gap-6 md:flex-row md:items-end md:justify-between"><Reveal><LineText as="h2" className="font-serif text-6xl tracking-[-0.05em]">Practice</LineText></Reveal><div className="flex max-w-4xl flex-wrap items-center justify-start gap-2 md:justify-end"><button type="button" onClick={() => { setBaseMode("all"); setSortMode("recent"); setOpenDropdown(null); }} style={boldUiTextStyle} className={`${filterControlBaseClass} ${workFilterButtonClass(sortMode === "recent" && baseMode === "all", isDark)}`}>Most recent</button><button type="button" onClick={() => { setBaseMode("all"); setSortMode("az"); setOpenDropdown(null); }} style={boldUiTextStyle} className={`${filterControlBaseClass} ${workFilterButtonClass(sortMode === "az" && baseMode === "all", isDark)}`}>A-Z</button><span className="mx-1 inline-block h-7 w-px bg-black/20 dark:bg-[#efe9df]/20" /><button type="button" onClick={() => { setBaseMode("picks"); setSortMode(null); setOpenDropdown(null); }} style={boldUiTextStyle} className={`${filterControlBaseClass} ${workFilterButtonClass(baseMode === "picks", isDark)}`} aria-pressed={baseMode === "picks"}>Picks</button><button type="button" onClick={() => { setBaseMode("all"); setSortMode("recent"); setOpenDropdown(null); }} style={boldUiTextStyle} className={`${filterControlBaseClass} ${workFilterButtonClass(baseMode === "all", isDark)}`} aria-pressed={baseMode === "all"}>All</button><span className="mx-1 inline-block h-7 w-px shrink-0 bg-black/20 dark:bg-[#efe9df]/20" /><div className="flex flex-nowrap items-center gap-2"><TagDropdown label="Sector" group="sector" tags={tagGroups.sector} activeTags={activeTags} onToggleTag={toggleTag} onClearGroup={clearGroup} openDropdown={openDropdown} setOpenDropdown={setOpenDropdown} isDark={isDark} /><TagDropdown label="Type" group="intent" tags={tagGroups.intent} activeTags={activeTags} onToggleTag={toggleTag} onClearGroup={clearGroup} openDropdown={openDropdown} setOpenDropdown={setOpenDropdown} isDark={isDark} /></div></div></div><motion.div key={filterKey} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3, ease: "easeOut" }} className="practice-masonry w-full">{visibleItems.map((item, index) => <PracticeMasonryItem key={item.id}><WorkCard item={item} index={index} span="" onSelectProject={onSelectProject} /></PracticeMasonryItem>)}</motion.div><AnimatePresence>{workNavVisible && <motion.button type="button" onClick={jumpWithinWork} initial={{ opacity: 0, y: 10, scale: 0.95 }} animate={{ opacity: 1, y: 0, scale: 1 }} exit={{ opacity: 0, y: 10, scale: 0.95 }} transition={{ duration: 0.25, ease: "easeOut" }} className="liquid-glass-shell liquid-glass-compact !fixed bottom-4 right-4 z-[100] grid h-14 w-14 cursor-pointer place-items-center rounded-[1.35rem] md:right-6" aria-label={workNavDirection === "down" ? "Go to end of practice section" : "Go to top of practice section"}><AnimatePresence mode="wait" initial={false}><motion.span key={workNavDirection} initial={{ opacity: 0, rotate: workNavDirection === "down" ? -180 : 180, scale: 0.7 }} animate={{ opacity: 1, rotate: workNavDirection === "down" ? 0 : 180, scale: 1 }} exit={{ opacity: 0, rotate: workNavDirection === "down" ? 180 : -180, scale: 0.7 }} transition={{ duration: 0.3, ease: "easeOut" }} className="relative z-10 text-[#efe9df] drop-shadow-[0_2px_10px_rgba(0,0,0,.65)]"><Icons name="down" size={18} /></motion.span></AnimatePresence></motion.button>}</AnimatePresence></section>;
}

function PortfolioHero() {
  const [activeIndex, setActiveIndex] = useState(0);
  const [incomingIndex, setIncomingIndex] = useState(null);
  const [scrollBlur, setScrollBlur] = useState(0);
  const [heroReady, setHeroReady] = useState(false);
  const [loadedHeroIndexes, setLoadedHeroIndexes] = useState(() => new Set());
  const activeIndexRef = useRef(0);
  const incomingIndexRef = useRef(null);
  const loadedHeroIndexesRef = useRef(new Set());
  const transitionTimerRef = useRef(0);
  const portfolioHeroFadeDuration = 0.55;
  const markHeroImageReady = (index) => {
    const nextLoaded = new Set([...loadedHeroIndexesRef.current, index]);
    loadedHeroIndexesRef.current = nextLoaded;
    setLoadedHeroIndexes(new Set(nextLoaded));
    if (index === 0) setHeroReady(true);
  };
  useEffect(() => {
    let cancelled = false;
    window.clearTimeout(transitionTimerRef.current);
    loadedHeroIndexesRef.current = new Set();
    activeIndexRef.current = 0;
    incomingIndexRef.current = null;
    setLoadedHeroIndexes(new Set());
    setActiveIndex(0);
    setIncomingIndex(null);
    setHeroReady(false);
    preloadImages(portfolioHeroImages.slice(1));
    preloadImagesStrict([portfolioHeroImages[0]]).then(() => {
      if (!cancelled) markHeroImageReady(0);
    }).catch(() => {
      if (!cancelled) setHeroReady(true);
    });
    return () => {
      cancelled = true;
      window.clearTimeout(transitionTimerRef.current);
    };
  }, []);
  useEffect(() => {
    if (!heroReady) return undefined;
    const interval = window.setInterval(() => {
      if (incomingIndexRef.current !== null) return;
      const current = activeIndexRef.current;
      const readySlides = loadedHeroIndexesRef.current;
      for (let step = 1; step <= portfolioHeroImages.length; step += 1) {
        const candidate = (current + step) % portfolioHeroImages.length;
        if (candidate !== current && readySlides.has(candidate)) {
          incomingIndexRef.current = candidate;
          setIncomingIndex(candidate);
          window.clearTimeout(transitionTimerRef.current);
          transitionTimerRef.current = window.setTimeout(() => {
            activeIndexRef.current = candidate;
            incomingIndexRef.current = null;
            setActiveIndex(candidate);
            setIncomingIndex(null);
          }, portfolioHeroFadeDuration * 1000 + 80);
          break;
        }
      }
    }, 5000);
    return () => {
      window.clearInterval(interval);
      window.clearTimeout(transitionTimerRef.current);
    };
  }, [heroReady]);
  useEffect(() => {
    let frame = 0;
    const updateBlur = () => {
      frame = 0;
      const progress = Math.min(1, Math.max(0, window.scrollY / Math.max(1, window.innerHeight * 0.72)));
      setScrollBlur(progress * 10);
    };
    const onScroll = () => {
      if (frame) return;
      frame = requestAnimationFrame(updateBlur);
    };
    updateBlur();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", updateBlur);
    return () => {
      if (frame) cancelAnimationFrame(frame);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", updateBlur);
    };
  }, []);
  return <section id="home" data-nav-tone="light" data-portfolio-hero-active={activeIndex} className="viewport-hero relative overflow-hidden bg-[#efe9df] transition-colors duration-[300ms] dark:bg-black"><div className="absolute inset-0 will-change-transform" style={{ filter: `blur(${scrollBlur}px)`, transform: `scale(${1 + scrollBlur * 0.003})` }}>{portfolioHeroImages.map((src, index) => { const isBase = index === activeIndex && loadedHeroIndexes.has(index) && (index !== 0 || heroReady); const isIncoming = index === incomingIndex && loadedHeroIndexes.has(index); const shouldFade = isIncoming || (index === 0 && isBase); return <motion.img key={src} src={src} alt="" loading={index < 2 ? "eager" : "lazy"} decoding="async" onLoad={() => markHeroImageReady(index)} initial={false} animate={{ opacity: isBase || isIncoming ? 1 : 0 }} transition={{ duration: shouldFade ? portfolioHeroFadeDuration : 0, ease: "easeInOut" }} className={`portfolio-hero-image absolute inset-0 h-full w-full object-cover ${(isBase || isIncoming) ? "is-zooming" : ""}`} style={{ zIndex: isIncoming ? 2 : isBase ? 1 : 0 }} />; })}</div><motion.div initial={false} animate={{ opacity: heroReady ? 1 : 0 }} transition={{ duration: portfolioHeroFadeDuration, ease: "easeInOut" }} className="absolute inset-0 bg-black/28" /><motion.div initial={false} animate={{ opacity: heroReady ? 1 : 0 }} transition={{ duration: portfolioHeroFadeDuration, ease: "easeInOut" }} className="hero-text-gradient-bottom" /><motion.div initial={false} animate={{ opacity: heroReady ? 1 : 0 }} transition={{ duration: portfolioHeroFadeDuration, ease: "easeInOut" }} className="hero-text-gradient-corner" /><motion.div initial={false} animate={{ opacity: heroReady ? 1 : 0 }} transition={{ duration: portfolioHeroFadeDuration, ease: "easeInOut" }} className="absolute bottom-10 left-5 right-5 flex items-end justify-between text-[#efe9df] md:left-6 md:right-6"><LineText as="h1" className="max-w-[96vw] font-serif text-[14vw] leading-[0.82] tracking-[-0.06em] md:max-w-5xl md:text-[7.6vw]"><span className="block whitespace-nowrap">Selected</span><span className="block whitespace-nowrap">visual work.</span></LineText><LineText className="hidden max-w-xs text-sm leading-relaxed md:block" delay={0.08}>A portfolio view focused on technical workflows, image-making skills and production range.</LineText></motion.div></section>;
}

function PortfolioGallery({ onSelectProject, isDark }) {
  const [baseMode, setBaseMode] = useState("picks");
  const [sortMode, setSortMode] = useState(null);
  const [activeTags, setActiveTags] = useState([]);
  const [openDropdown, setOpenDropdown] = useState(null);
  useEffect(() => {
    if (!openDropdown) return undefined;
    const closeOnScroll = () => setOpenDropdown(null);
    window.addEventListener("scroll", closeOnScroll, { once: true, passive: true });
    window.addEventListener("wheel", closeOnScroll, { once: true, passive: true });
    window.addEventListener("touchmove", closeOnScroll, { once: true, passive: true });
    return () => {
      window.removeEventListener("scroll", closeOnScroll);
      window.removeEventListener("wheel", closeOnScroll);
      window.removeEventListener("touchmove", closeOnScroll);
    };
  }, [openDropdown]);
  const toggleTag = (tag) => setActiveTags((current) => (current.includes(tag) ? current.filter((item) => item !== tag) : [...current, tag]));
  const visibleItems = useMemo(() => {
    const baseItems = baseMode === "picks" ? pickedProjectIds.map((id) => portfolioItems.find((item) => item.id === id)).filter(Boolean) : portfolioItems;
    const filtered = activeTags.length > 0 ? baseItems.filter((item) => activeTags.every((tag) => item.tags.includes(tag))) : baseItems;
    if (baseMode === "picks" || !sortMode) return filtered;
    return [...filtered].sort((a, b) => (sortMode === "az" ? a.title.localeCompare(b.title) : new Date(b.date).getTime() - new Date(a.date).getTime()));
  }, [activeTags, sortMode, baseMode]);
  useEffect(() => {
    preloadImages(visibleItems.slice(0, 3).flatMap(getWorkThumbnailPreloadSources));
  }, [visibleItems]);
  const filterKey = `portfolio-${baseMode}-${sortMode}-${activeTags.slice().sort().join("|") || "none"}`;
  return <section id="portfolio-work" data-nav-tone="light" className="relative scroll-mt-24 px-5 pb-28 pt-16 md:px-6 md:pt-24 dark:text-[#efe9df]"><div className="mb-8 flex flex-col gap-6 md:flex-row md:items-end md:justify-between"><Reveal><LineText as="h2" className="font-serif text-6xl tracking-[-0.05em]">Portfolio</LineText></Reveal><div className="flex max-w-4xl flex-wrap items-center justify-start gap-2 md:justify-end"><button type="button" onClick={() => { setBaseMode("all"); setSortMode("recent"); setOpenDropdown(null); }} style={boldUiTextStyle} className={`${filterControlBaseClass} ${workFilterButtonClass(sortMode === "recent" && baseMode === "all", isDark)}`}>Most recent</button><button type="button" onClick={() => { setBaseMode("all"); setSortMode("az"); setOpenDropdown(null); }} style={boldUiTextStyle} className={`${filterControlBaseClass} ${workFilterButtonClass(sortMode === "az" && baseMode === "all", isDark)}`}>A-Z</button><span className="mx-1 inline-block h-7 w-px bg-black/20 dark:bg-[#efe9df]/20" /><button type="button" onClick={() => { setBaseMode("picks"); setSortMode(null); setOpenDropdown(null); }} style={boldUiTextStyle} className={`${filterControlBaseClass} ${workFilterButtonClass(baseMode === "picks", isDark)}`} aria-pressed={baseMode === "picks"}>Picks</button><button type="button" onClick={() => { setBaseMode("all"); setSortMode("recent"); setOpenDropdown(null); }} style={boldUiTextStyle} className={`${filterControlBaseClass} ${workFilterButtonClass(baseMode === "all", isDark)}`} aria-pressed={baseMode === "all"}>All</button><span className="mx-1 inline-block h-7 w-px shrink-0 bg-black/20 dark:bg-[#efe9df]/20" /><TagDropdown label="Workflows" group="workflow" tags={portfolioWorkflowTags} activeTags={activeTags} onToggleTag={toggleTag} onClearGroup={() => setActiveTags([])} openDropdown={openDropdown} setOpenDropdown={setOpenDropdown} isDark={isDark} /></div></div><motion.div key={filterKey} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3, ease: "easeOut" }} className="practice-masonry w-full">{visibleItems.map((item, index) => <PracticeMasonryItem key={item.id}><WorkCard item={item} index={index} span="" onSelectProject={onSelectProject} tagSize="portfolio" /></PracticeMasonryItem>)}</motion.div></section>;
}

function PortfolioAboutStub() {
  return <section id="about" data-nav-tone="dark" className="scroll-mt-24 border-t border-black/10 px-5 py-12 transition-colors duration-[300ms] dark:border-[#efe9df]/10 md:px-[7.75vw]"><Reveal className="grid gap-10 lg:grid-cols-[minmax(0,1fr)_minmax(16rem,.42fr)] lg:items-start"><div><LineText className="text-xs font-medium uppercase tracking-[0.24em] text-black/45 dark:text-[#efe9df]/45">About</LineText><LineText className="mt-8 max-w-5xl font-serif text-4xl leading-tight tracking-[-0.03em] md:text-6xl">{portfolioAboutCopy}</LineText></div><div className="relative overflow-hidden bg-black lg:mt-10"><LoadAwareImage src={media.portrait} alt="Portrait of Afonso Goncalves" className="aspect-[4/5] w-full object-cover opacity-90" /><div className="absolute inset-0 bg-gradient-to-t from-black/30 via-transparent to-transparent" /></div></Reveal></section>;
}

function ImageLightbox({ src, alt, onClose }) {
  const [loadedSrc, setLoadedSrc] = useState(null);
  const [minimumLoaderShown, setMinimumLoaderShown] = useState(false);
  const imageRef = useRef(null);
  const imageReady = loadedSrc === src && minimumLoaderShown;
  useEffect(() => {
    if (!src) return undefined;
    const previousOverflow = document.body.style.overflow;
    const previousPosition = document.body.style.position;
    const previousTop = document.body.style.top;
    const previousLeft = document.body.style.left;
    const previousRight = document.body.style.right;
    const previousWidth = document.body.style.width;
    const scrollY = window.scrollY;
    document.body.style.overflow = "hidden";
    document.body.style.position = "fixed";
    document.body.style.top = `-${scrollY}px`;
    document.body.style.left = "0";
    document.body.style.right = "0";
    document.body.style.width = "100%";
    document.documentElement.classList.add("lightbox-open");
    const onKeyDown = (event) => { if (event.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKeyDown);
    return () => {
      document.body.style.overflow = previousOverflow;
      document.body.style.position = previousPosition;
      document.body.style.top = previousTop;
      document.body.style.left = previousLeft;
      document.body.style.right = previousRight;
      document.body.style.width = previousWidth;
      document.documentElement.classList.remove("lightbox-open");
      window.scrollTo(0, scrollY);
      window.removeEventListener("keydown", onKeyDown);
    };
  }, [src, onClose]);
  const markImageReady = async (image) => {
    if (!image || image.dataset.readySrc !== src) return;
    try {
      await image.decode?.();
    } catch {
      // A completed load is enough if decode is unavailable or already handled.
    }
    if (!image.isConnected || image.dataset.readySrc !== src) return;
    requestAnimationFrame(() => {
      if (image.isConnected && image.dataset.readySrc === src) setLoadedSrc(src);
    });
  };
  useEffect(() => {
    setLoadedSrc(null);
    setMinimumLoaderShown(false);
    const minimumLoaderTimer = window.setTimeout(() => setMinimumLoaderShown(true), 360);
    const image = imageRef.current;
    if (image?.complete && image.naturalWidth > 0) markImageReady(image);
    return () => window.clearTimeout(minimumLoaderTimer);
  }, [src]);
  return <AnimatePresence>{src && <motion.div key="lightbox" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.24, ease: "easeOut" }} className="project-lightbox fixed inset-0 z-[120]" role="dialog" aria-modal="true" aria-label="Expanded image"><button type="button" className="absolute inset-0 h-full w-full cursor-pointer bg-[#efe9df]/86 backdrop-blur-[2px] transition-colors duration-[300ms] dark:bg-black/82" onClick={onClose} aria-label="Close expanded image backdrop" /><div className="pointer-events-none absolute inset-0"><AnimatePresence>{!imageReady && <motion.div key="lightbox-loader" initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -6 }} transition={{ duration: 0.18, ease: "easeOut" }} className="lightbox-loader-card absolute inset-0 grid place-items-center"><div className="grid place-items-center gap-4"><span className="lightbox-loader-bar" /><span className="text-[10px] font-semibold uppercase tracking-[0.18em]">Loading image</span></div></motion.div>}</AnimatePresence><motion.div initial={false} animate={imageReady ? { opacity: 1, scale: 1, filter: "blur(0px)" } : { opacity: 0, scale: 0.972, filter: "blur(8px)" }} exit={{ opacity: 0, scale: 0.985, filter: "blur(4px)" }} transition={{ duration: 0.24, ease: [0.22, 1, 0.36, 1] }} className="absolute inset-0 flex items-center justify-center p-5 md:p-8"><img ref={imageRef} key={src} src={src} alt={alt} data-ready-src={src} loading="eager" decoding="async" onLoad={(event) => markImageReady(event.currentTarget)} onError={() => setLoadedSrc(src)} onClick={onClose} className={`${imageReady ? "pointer-events-auto" : "pointer-events-none"} max-h-[88vh] max-w-[92vw] cursor-pointer object-contain shadow-[0_18px_55px_rgba(0,0,0,.26)]`} /></motion.div></div></motion.div>}</AnimatePresence>;
}

function ProjectPage({ project, onBack, onContact, isDark, onToggleTheme, backLabel = "Back to Practice", showContact = true }) {
  const hidden = useHideOnScroll();
  const [lightboxImage, setLightboxImage] = useState(null);
  const [holdControlsHidden, setHoldControlsHidden] = useState(false);
  const [projectCueVisible, setProjectCueVisible] = useState(() => window.scrollY < 8);
  const heroImage = project.hqSrc || (project.type === "video" ? project.poster : project.src);
  const showProjectVideo = project.type === "video" && !project.hqSrc;
  const secondaryMedia = (project.galleryHq || project.gallery || (project.hqSrc ? [] : [media.work2, media.work4, media.work6, media.work8]))
    .map((item, index) => normalizeProjectGalleryMedia(item, index, project.id))
    .filter((item) => item.src !== heroImage);
  useEffect(() => {
    if (!holdControlsHidden) return undefined;
    let lastY = window.scrollY;
    const releaseOnUpwardScroll = () => {
      const y = window.scrollY;
      if (y < 16 || y < lastY - 8) setHoldControlsHidden(false);
      lastY = y;
    };
    window.addEventListener("scroll", releaseOnUpwardScroll, { passive: true });
    return () => window.removeEventListener("scroll", releaseOnUpwardScroll);
  }, [holdControlsHidden]);
  useEffect(() => {
    const updateCueVisibility = () => setProjectCueVisible(window.scrollY < 8);
    updateCueVisibility();
    window.addEventListener("scroll", updateCueVisibility, { passive: true });
    return () => window.removeEventListener("scroll", updateCueVisibility);
  }, []);
  const jumpPastProjectHero = () => {
    window.scrollTo({ top: window.innerHeight, behavior: "smooth" });
  };
  const openLightbox = (src) => {
    if (!src) return;
    setLightboxImage(src);
  };
  const closeLightbox = () => {
    const lockedScrollY = Math.abs(parseFloat(document.body.style.top || "0")) || 0;
    const shouldHoldControls = lockedScrollY > 80 || window.scrollY > 80;
    setLightboxImage(null);
    if (shouldHoldControls) setHoldControlsHidden(true);
  };
  const controlsHidden = hidden || Boolean(lightboxImage) || holdControlsHidden;
  return <main className={`project-page-shell ${lightboxImage ? "is-lightbox-open" : ""} min-h-screen antialiased transition-colors duration-[300ms] ${isDark ? "dark bg-[#11100e] text-[#efe9df]" : "bg-[#efe9df] text-black"}`} style={{ fontFamily: fontStack }}><motion.div initial={false} animate={navChromeMotion(controlsHidden)} transition={navChromeTransition} style={navChromeStyle(controlsHidden)} className="nav-tone-ignore fixed inset-x-0 top-4 z-40 h-14"><div className="absolute left-4 top-0 md:left-6"><GlassButton onClick={onBack} forceDarkTint={!isDark} className="px-5 text-xs font-medium uppercase tracking-[0.18em] md:tracking-[0.22em]">{backLabel}</GlassButton></div>{showContact && <div className="absolute right-[5.25rem] top-0 max-[640px]:right-[4.5rem] md:right-[5.75rem]"><ContactToggle isDark={isDark} onContact={onContact} /></div>}<div className="absolute right-4 top-0 max-[640px]:right-2 md:right-6"><ThemeToggle isDark={isDark} onToggle={onToggleTheme} /></div></motion.div><section data-nav-tone="light" className="viewport-hero relative overflow-hidden bg-black"><motion.div initial={{ opacity: 0.92, scale: 1.012, filter: "blur(4px)" }} animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }} transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1] }} className="absolute inset-0">{showProjectVideo ? <video src={project.src} poster={project.poster} autoPlay muted loop playsInline className="absolute inset-0 h-full w-full object-cover opacity-85" /> : <img src={heroImage} alt={project.title} className="absolute inset-0 h-full w-full object-cover opacity-90" />}<div className="absolute inset-0 bg-black/20" /></motion.div><div className="absolute bottom-8 left-5 right-5 z-10 text-[#efe9df] md:left-6 md:right-6"><div className="mb-5 flex flex-wrap gap-2">{project.tags.map((tag) => { const isIntent = tagGroupByName[tag] === "intent"; return <span key={tag} className="tag-static relative overflow-hidden rounded-[0.7rem] px-3 py-1 text-[10px] font-bold uppercase tracking-[0.055em]"><span className={`tag-glass ${isIntent ? "intent" : "sector"}`} /><span className={`relative z-10 ${isIntent ? "text-[#efe9df]/90" : "text-[#efe9df]"}`}>{tag}</span></span>; })}</div><LineText as="h1" className="max-w-5xl font-serif text-[15vw] leading-[0.82] tracking-[-0.06em] md:text-[8vw]">{project.title}</LineText></div><motion.button type="button" data-project-scroll-cue="true" onClick={jumpPastProjectHero} aria-label="Scroll to project details" initial={{ opacity: 0 }} animate={{ opacity: projectCueVisible ? 1 : 0 }} transition={{ duration: projectCueVisible ? 0.55 : 0.28, delay: projectCueVisible ? 2 : 0, ease: "easeOut" }} className="absolute bottom-12 left-1/2 z-20 -translate-x-1/2 cursor-pointer text-[#efe9df]"><div className="absolute -bottom-8 left-1/2 h-24 w-56 -translate-x-1/2 rounded-t-full bg-black/35 blur-2xl" /><motion.span animate={{ y: [0, 12, 0] }} transition={{ duration: 1.75, repeat: Infinity, ease: "easeInOut" }} className="relative z-10 block drop-shadow-[0_2px_14px_rgba(0,0,0,.72)]"><Icons name="down" size={40} /></motion.span></motion.button></section><section data-nav-tone="dark" className="grid gap-12 px-5 py-24 md:grid-cols-12 md:px-6 md:py-32"><Reveal className="md:col-span-3"><LineText className="text-xs font-medium uppercase tracking-[0.24em] text-black/55 dark:text-[#efe9df]/55">Project</LineText><div className="tone-transition mt-8 space-y-3 text-sm text-black/70 dark:text-[#efe9df]/70"><p><span className="text-black dark:text-[#efe9df]">Location:</span> {project.location}</p><p><span className="text-black dark:text-[#efe9df]">Year:</span> {project.year}</p></div></Reveal><Reveal className="md:col-span-7 md:col-start-5" delay={0.04}><LineText className="font-serif text-4xl leading-tight tracking-[-0.03em] md:text-6xl">{project.description}</LineText></Reveal></section><ProjectGallery project={project} heroImage={heroImage} showProjectVideo={showProjectVideo} secondaryMedia={secondaryMedia} onExpand={openLightbox} /><ImageLightbox src={lightboxImage} alt={project.title} onClose={closeLightbox} /><CleanFooter /></main>;
}

function ResumeEntry({ item }) {
  return <article className="tone-transition grid gap-4 border-t border-black/20 py-6 dark:border-[#efe9df]/20 md:grid-cols-[9rem_1fr]"><p className="tone-transition text-xs font-semibold uppercase tracking-[0.16em] text-black/45 dark:text-[#efe9df]/45">{item.period}</p><div><h3 className="tone-transition font-serif text-2xl leading-tight tracking-[-0.03em] md:text-3xl">{item.title}</h3>{item.place && <p className="tone-transition mt-2 text-xs font-semibold uppercase tracking-[0.14em] text-black/48 dark:text-[#efe9df]/48">{item.place}</p>}{item.detail && <p className="tone-transition mt-4 max-w-3xl text-sm leading-7 text-black/65 dark:text-[#efe9df]/65">{item.detail}</p>}</div></article>;
}

function ResumeSection({ eyebrow, title, children, className = "" }) {
  return <Reveal className={className}><div className="tone-transition grid gap-8 border-t border-black/20 pt-8 dark:border-[#efe9df]/20 md:grid-cols-[12rem_1fr]"><p className="tone-transition text-xs font-medium uppercase tracking-[0.24em] text-black/45 dark:text-[#efe9df]/45">{eyebrow}</p><div><h2 className="tone-transition mb-6 font-serif text-5xl leading-[0.9] tracking-[-0.05em] md:text-6xl">{title}</h2>{children}</div></div></Reveal>;
}

function ResumePage({ onBack, isDark, onToggleTheme }) {
  const hidden = useHideOnScroll();
  return <main className={`min-h-screen antialiased transition-colors duration-[300ms] ${isDark ? "dark bg-[#11100e] text-[#efe9df]" : "bg-[#efe9df] text-black"}`} style={{ fontFamily: fontStack }}>
    <motion.div initial={false} animate={navChromeMotion(hidden)} transition={navChromeTransition} style={navChromeStyle(hidden)} className="nav-tone-ignore fixed inset-x-0 top-4 z-40 flex w-full items-center justify-between px-4 md:px-6">
      <GlassButton onClick={onBack} forceDarkTint={!isDark} className="px-5 text-xs font-medium uppercase tracking-[0.18em] md:tracking-[0.22em]">Back</GlassButton>
      <ThemeToggle isDark={isDark} onToggle={onToggleTheme} />
    </motion.div>
    <section data-nav-tone="dark" className="px-5 pb-20 pt-28 md:px-6 md:pb-24 md:pt-16">
      <Reveal className="mx-auto grid max-w-[91rem] gap-7 md:grid-cols-[8.5rem_minmax(0,1fr)] md:items-start">
        <LineText className="pt-1 text-xs font-medium uppercase tracking-[0.24em] text-black/38 dark:text-[#efe9df]/42">R&eacute;sume</LineText>
        <div>
          <LineText as="h1" className="max-w-[70.2rem] font-serif text-[clamp(4.25rem,5.45vw,7.05rem)] leading-[0.76] tracking-[-0.065em]">{resumeData.name}</LineText>
          <LineText className="mt-7 max-w-[72.8rem] font-serif text-[clamp(1.8rem,2.05vw,2.45rem)] leading-[1.02] tracking-[-0.045em]" delay={0.04}>{resumeData.intro}</LineText>
        </div>
      </Reveal>
    </section>
    <section data-nav-tone="dark" className="mx-auto grid max-w-[67.6rem] gap-16 px-5 pb-28 md:px-6">
      <ResumeSection eyebrow="Work" title="Experience"><div>{resumeData.experience.map((item) => <ResumeEntry key={`${item.period}-${item.title}-${item.place}`} item={item} />)}</div></ResumeSection>
      <ResumeSection eyebrow="Tools" title="Proficiency"><ul className="tone-transition grid gap-x-10 gap-y-3 text-sm leading-6 text-black/70 dark:text-[#efe9df]/70 sm:grid-cols-2">{resumeData.proficiency.map((item) => <li key={item}>{item}</li>)}</ul><p className="tone-transition mt-8 max-w-3xl border-t border-black/15 pt-5 text-sm leading-7 text-black/60 dark:border-[#efe9df]/15 dark:text-[#efe9df]/60">{resumeData.aiNote}</p></ResumeSection>
      <ResumeSection eyebrow="Training" title="Education"><div>{resumeData.education.map((item) => <ResumeEntry key={`${item.period}-${item.title}-${item.place}`} item={item} />)}</div></ResumeSection>
      <ResumeSection eyebrow="Certificates" title="Certificates"><div>{resumeData.certificates.map((item) => <ResumeEntry key={`${item.period}-${item.title}`} item={item} />)}</div></ResumeSection>
    </section>
    <CleanFooter />
  </main>;
}

function ServiceLearnMore({ service, onOpen }) {
  return <button type="button" onClick={(event) => { event.stopPropagation(); onOpen(service); }} className="mt-5 inline-flex w-fit cursor-pointer items-center gap-2 border-b border-[#efe9df]/20 pb-1 text-sm font-semibold uppercase tracking-[0.08em] text-[#efe9df]/60 transition hover:border-[#efe9df]/45 hover:text-[#efe9df]/85" aria-label={`Learn more about ${service.title}`}>Learn more <Icons name="arrow" size={14} /></button>;
}

function ServiceModal({ service, onClose }) {
  const isWebExperience = service?.title === "Web Experiences";
  const isMarketingCollateral = service?.title === "Marketing Collateral";
  useEffect(() => {
    if (!service) return undefined;
    const previousOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    const onKeyDown = (event) => { if (event.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKeyDown);
    return () => {
      document.body.style.overflow = previousOverflow;
      window.removeEventListener("keydown", onKeyDown);
    };
  }, [service, onClose]);
  return <AnimatePresence>{service && <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.24, ease: "easeOut" }} className="fixed inset-0 z-[110] overflow-y-auto bg-black/55 px-5 py-6 text-[#efe9df] backdrop-blur-md md:px-6" role="dialog" aria-modal="true" aria-label={`${service.title} details`}><motion.div initial={{ opacity: 0, scale: 0.96, y: 18 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.96, y: 18 }} transition={{ duration: 0.32, ease: [0.22, 1, 0.36, 1] }} className="relative mx-auto my-10 min-h-[78vh] max-w-6xl overflow-hidden rounded-[1.35rem] border border-[#efe9df]/16 bg-[#11100e]/88 shadow-[0_30px_90px_rgba(0,0,0,.42)]"><button type="button" onClick={onClose} className="group nav-glass nav-glass-dark !absolute right-4 top-4 z-[120] grid h-14 w-14 cursor-pointer place-items-center rounded-[1.35rem]" aria-label="Close service details"><span className="nav-glass-content text-[#efe9df] drop-shadow-[0_1px_8px_rgba(0,0,0,.42)]"><Icons name="close" size={18} /></span></button><div className="absolute inset-0"><LoadAwareImage src={service.img} alt="" className="h-full w-full object-cover opacity-25" /><div className="absolute inset-0 bg-gradient-to-br from-black/80 via-[#11100e]/86 to-black/56" /></div><div className="relative z-10 grid min-h-[78vh] gap-10 p-6 pt-24 md:grid-cols-12 md:p-10 md:pt-28"><div className="md:col-span-5"><p className="mb-8 text-xs font-semibold uppercase tracking-[0.22em] text-[#efe9df]/45">Service detail</p><h2 className="font-serif text-6xl leading-[0.86] tracking-[-0.06em] md:text-8xl">{service.title}</h2></div><div className="md:col-span-6 md:col-start-7"><p className="font-serif text-3xl leading-tight tracking-[-0.03em] md:text-5xl">{service.copy}</p>{isWebExperience ? <div className="mt-10 grid gap-8 text-sm leading-7 text-[#efe9df]/72 md:grid-cols-2"><p>A complete website workflow: visual direction, interface design, front-end development, responsive behavior, content structure and publishing handled as one coherent system instead of separate disconnected steps.</p><p>The work can include content development, search engine optimization, visibility for AI and large language model discovery, hosting setup, domain coordination, launch support and ongoing maintenance after the site is live.</p><div className="md:col-span-2"><p className="mb-4 text-xs font-semibold uppercase tracking-[0.2em] text-[#efe9df]/48">Included workflow</p><ul className="grid gap-3 md:grid-cols-2">{["Tailor-made design language and user experience", "Website development from the ground up", "Responsive desktop and mobile implementation", "Content development and page structure", "SEO and AI/LLM search presence optimization", "Hosting, domain setup and technical launch", "Maintenance, updates and continued support"].map((item) => <li key={item} className="border-t border-[#efe9df]/16 pt-3">{item}</li>)}</ul></div></div> : isMarketingCollateral ? <div className="mt-10 grid gap-8 text-sm leading-7 text-[#efe9df]/72 md:grid-cols-2"><p>Design support for developments that need more than standalone renderings: sales brochures, launch decks, printed handouts, campaign visuals, property sheets and graphic systems that hold text, image and project information together.</p><p>The work can pair architectural imagery with layout, typography, diagram logic, illustration-style visuals and clear information hierarchy so the project feels coherent across print, PDF and digital presentation formats.</p><div className="md:col-span-2"><p className="mb-4 text-xs font-semibold uppercase tracking-[0.2em] text-[#efe9df]/48">Possible deliverables</p><ul className="grid gap-3 md:grid-cols-2">{["Real estate brochures and sales PDFs", "Launch and investor presentation material", "Illustrated architectural visuals or diagrams", "Property sheets and specification layouts", "Campaign graphics for digital publication", "Visual systems that align renders, copy and brand tone"].map((item) => <li key={item} className="border-t border-[#efe9df]/16 pt-3">{item}</li>)}</ul></div></div> : <div className="mt-10 grid gap-6 text-sm leading-7 text-[#efe9df]/72 md:grid-cols-2"><p>This page can later hold project examples, package details, expected timelines, input requirements and client-facing notes specific to {service.title.toLowerCase()}.</p><p>Define goals, reference material, deliverables, review cadence and publication use before production starts.</p></div>}</div></div></motion.div></motion.div>}</AnimatePresence>;
}

function Services() {
  const services = useMemo(() => servicesData, []);
  const [isMobileBackground, setIsMobileBackground] = useState(false);
  const midpoint = Math.ceil(services.length / 2);
  const serviceColumns = [services.slice(0, midpoint), services.slice(midpoint)];
  const [active, setActive] = useState(null);
  const [expanded, setExpanded] = useState(-1);
  const [serviceModal, setServiceModal] = useState(null);
  const [backgroundService, setBackgroundService] = useState(null);
  const [backgroundVisible, setBackgroundVisible] = useState(false);
  const activeService = active === null ? null : services[active];
  useEffect(() => {
    const query = window.matchMedia("(max-width: 767px)");
    const update = () => setIsMobileBackground(query.matches);
    update();
    query.addEventListener?.("change", update);
    return () => query.removeEventListener?.("change", update);
  }, []);
  useEffect(() => {
    const fadeOutMs = 550;
    if (!activeService) {
      setBackgroundVisible(false);
      const clearTimer = window.setTimeout(() => setBackgroundService(null), fadeOutMs);
      return () => window.clearTimeout(clearTimer);
    }
    const nextVideo = activeService.video ? (isMobileBackground ? activeService.videoMobile || activeService.video : activeService.video) : null;
    let cancelled = false;
    const fadeOutPromise = new Promise((resolve) => window.setTimeout(resolve, fadeOutMs));
    const commitBackground = () => {
      Promise.all([fadeOutPromise]).then(() => {
        if (cancelled) return;
        setBackgroundService(activeService);
        window.requestAnimationFrame(() => {
          if (!cancelled) setBackgroundVisible(true);
        });
      });
    };
    setBackgroundVisible(false);
    if (nextVideo) {
      const video = document.createElement("video");
      video.muted = true;
      video.playsInline = true;
      video.preload = "auto";
      video.onloadeddata = commitBackground;
      video.oncanplay = commitBackground;
      video.onerror = commitBackground;
      video.src = nextVideo;
      video.load();
    } else {
      const image = new Image();
      image.onload = commitBackground;
      image.onerror = commitBackground;
      image.src = activeService.img;
      if (image.complete) commitBackground();
    }
    return () => {
      cancelled = true;
    };
  }, [activeService, isMobileBackground]);
  const backgroundVideo = backgroundService?.video ? (isMobileBackground ? backgroundService.videoMobile || backgroundService.video : backgroundService.video) : null;
  return <section id="services" data-nav-tone="light" className="relative flex min-h-[72vh] scroll-mt-24 items-center overflow-hidden bg-black px-5 py-10 text-[#efe9df] md:px-6 md:py-12"><AnimatePresence mode="sync">{backgroundService && (backgroundVideo ? <motion.video key={backgroundVideo} src={backgroundVideo} poster={backgroundService.img} initial={{ opacity: 0, scale: 1.03 }} animate={{ opacity: backgroundVisible ? 0.55 : 0, scale: 1 }} exit={{ opacity: 0, scale: 1.03 }} transition={{ duration: 0.55, ease: "easeInOut" }} className="absolute inset-0 h-full w-full object-cover" autoPlay muted loop playsInline preload="metadata" /> : <motion.img key={backgroundService.img} src={backgroundService.img} alt="Service background" initial={{ opacity: 0, scale: 1.03 }} animate={{ opacity: backgroundVisible ? 0.55 : 0, scale: 1 }} exit={{ opacity: 0, scale: 1.03 }} transition={{ duration: 0.55, ease: "easeInOut" }} className="absolute inset-0 h-full w-full object-cover" />)}</AnimatePresence><div className="absolute inset-0 bg-black/35" /><div className="relative z-10 mx-auto w-full max-w-6xl py-5 md:py-6"><Reveal><p className="mb-8 text-left text-xs font-medium uppercase tracking-[0.24em]">Services</p></Reveal><div className="grid gap-x-12 gap-y-0 md:grid-cols-2">{serviceColumns.map((column) => <div key={column[0]?.title} className="flex flex-col">{column.map((service) => { const index = services.findIndex((entry) => entry.title === service.title); const isExpanded = expanded === index; return <Reveal key={service.title} className="relative border-t border-[#efe9df]/25 py-4 last:border-b md:py-5"><button type="button" onClick={() => { setExpanded(isExpanded ? -1 : index); setActive(isExpanded ? null : index); }} className="w-full cursor-pointer text-left font-serif text-4xl leading-[0.95] tracking-[-0.05em] md:text-6xl" aria-expanded={isExpanded}>{service.title}</button><AnimatePresence initial={false}>{isExpanded && <motion.div key={`${service.title}-copy`} initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ height: { duration: 0.35, ease: [0.76, 0, 0.24, 1] }, opacity: { duration: 0.2 } }} className="overflow-hidden"><p className="max-w-2xl pt-5 text-sm leading-7 text-[#efe9df]/85">{service.copy}</p>{service.learnMore && <ServiceLearnMore service={service} onOpen={setServiceModal} />}</motion.div>}</AnimatePresence></Reveal>; })}</div>)}</div></div><ServiceModal service={serviceModal} onClose={() => setServiceModal(null)} /></section>;
}

function Blog({ onSelectPost }) {
  return <section id="blog" data-nav-tone="dark" className="scroll-mt-24 px-5 py-10 md:px-6 md:py-12"><Reveal><LineText as="h2" className="mb-8 font-serif text-6xl tracking-[-0.05em]">Journal</LineText></Reveal><div className="grid gap-4 md:grid-cols-3">{blogPosts.map((post, index) => <Reveal key={post.slug} delay={index * 0.04} className="border-t border-black/25 py-5 dark:border-[#efe9df]/20"><article><a href={`/visualization/journal/${post.slug}`} onClick={(event) => { event.preventDefault(); onSelectPost?.(post); }} className="group block cursor-pointer text-left"><div className="work-card relative mb-4 aspect-[4/3] overflow-hidden bg-black"><div className="work-media-zoom h-full"><LoadAwareImage src={post.image} alt="" className="block h-full w-full object-cover" /></div><div className="absolute inset-0 bg-black/10" /></div><LineText as="h3" className="font-serif text-3xl leading-[0.98] tracking-[-0.03em]">{post.title}</LineText><LineText className="mt-3 text-sm leading-6 text-black/70 dark:text-[#efe9df]/70">{post.copy}</LineText><span className="mt-4 inline-flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.12em] text-black/50 transition-colors duration-[300ms] group-hover:text-black dark:text-[#efe9df]/50 dark:group-hover:text-[#efe9df]">Read article <Icons name="arrow" size={14} /></span></a></article></Reveal>)}</div></section>;
}

function JournalBodyParagraph({ paragraph, index }) {
  if (index !== 0) {
    return <LineText as="p" className="text-base leading-8 text-black/72 dark:text-[#efe9df]/72 md:text-lg md:leading-9">{paragraph}</LineText>;
  }
  const firstLetter = paragraph.slice(0, 1);
  const rest = paragraph.slice(1).trimStart();
  return <p className="text-base leading-8 text-black/72 dark:text-[#efe9df]/72 md:text-lg md:leading-9"><span className="journal-dropcap">{firstLetter}</span><LineText as="span">{rest}</LineText></p>;
}

function JournalArticlePage({ post, onBack, onContact, isDark, onToggleTheme }) {
  return <main className={`min-h-screen antialiased transition-colors duration-[300ms] ${isDark ? "dark bg-[#11100e] text-[#efe9df]" : "bg-[#efe9df] text-black"}`} style={{ fontFamily: fontStack }}><div className="nav-tone-ignore fixed inset-x-0 top-4 z-40 h-14"><div className="absolute left-4 top-0 md:left-6"><GlassButton onClick={onBack} forceDarkTint={!isDark} className="px-5 text-xs font-medium uppercase tracking-[0.18em] md:tracking-[0.22em]">Back to Journal</GlassButton></div><div className="absolute right-[5.25rem] top-0 max-[640px]:right-[4.5rem] md:right-[5.75rem]"><ContactToggle isDark={isDark} onContact={onContact} /></div><div className="absolute right-4 top-0 max-[640px]:right-2 md:right-6"><ThemeToggle isDark={isDark} onToggle={onToggleTheme} /></div></div><article data-nav-tone="dark" className="px-5 pb-24 pt-28 md:px-6 md:pb-28 md:pt-32"><Reveal className="mx-auto max-w-6xl"><LineText className="mb-8 text-xs font-semibold uppercase tracking-[0.24em] text-black/45 dark:text-[#efe9df]/45">OPINION</LineText><LineText as="h1" className="max-w-5xl font-serif text-[3.45rem] leading-[0.88] tracking-[-0.055em] md:text-[5.4rem] lg:text-[6.5rem]">{post.title}</LineText><LineText className="mt-8 max-w-2xl text-base leading-8 text-black/62 dark:text-[#efe9df]/62">{post.copy}</LineText></Reveal>{post.image && <Reveal className="mx-auto mt-14 max-w-6xl"><LoadAwareImage src={post.image} alt="" className="aspect-[16/9] w-full object-cover" /></Reveal>}<div className="journal-body mx-auto mt-14 grid max-w-6xl gap-6 border-t border-black pt-10 dark:border-white">{post.body.map((paragraph, index) => <JournalBodyParagraph key={paragraph} paragraph={paragraph} index={index} />)}</div></article><CleanFooter /></main>;
}

function Footer() {
  const socialButtonClass = "tone-transition grid h-9 w-9 cursor-pointer place-items-center rounded-[0.8rem] border border-black/18 bg-black/[0.035] text-black/68 shadow-[inset_0_1px_0_rgba(0,0,0,0.04)] hover:border-black/34 hover:bg-black/[0.07] hover:text-black dark:border-[#efe9df]/22 dark:bg-[#efe9df]/[0.055] dark:text-[#efe9df] dark:shadow-[inset_0_1px_0_rgba(239,233,223,0.08)] dark:hover:border-[#efe9df]/42 dark:hover:bg-[#efe9df]/[0.10] dark:hover:text-[#efe9df]";
  return <footer className="grid gap-8 border-t border-black/20 px-5 py-10 text-sm dark:border-transparent md:grid-cols-3 md:px-6"><PlaceholderLogo className="text-black dark:text-[#efe9df]" /><p className="text-black/55 dark:text-[#efe9df]/55">&copy; 2026 Afonso Gon&ccedil;alves. All rights reserved.</p><div className="flex flex-col gap-4 md:items-end md:text-right"><div><p>hello@afoviz.com</p><p>+351 961 490 792</p></div><div className="flex gap-3"><a href={socialLinks.instagram} target="_blank" rel="noreferrer" className={socialButtonClass} aria-label="Instagram"><Icons name="instagram" size={16} /></a><a href={socialLinks.youtube} target="_blank" rel="noreferrer" className={socialButtonClass} aria-label="YouTube"><Icons name="youtube" size={18} /></a></div></div></footer>;
}

function CleanFooter() {
  return <footer className="border-t border-black/16 px-5 py-10 text-sm dark:border-transparent md:px-6 md:py-12"><div className="grid w-full gap-10 md:grid-cols-[minmax(0,1fr)_auto] md:items-end"><div className="grid gap-7"><PlaceholderLogo className="text-black dark:text-[#efe9df]" showText={false} /><p className="max-w-sm text-black/50 dark:text-[#efe9df]/50">&copy; 2026 Afonso Gon&ccedil;alves. All rights reserved.</p></div><address className="grid gap-5 not-italic md:min-w-[22rem] md:grid-cols-2 md:text-right"><div className="grid gap-2 border-t border-black/12 pt-3 dark:border-transparent"><p className="text-[10px] font-semibold uppercase tracking-[0.22em] text-black/40 dark:text-[#efe9df]/40">Email</p><a href="mailto:hello@afoviz.com" className="tone-transition cursor-pointer text-black/76 hover:text-black dark:text-[#efe9df]/76 dark:hover:text-[#efe9df]">hello@afoviz.com</a></div><div className="grid gap-2 border-t border-black/12 pt-3 dark:border-transparent"><p className="text-[10px] font-semibold uppercase tracking-[0.22em] text-black/40 dark:text-[#efe9df]/40">Phone</p><a href="tel:+351961490792" className="tone-transition cursor-pointer text-black/76 hover:text-black dark:text-[#efe9df]/76 dark:hover:text-[#efe9df]">+351 961 490 792</a></div></address></div></footer>;
}

const ecosystemBranches = [
  { id: "visualization", path: "/visualization", title: "Visualization", eyebrow: "Images for spaces that need to sell the idea", copy: "The client-facing room: architecture, property, hospitality and spatial media built to look sharp, stay legible, and hold up when people start asking hard questions.", status: "Client-facing room", image: media.ericeiraResidences1, accent: "#6f9bff" },
  { id: "portfolio", path: "/portfolio", title: "Portfolio", eyebrow: "The receipts", copy: "No funnel, no velvet rope, no pitch-deck perfume. Just selected work, the craft behind it, and enough context to know what I actually did.", status: "Work-only room", image: media.montiSuites, accent: "#ff8f66" },
  { id: "personal", path: "/personal", title: "Personal", eyebrow: "Photos, songs, notes, small evidence", copy: "The unpolished room: pictures I kept, tracks I keep replaying, and stray notes from a life that is not trying to be a brand every second.", status: "Private room", image: media.about2, accent: "#72c98c" },
];

function normalizeSitePath(pathname) {
  const cleanPath = pathname.replace(/\/+$/, "") || "/";
  if (cleanPath.startsWith("/visualization/journal/")) return "/visualization";
  if (cleanPath === "/real-estate" || cleanPath === "/architecture" || cleanPath === "/archviz") return "/visualization";
  return ecosystemBranches.some((branch) => branch.path === cleanPath) ? cleanPath : "/";
}

function getJournalPostFromPath(pathname) {
  const cleanPath = pathname.replace(/\/+$/, "") || "/";
  const match = cleanPath.match(/^\/visualization\/journal\/([^/]+)$/);
  if (!match) return null;
  return blogPosts.find((post) => post.slug === match[1]) || null;
}

function EcosystemShell({ children, isDark, onToggleTheme }) {
  const hidden = useHideOnScroll();
  return <main className={`min-h-screen antialiased transition-colors duration-[300ms] ${isDark ? "bg-[#11100e] text-[#efe9df]" : "bg-[#efe9df] text-black"}`} style={{ fontFamily: fontStack }}><motion.div initial={false} animate={navChromeMotion(hidden)} transition={navChromeTransition} style={navChromeStyle(hidden)} className="nav-tone-ignore fixed right-4 top-4 z-40 md:right-6"><ThemeToggle isDark={isDark} onToggle={onToggleTheme} /></motion.div>{children}</main>;
}

function EcosystemCard({ branch, index, onNavigate, ready = true }) {
  const [imageReady, setImageReady] = useState(false);
  const markReady = () => setImageReady(true);
  const hiddenState = { opacity: 0, y: 34, scale: 0.94, clipPath: "inset(14% 0 14% 0 round 1.35rem)", filter: "blur(10px)" };
  const visibleState = { opacity: 1, y: 0, scale: 1, clipPath: "inset(0% 0 0% 0 round 1.35rem)", filter: "blur(0px)" };
  return <motion.a key={branch.id} href={branch.path} onClick={(event) => { event.preventDefault(); onNavigate(branch.path); }} initial={hiddenState} animate={imageReady && ready ? visibleState : hiddenState} transition={{ duration: 0.72, delay: ready ? index * 0.08 : 0, ease: [0.16, 1, 0.3, 1] }} className="ecosystem-card group relative min-h-[27rem] cursor-pointer overflow-hidden rounded-[1.35rem] border border-white/25 bg-black/[0.04] shadow-[0_24px_80px_rgba(0,0,0,.10)] backdrop-blur-xl transition hover:-translate-y-2 hover:border-white/45 hover:shadow-[0_32px_90px_rgba(0,0,0,.18)] dark:border-[#efe9df]/14 dark:bg-[#efe9df]/[0.045] dark:hover:border-[#efe9df]/28"><LoadAwareImage src={branch.image} alt="" eager revealOnLoad={false} onLoad={markReady} className="ecosystem-card-image absolute inset-0 h-full w-full object-cover opacity-78 dark:opacity-64" /><span className="absolute inset-0 z-[1] bg-gradient-to-t from-[#efe9df]/95 via-[#efe9df]/38 to-white/22 opacity-100 transition-opacity duration-[300ms] group-hover:from-[#efe9df]/84 group-hover:via-[#efe9df]/26 group-hover:to-white/14 dark:opacity-0" /><span className="absolute inset-0 z-[1] bg-gradient-to-t from-[#11100e]/90 via-[#11100e]/36 to-black/20 opacity-0 transition-opacity duration-[300ms] dark:opacity-100 dark:group-hover:from-[#11100e]/82 dark:group-hover:via-[#11100e]/28" /><span className="ecosystem-card-glow" aria-hidden="true" /><div className="relative z-10 flex h-full min-h-[27rem] flex-col justify-end p-5"><div className="mb-auto flex items-start justify-end gap-4"><span className="ecosystem-card-arrow grid h-10 w-10 place-items-center rounded-full bg-white/72 text-black shadow-[0_10px_35px_rgba(0,0,0,.12)] dark:bg-[#efe9df]/88"><Icons name="right" size={17} /></span></div><h2 className="font-serif text-4xl leading-none tracking-[-0.055em] md:text-[2.55rem]">{branch.title}</h2><div className="ecosystem-card-details grid max-h-0 overflow-hidden opacity-0 transition-[max-height,opacity,padding] group-hover:max-h-16 group-hover:pt-4 group-hover:opacity-100"><p className="inline-flex w-fit items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-black/56 dark:text-[#efe9df]/58">Go to website <Icons name="arrow" size={13} /></p></div></div></motion.a>;
}

function RootSelector({ isDark, onToggleTheme, onNavigate }) {
  const [indexImagesReady, setIndexImagesReady] = useState(false);
  const indexChromeHidden = useHideOnScroll();
  useEffect(() => {
    let cancelled = false;
    setIndexImagesReady(false);
    Promise.allSettled(ecosystemBranches.map((branch) => preloadImagesStrict([branch.image]))).then(() => {
      if (!cancelled) setIndexImagesReady(true);
    });
    return () => {
      cancelled = true;
    };
  }, []);
  return <EcosystemShell isDark={isDark} onToggleTheme={onToggleTheme}><section data-nav-tone="dark" className="ecosystem-index relative flex min-h-screen flex-col overflow-hidden px-4 py-6 md:px-6"><motion.div initial={false} animate={navChromeMotion(indexChromeHidden)} transition={navChromeTransition} style={navChromeStyle(indexChromeHidden)} className="relative z-20 flex items-start justify-between pr-20 md:pr-24"><PlaceholderLogo className="text-black dark:text-[#efe9df]" showText={false} /></motion.div><div className="relative z-10 mx-auto flex w-full max-w-6xl flex-1 flex-col justify-center py-20 md:py-16"><div className="mx-auto mb-10 max-w-3xl text-center md:mb-14"><LineText as="h1" className="font-serif text-[4rem] leading-[0.86] tracking-[-0.06em] md:text-[6.2rem]">Pick the door. Get to the work.</LineText></div><div className="grid gap-4 md:grid-cols-3">{ecosystemBranches.map((branch, index) => <EcosystemCard key={branch.id} branch={branch} index={index} ready={indexImagesReady} onNavigate={onNavigate} />)}</div></div></section></EcosystemShell>;
}

function BranchPlaceholder({ branch, isDark, onToggleTheme, onNavigate }) {
  return <EcosystemShell isDark={isDark} onToggleTheme={onToggleTheme}><section data-nav-tone="dark" className="grid min-h-screen content-between gap-16 px-5 py-6 md:px-6"><div className="flex items-center justify-between"><button type="button" onClick={() => onNavigate("/")} className="cursor-pointer text-xs font-semibold uppercase tracking-[0.18em] text-black/50 transition hover:text-black dark:text-[#efe9df]/55 dark:hover:text-[#efe9df]">Back to index</button></div><div className="grid gap-10 md:grid-cols-12 md:items-end"><div className="md:col-span-7"><LineText className="mb-8 text-xs font-semibold uppercase tracking-[0.22em] text-black/45 dark:text-[#efe9df]/45">{branch.eyebrow}</LineText><LineText as="h1" className="font-serif text-[5rem] leading-[0.82] tracking-[-0.07em] md:text-[10vw]">{branch.title}</LineText></div><div className="md:col-span-4 md:col-start-9"><p className="text-sm leading-7 text-black/60 dark:text-[#efe9df]/60">{branch.copy}</p><p className="mt-8 border-t border-black/12 pt-5 text-xs font-semibold uppercase tracking-[0.18em] text-black/38 dark:border-[#efe9df]/12 dark:text-[#efe9df]/38">{branch.status}</p></div></div><CleanFooter /></section></EcosystemShell>;
}

function ResourceContent({ content }) {
  if (typeof content === "string") return <p className="max-w-2xl pt-4 text-sm leading-7 text-black/60 dark:text-[#efe9df]/60">{content}</p>;
  return <div className="max-w-3xl space-y-4 pt-4 text-sm leading-7 text-black/60 dark:text-[#efe9df]/60">{content.intro && <p>{content.intro}</p>}{content.bullets && <ul className="space-y-2 pl-4">{content.bullets.map((item) => <li key={item} className="list-disc pl-1">{item}</li>)}</ul>}</div>;
}

function ContactAccordionGroup({ group, openResource, setOpenResource }) {
  return <div className="border-t border-black/20 py-2 first:border-t-0 dark:border-[#efe9df]/20"><p className="mb-4 text-xs font-medium uppercase tracking-[0.24em] text-black/45 dark:text-[#efe9df]/45">{group.title}</p><div className="divide-y divide-black/15 dark:divide-[#efe9df]/15">{group.items.map(([title, copy]) => { const itemKey = `${group.title}:${title}`; const isOpen = openResource === itemKey; return <div key={title} className="py-3 first:pt-0 last:pb-0"><button type="button" onClick={() => setOpenResource(isOpen ? null : itemKey)} className="w-full cursor-pointer text-left font-serif text-2xl font-normal leading-[0.95] tracking-[-0.05em] text-black transition hover:text-black/70 dark:text-[#efe9df] dark:hover:text-[#efe9df]/70 md:text-4xl" aria-expanded={isOpen}>{title}</button><AnimatePresence initial={false}>{isOpen && <motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ height: { duration: 0.3, ease: [0.76, 0, 0.24, 1] }, opacity: { duration: 0.18 } }} className="overflow-hidden"><ResourceContent content={copy} /></motion.div>}</AnimatePresence></div>; })}</div></div>;
}

function ContactResources() {
  const [openResource, setOpenResource] = useState(null);
  return <motion.aside key="contact-resources" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.22, ease: "easeOut" }} className="space-y-12">{contactResourceGroups.map((group) => <ContactAccordionGroup key={group.title} group={group} openResource={openResource} setOpenResource={setOpenResource} />)}</motion.aside>;
}

const defaultTimingOptions = ["As soon as possible", "Within 1 month", "1-3 months", "Flexible"];

function InquiryDropdown({ value, onChange, options = defaultTimingOptions }) {
  const [open, setOpen] = useState(false);
  const wrapperRef = useRef(null);
  useEffect(() => {
    if (!open) return undefined;
    const handlePointerDown = (event) => { if (!wrapperRef.current?.contains(event.target)) setOpen(false); };
    window.addEventListener("pointerdown", handlePointerDown);
    return () => window.removeEventListener("pointerdown", handlePointerDown);
  }, [open]);
  return <div ref={wrapperRef} className="relative"><button type="button" style={boldUiTextStyle} onClick={() => setOpen((current) => !current)} className="force-bold-ui flex h-11 w-full cursor-pointer items-center justify-between rounded-[0.7rem] border border-black/15 bg-black/[0.055] px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.055em] text-black/82 outline-none backdrop-blur-md transition-colors duration-[300ms] hover:bg-black/[0.085] hover:text-black dark:border-[#efe9df]/15 dark:bg-[rgba(239,233,223,0.11)] dark:text-[#efe9df] dark:hover:bg-[rgba(239,233,223,0.16)] dark:hover:text-[#efe9df]"><span>{value || "Select an option"}</span><span className={`grid h-3 w-3 place-items-center transition-transform duration-[300ms] ${open ? "rotate-180" : "rotate-0"}`}><Icons name="chevron" size={11} /></span></button><AnimatePresence>{open && <motion.div initial={{ opacity: 0, y: 6 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 6 }} transition={{ duration: 0.3, ease: "easeOut" }} className="absolute left-0 top-[calc(100%+0.45rem)] z-50 flex w-full min-w-56 flex-col gap-1.5 rounded-[1rem] border border-black/10 bg-[#efe9df]/95 p-2 shadow-[0_18px_60px_rgba(0,0,0,0.12)] backdrop-blur-2xl dark:border-[#efe9df]/15 dark:bg-[#1d1b18]/95">{options.map((option) => { const isActive = value === option; return <button type="button" key={option} style={boldUiTextStyle} onClick={() => { onChange(option); setOpen(false); }} className={`force-bold-ui flex w-full cursor-pointer items-center justify-between rounded-[0.7rem] border px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.055em] transition-colors duration-[300ms] ${isActive ? "border-black/40 bg-black text-[#efe9df] dark:border-[#efe9df]/35 dark:bg-[#efe9df] dark:text-black" : "border-black/10 bg-black/[0.055] text-black/82 hover:bg-black/[0.085] hover:text-black dark:border-[#efe9df]/10 dark:bg-[rgba(239,233,223,0.09)] dark:text-[#efe9df] dark:hover:bg-[rgba(239,233,223,0.15)] dark:hover:text-[#efe9df]"}`}><span>{option}</span>{isActive && <span><Icons name="check" size={12} /></span>}</button>; })}</motion.div>}</AnimatePresence><input tabIndex={-1} aria-hidden="true" value={value} required onChange={() => {}} className="pointer-events-none absolute h-px w-px opacity-0" /></div>;
}

function ContactCurtain({ open, onClose }) {
  const [selectedTypes, setSelectedTypes] = useState([]);
  const [timeline, setTimeline] = useState("");
  const [activePanel, setActivePanel] = useState("form");
  const [formValues, setFormValues] = useState({ firstName: "", lastName: "", email: "", project: "", budget: "" });
  useEffect(() => {
    if (!open) return undefined;
    document.documentElement.classList.add("form-open");
    const onKeyDown = (event) => { if (event.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKeyDown);
    return () => {
      document.documentElement.classList.remove("form-open");
      window.removeEventListener("keydown", onKeyDown);
    };
  }, [open, onClose]);
  useEffect(() => { if (open) setActivePanel("form"); }, [open]);
  const toggleType = (type) => setSelectedTypes((current) => (current.includes(type) ? current.filter((item) => item !== type) : [...current, type]));
  const updateFormValue = (field) => (event) => setFormValues((current) => ({ ...current, [field]: event.target.value }));
  const handlePanelSelect = (panel) => (event) => {
    if (event.detail > 0) event.currentTarget.blur();
    setActivePanel(panel);
  };
  return <AnimatePresence>{open && <motion.div initial={{ clipPath: "inset(0 0 100% 0)" }} animate={{ clipPath: "inset(0 0 0% 0)" }} exit={{ clipPath: "inset(0 0 100% 0)" }} transition={{ duration: 0.65, ease: [0.76, 0, 0.24, 1] }} style={{ willChange: "clip-path" }} className="inquiry-curtain contact-curtain fixed inset-0 z-50 overflow-y-auto bg-[#efe9df] px-5 py-6 text-black transition-colors duration-[300ms] dark:bg-[#11100e] dark:text-[#efe9df] md:px-6" role="dialog" aria-modal="true" aria-label="Contact information">
    <button type="button" onClick={onClose} className="group nav-glass nav-glass-dark !fixed right-4 top-4 z-[60] grid h-14 w-14 cursor-pointer place-items-center rounded-[1.35rem] max-[640px]:right-2 md:right-6" aria-label="Close contact">
      <span className="tone-transition nav-glass-content text-[#efe9df] drop-shadow-[0_1px_8px_rgba(0,0,0,.42)]"><Icons name="close" size={18} /></span>
    </button>
    <div className="relative isolate mx-auto grid min-h-full max-w-7xl gap-8 py-16 md:grid-cols-12 md:items-start md:py-20">
      <section className="relative z-10 md:col-span-5">
        <p className="mb-8 text-xs font-semibold uppercase tracking-[0.22em] text-black/45 dark:text-[#efe9df]/45">Contact</p>
        <h2 className="max-w-4xl font-serif text-5xl leading-[0.9] tracking-[-0.06em] md:text-7xl">If you have a project in mind, send your inquiry.</h2>
        <p className="mt-8 max-w-md text-sm leading-7 text-black/60 dark:text-[#efe9df]/60">Start with the form when you are ready. Open client resources if you want to clarify scope, deliverables and review expectations first.</p>
        <div className="mt-10 grid max-w-md gap-3">
          <button type="button" onClick={handlePanelSelect("form")} className={`group cursor-pointer rounded-[1.05rem] border px-5 py-3 text-left transition ${activePanel === "form" ? "border-black/25 bg-black text-[#efe9df] dark:border-[#efe9df]/25 dark:bg-[#efe9df] dark:text-black" : "border-black/15 bg-black/[0.035] text-black hover:bg-black/[0.06] dark:border-[#efe9df]/15 dark:bg-[rgba(239,233,223,0.06)] dark:text-[#efe9df] dark:hover:bg-[rgba(239,233,223,0.10)]"}`}>
            <span className="block text-xs font-semibold uppercase tracking-[0.08em]">Send an Inquiry</span>
          </button>
          <button type="button" onClick={handlePanelSelect("resources")} className={`group cursor-pointer rounded-[1.05rem] border px-5 py-3 text-left transition ${activePanel === "resources" ? "border-black/25 bg-black text-[#efe9df] dark:border-[#efe9df]/25 dark:bg-[#efe9df] dark:text-black" : "border-black/15 bg-black/[0.035] text-black hover:bg-black/[0.06] dark:border-[#efe9df]/15 dark:bg-[rgba(239,233,223,0.06)] dark:text-[#efe9df] dark:hover:bg-[rgba(239,233,223,0.10)]"}`}>
            <span className="block text-xs font-semibold uppercase tracking-[0.08em]">Review Client Resources</span>
          </button>
        </div>
      </section>
      <section className="relative z-20 overflow-visible md:col-span-7">
        <div className="relative pb-2">
          <motion.form initial={false} animate={{ opacity: activePanel === "form" ? 1 : 0 }} transition={{ duration: 0.34, ease: "easeOut" }} aria-hidden={activePanel !== "form"} className={`${activePanel === "form" ? "grid" : "hidden"} gap-7 ${activePanel === "form" ? "pointer-events-auto" : "pointer-events-none"}`} onSubmit={(event) => event.preventDefault()}>
            <div className="grid gap-4 overflow-visible md:h-[72px] md:grid-cols-2 md:items-start"><label className="contact-field text-sm font-semibold"><span className="contact-field-label">* First Name</span><input value={formValues.firstName} onChange={updateFormValue("firstName")} className="h-11 rounded-[0.8rem] border border-black/10 bg-black/[0.045] px-4 outline-none transition focus:border-black/35 dark:border-[#efe9df]/10 dark:bg-[rgba(239,233,223,0.075)] dark:focus:border-[#efe9df]/35" required /></label><label className="contact-field text-sm font-semibold"><span className="contact-field-label">* Last Name</span><input value={formValues.lastName} onChange={updateFormValue("lastName")} className="h-11 rounded-[0.8rem] border border-black/10 bg-black/[0.045] px-4 outline-none transition focus:border-black/35 dark:border-[#efe9df]/10 dark:bg-[rgba(239,233,223,0.075)] dark:focus:border-[#efe9df]/35" required /></label></div>
            <label className="grid gap-2 text-sm font-semibold">* Email <input type="email" value={formValues.email} onChange={updateFormValue("email")} className="h-11 rounded-[0.8rem] border border-black/10 bg-black/[0.045] px-4 outline-none transition focus:border-black/35 dark:border-[#efe9df]/10 dark:bg-[rgba(239,233,223,0.075)] dark:focus:border-[#efe9df]/35" required /></label>
            <div><p className="mb-3 text-sm font-semibold leading-[1.15]">* Project Type <span className="text-black/45 dark:text-[#efe9df]/45">(select all that apply)</span></p><div className="flex flex-wrap gap-2">{contactProjectTypes.map((type) => { const isSelected = selectedTypes.includes(type); return <button type="button" key={type} onClick={() => toggleType(type)} style={boldUiTextStyle} className={`${filterControlBaseClass} ${isSelected ? "border-black/40 bg-black text-[#efe9df] dark:border-[#efe9df]/35 dark:bg-[#efe9df] dark:text-black" : "border-black/15 bg-black/[0.055] text-black/82 hover:bg-black/[0.085] hover:text-black dark:border-[#efe9df]/15 dark:bg-[rgba(239,233,223,0.07)] dark:text-[#efe9df]/82 dark:hover:bg-[rgba(239,233,223,0.12)] dark:hover:text-[#efe9df]"}`}>{type}</button>; })}</div></div>
            <label className="grid gap-2 text-sm font-semibold">* Describe your project <textarea value={formValues.project} onChange={updateFormValue("project")} className="min-h-36 resize-y rounded-[0.8rem] border border-black/10 bg-black/[0.045] p-4 outline-none transition focus:border-black/35 dark:border-[#efe9df]/10 dark:bg-[rgba(239,233,223,0.075)] dark:focus:border-[#efe9df]/35" required /></label>
            <div className="grid items-start gap-4 md:grid-cols-[1fr_0.8fr]"><label className="grid grid-rows-[20px_44px] gap-2 text-sm font-semibold"><span className="block leading-5">* Desired Timeline</span><InquiryDropdown value={timeline} onChange={setTimeline} /></label><label className="grid grid-rows-[20px_44px] gap-2 text-sm font-semibold"><span className="block whitespace-nowrap text-xs leading-5 md:text-sm">Budget <span className="text-black/45 dark:text-[#efe9df]/45">(if looking for a quote, leave blank)</span></span><input type="text" value={formValues.budget} onChange={updateFormValue("budget")} className="h-11 rounded-[0.8rem] border border-black/10 bg-black/[0.045] px-4 outline-none transition placeholder:text-black/35 focus:border-black/35 dark:border-[#efe9df]/10 dark:bg-[rgba(239,233,223,0.075)] dark:text-[#efe9df]/82 dark:placeholder:text-[#efe9df]/35 dark:focus:border-[#efe9df]/35" /></label></div>
            <button type="submit" style={boldUiTextStyle} className="force-bold-ui mt-2 w-fit cursor-pointer rounded-[1rem] bg-black px-8 py-5 text-sm font-bold uppercase tracking-[0.08em] text-[#efe9df] transition hover:bg-black/80 dark:bg-[#efe9df] dark:text-black dark:hover:bg-[#efe9df]/80">Submit</button>
          </motion.form>
          <motion.div initial={false} animate={{ opacity: activePanel === "resources" ? 1 : 0 }} transition={{ duration: 0.34, ease: "easeOut" }} aria-hidden={activePanel !== "resources"} className={`${activePanel === "resources" ? "block" : "hidden"} ${activePanel === "resources" ? "pointer-events-auto" : "pointer-events-none"}`}>
            <ContactResources />
          </motion.div>
        </div>
      </section>
    </div>
  </motion.div>}</AnimatePresence>;
}

function PortfolioContactCurtain({ open, onClose }) {
  useEffect(() => {
    if (!open) return undefined;
    document.documentElement.classList.add("form-open");
    const onKeyDown = (event) => { if (event.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKeyDown);
    return () => {
      document.documentElement.classList.remove("form-open");
      window.removeEventListener("keydown", onKeyDown);
    };
  }, [open, onClose]);
  return <AnimatePresence>{open && <motion.div initial={{ clipPath: "inset(0 0 100% 0)" }} animate={{ clipPath: "inset(0 0 0% 0)" }} exit={{ clipPath: "inset(0 0 100% 0)" }} transition={{ duration: 0.65, ease: [0.76, 0, 0.24, 1] }} style={{ willChange: "clip-path" }} className="contact-curtain fixed inset-0 z-50 overflow-y-auto bg-[#efe9df] px-5 py-6 text-black transition-colors duration-[300ms] dark:bg-[#11100e] dark:text-[#efe9df] md:px-6" role="dialog" aria-modal="true" aria-label="Portfolio contact">
    <button type="button" onClick={onClose} className="group nav-glass nav-glass-dark !fixed right-4 top-4 z-[60] grid h-14 w-14 cursor-pointer place-items-center rounded-[1.35rem] max-[640px]:right-2 md:right-6" aria-label="Close contact">
      <span className="tone-transition nav-glass-content text-[#efe9df] drop-shadow-[0_1px_8px_rgba(0,0,0,.42)]"><Icons name="close" size={18} /></span>
    </button>
    <div className="mx-auto grid min-h-full max-w-7xl content-center gap-10 py-16 md:grid-cols-12 md:items-center md:py-20">
      <div className="md:col-span-5">
        <div className="overflow-hidden border border-black/10 bg-black/10 shadow-[0_22px_90px_rgba(0,0,0,.12)] dark:border-[#efe9df]/10 dark:bg-[#efe9df]/10">
          <LoadAwareImage src={media.portrait} alt="Afonso Goncalves portrait placeholder" eager className="aspect-[4/5] w-full object-cover" />
        </div>
      </div>
      <section className="md:col-span-6 md:col-start-7">
        <p className="mb-8 text-xs font-semibold uppercase tracking-[0.22em] text-black/45 dark:text-[#efe9df]/45">Portfolio Contact</p>
        <h2 className="font-serif text-[4rem] leading-[0.82] tracking-[-0.065em] md:text-[8vw]">Afonso Gonçalves</h2>
        <div className="mt-10 grid gap-4 border-t border-black/12 pt-8 text-sm leading-7 text-black/65 dark:border-[#efe9df]/12 dark:text-[#efe9df]/65">
          <a href="mailto:hello@afoviz.com" className="w-fit cursor-pointer transition hover:text-black dark:hover:text-[#efe9df]">hello@afoviz.com</a>
          <a href="tel:+351961490792" className="w-fit cursor-pointer transition hover:text-black dark:hover:text-[#efe9df]">+351 961 490 792</a>
        </div>
      </section>
    </div>
  </motion.div>}</AnimatePresence>;
}

function HomePage({ onContact, onLogoClick, onSelectProject, onSelectPost, forceNavVisible = false, onForceNavVisibleConsumed, isDark, onToggleTheme }) {
  return <main className={`min-h-screen antialiased transition-colors duration-[300ms] ${isDark ? "bg-[#11100e] text-[#efe9df]" : "bg-[#efe9df] text-black"}`} style={{ fontFamily: fontStack }}><Navbar onContact={onContact} onLogoClick={onLogoClick} forceVisible={forceNavVisible} onForceVisibleConsumed={onForceNavVisibleConsumed} isDark={isDark} onToggleTheme={onToggleTheme} showResume={false} /><Hero /><PracticeGallery onSelectProject={onSelectProject} isDark={isDark} /><Intro /><Services /><Blog onSelectPost={onSelectPost} /><CleanFooter /></main>;
}

function PortfolioHomePage({ onContact, onResume, onLogoClick, onSelectProject, forceNavVisible = false, onForceNavVisibleConsumed, isDark, onToggleTheme }) {
  return <main className={`min-h-screen antialiased transition-colors duration-[300ms] ${isDark ? "bg-[#11100e] text-[#efe9df]" : "bg-[#efe9df] text-black"}`} style={{ fontFamily: fontStack }}><Navbar onContact={onContact} onResume={onResume} onLogoClick={onLogoClick} forceVisible={forceNavVisible} onForceVisibleConsumed={onForceNavVisibleConsumed} isDark={isDark} onToggleTheme={onToggleTheme} navItems={portfolioNavItems} /><PortfolioHero /><PortfolioGallery onSelectProject={onSelectProject} isDark={isDark} /><PortfolioAboutStub /><CleanFooter /></main>;
}

function PersonalHero() {
  return <section id="home" data-nav-tone="light" className="relative flex min-h-screen items-end overflow-hidden bg-[#efe9df] px-5 pb-10 text-[#efe9df] transition-colors duration-[300ms] dark:bg-[#11100e] md:px-6"><LoadAwareImage src={media.about2} alt="" eager className="absolute inset-0 h-full w-full object-cover" /><div className="absolute inset-0 bg-gradient-to-t from-black/72 via-black/28 to-black/12" /><div className="relative z-10 grid w-full gap-8 md:grid-cols-12 md:items-end"><div className="md:col-span-7"><LineText className="mb-6 text-xs font-semibold uppercase tracking-[0.24em] text-[#efe9df]/58">Personal</LineText><LineText as="h1" className="max-w-5xl font-serif text-[4.5rem] leading-[0.82] tracking-[-0.07em] md:text-[9vw]">A private corner for the things I actually keep.</LineText></div><div className="md:col-span-4 md:col-start-9"><LineText className="max-w-sm text-sm leading-7 text-[#efe9df]/76" delay={0.08}>Photos, songs, notes, loose scraps. No funnel. No algorithm. Just a small room with a pulse.</LineText></div></div></section>;
}

function PersonalPhotosSection() {
  return <section id="photos" data-nav-tone="dark" className="scroll-mt-24 px-5 py-12 transition-colors duration-[300ms] md:px-6 md:py-16"><Reveal className="mb-8 grid gap-5 md:grid-cols-12 md:items-end"><div className="md:col-span-4"><LineText className="text-xs font-semibold uppercase tracking-[0.24em] text-black/45 dark:text-[#efe9df]/45">Photography</LineText></div><div className="md:col-span-7"><LineText as="h2" className="font-serif text-5xl leading-[0.9] tracking-[-0.055em] md:text-7xl">Pictures that survived the delete pile.</LineText></div></Reveal><div className="grid gap-4 md:grid-cols-3">{personalPhotos.map((photo, index) => <Reveal key={photo.title} delay={index * 0.04} className="group overflow-hidden rounded-[1.15rem] border border-black/10 bg-black/[0.035] transition-colors duration-[300ms] dark:border-[#efe9df]/10 dark:bg-[#efe9df]/[0.045]"><div className="overflow-hidden bg-black"><LoadAwareImage src={photo.image} alt="" className="aspect-[4/5] w-full object-cover opacity-90 transition duration-700 group-hover:scale-[1.04] group-hover:opacity-100" /></div><div className="p-5"><p className="mb-4 text-[10px] font-semibold uppercase tracking-[0.18em] text-black/42 dark:text-[#efe9df]/42">{photo.place} / {photo.date}</p><LineText as="h3" className="font-serif text-3xl leading-none tracking-[-0.045em]">{photo.title}</LineText><p className="mt-4 text-sm leading-7 text-black/62 dark:text-[#efe9df]/62">{photo.note}</p></div></Reveal>)}</div></section>;
}

function PersonalListeningSection() {
  return <section id="listening" data-nav-tone="dark" className="scroll-mt-24 px-5 py-12 transition-colors duration-[300ms] md:px-6 md:py-16"><Reveal className="mb-8 grid gap-5 md:grid-cols-12 md:items-end"><div className="md:col-span-4"><LineText className="text-xs font-semibold uppercase tracking-[0.24em] text-black/45 dark:text-[#efe9df]/45">Listening</LineText></div><div className="md:col-span-7"><LineText as="h2" className="font-serif text-5xl leading-[0.9] tracking-[-0.055em] md:text-7xl">The tracks that will not leave me alone.</LineText></div></Reveal><div className="grid gap-4 md:grid-cols-2">{personalSongs.map((song, index) => <Reveal key={song.title} delay={index * 0.04} className="nav-glass nav-glass-dark relative overflow-hidden rounded-[1.35rem] p-3 text-[#efe9df]"><div className="nav-glass-content grid gap-4 sm:grid-cols-[10rem_minmax(0,1fr)]"><LoadAwareImage src={song.image} alt="" className="aspect-square w-full rounded-[1rem] object-cover" /><div className="flex min-h-[10rem] flex-col p-2"><p className="text-[10px] font-semibold uppercase tracking-[0.18em] text-[#efe9df]/45">{song.date}</p><h3 className="mt-4 font-serif text-4xl leading-[0.92] tracking-[-0.055em]">{song.title}</h3><p className="mt-2 text-sm text-[#efe9df]/58">{song.artist}</p><p className="mt-5 text-sm leading-7 text-[#efe9df]/68">{song.note}</p><a href={song.link} target="_blank" rel="noreferrer" className="mt-auto inline-flex w-fit cursor-pointer items-center gap-2 pt-6 text-xs font-semibold uppercase tracking-[0.12em] text-[#efe9df]/70 transition hover:text-[#efe9df]">Open track <Icons name="arrow" size={14} /></a></div></div></Reveal>)}</div></section>;
}

function PersonalNotesSection() {
  return <section id="notes" data-nav-tone="dark" className="scroll-mt-24 px-5 py-12 transition-colors duration-[300ms] md:px-6 md:py-16"><Reveal className="mb-8 grid gap-5 md:grid-cols-12 md:items-end"><div className="md:col-span-4"><LineText className="text-xs font-semibold uppercase tracking-[0.24em] text-black/45 dark:text-[#efe9df]/45">Notes</LineText></div><div className="md:col-span-7"><LineText as="h2" className="font-serif text-5xl leading-[0.9] tracking-[-0.055em] md:text-7xl">Thoughts before they learn manners.</LineText></div></Reveal><div className="divide-y divide-black/14 border-y border-black/14 dark:divide-[#efe9df]/14 dark:border-[#efe9df]/14">{personalNotes.map((note, index) => <Reveal key={note.title} delay={index * 0.035} className="grid gap-5 py-6 md:grid-cols-12 md:items-start"><p className="text-[10px] font-semibold uppercase tracking-[0.22em] text-black/40 dark:text-[#efe9df]/40 md:col-span-3">{note.date}</p><div className="md:col-span-5"><LineText as="h3" className="font-serif text-4xl leading-none tracking-[-0.05em]">{note.title}</LineText></div><p className="text-sm leading-7 text-black/62 dark:text-[#efe9df]/62 md:col-span-4">{note.copy}</p></Reveal>)}</div></section>;
}

function PersonalFeedIntro() {
  return null;
}

function PersonalPostIcon({ type }) {
  const iconName = type === "photos" ? "photo" : type === "song" ? "music" : "note";
  const label = type === "photos" ? "Photo post" : type === "song" ? "Song post" : "Note post";
  return <span className="absolute left-5 top-5 z-20 grid h-10 w-10 place-items-center rounded-full border border-black/35 bg-[#fffaf1]/82 text-black/68 shadow-[0_10px_30px_rgba(0,0,0,.08)] backdrop-blur-md transition-colors duration-[300ms] dark:border-[#efe9df]/12 dark:bg-black/58 dark:text-[#efe9df]/68" aria-label={label}><Icons name={iconName} size={17} /></span>;
}

function PersonalFeedPost({ post, index, onExpand }) {
  const alternatingToneClass = index % 2 === 0 ? "bg-black/[0.055] dark:bg-[#efe9df]/[0.06]" : "bg-[#efe9df]/46 dark:bg-[#efe9df]/[0.035]";
  const baseClass = `relative overflow-hidden rounded-[1.35rem] border border-black/10 p-4 shadow-[0_18px_70px_rgba(0,0,0,.055)] transition-colors duration-[300ms] dark:border-[#efe9df]/10 md:p-5 ${alternatingToneClass}`;
  const bodyGridClass = "grid gap-4 pt-5 md:grid-cols-[minmax(0,.72fr)_minmax(0,1fr)] md:items-start";
  const titleClass = "font-serif text-4xl leading-[0.94] tracking-[-0.055em] md:text-5xl";
  const copyClass = "max-w-xl text-sm leading-7 text-black/64 dark:text-[#efe9df]/64";
  if (post.type === "song") {
    return <Reveal delay={index * 0.035} className={baseClass}><PersonalPostIcon type={post.type} /><article className="grid gap-5 pt-14 md:grid-cols-[11rem_minmax(0,1fr)] md:items-center"><a href={post.link} target="_blank" rel="noreferrer" className="personal-media block aspect-square rounded-[1rem]"><LoadAwareImage src={post.image} alt="" className="object-cover object-center opacity-90" /></a><div className="flex flex-col md:pr-4"><LineText as="h2" className={titleClass}>{post.title}</LineText><p className="mt-2 text-sm text-black/50 dark:text-[#efe9df]/50">{post.artist}</p><p className="mt-5 max-w-2xl text-sm leading-7 text-black/64 dark:text-[#efe9df]/64">{post.copy}</p><a href={post.link} target="_blank" rel="noreferrer" className="mt-7 inline-flex w-fit cursor-pointer items-center gap-2 text-xs font-semibold uppercase tracking-[0.12em] text-black/52 transition-colors duration-[300ms] hover:text-black dark:text-[#efe9df]/52 dark:hover:text-[#efe9df]">Open track <Icons name="arrow" size={14} /></a></div></article></Reveal>;
  }
  if (post.type === "photos") {
    return <Reveal delay={index * 0.035} className={baseClass}><PersonalPostIcon type={post.type} /><article className="pt-14"><div className={`grid gap-3 ${post.images.length > 1 ? "md:grid-cols-2" : ""}`}>{post.images.map((image, imageIndex) => { const frameClass = post.images.length > 1 && imageIndex === 0 ? "aspect-[4/5]" : "aspect-[4/3]"; return <button type="button" key={`${post.id}-${image}`} onClick={() => onExpand?.(image)} className={`personal-media block cursor-pointer rounded-[1rem] text-left ${frameClass}`} aria-label={`Expand ${post.title} image ${imageIndex + 1}`}><LoadAwareImage src={image} alt="" className="object-cover object-center opacity-90" /></button>; })}</div><div className={bodyGridClass}><LineText as="h2" className={titleClass}>{post.title}</LineText><p className={copyClass}>{post.copy}</p></div></article></Reveal>;
  }
  return <Reveal delay={index * 0.035} className={`${baseClass} px-5 py-7 md:px-8 md:py-9`}><PersonalPostIcon type={post.type} /><article className="grid gap-5 pt-14 md:grid-cols-[minmax(0,.72fr)_minmax(0,1fr)] md:items-start"><LineText as="h2" className="font-serif text-5xl leading-[0.92] tracking-[-0.06em] md:text-6xl">{post.title}</LineText><p className="max-w-2xl text-base leading-8 text-black/66 dark:text-[#efe9df]/66">{post.copy}</p></article></Reveal>;
}

function PersonalFeed({ onExpand }) {
  return <section id="feed" data-nav-tone="dark" className="scroll-mt-24 bg-[#d8d0c4] px-5 pb-20 pt-32 transition-colors duration-[300ms] dark:bg-[#0f0e0d] md:px-6 md:pb-24 md:pt-40"><div className="mx-auto max-w-5xl"><div className="grid gap-5">{personalFeedPosts.map((post, index) => <PersonalFeedPost key={post.id} post={post} index={index} onExpand={onExpand} />)}</div></div></section>;
}

function PersonalHomePage({ onLogoClick, forceNavVisible = false, onForceNavVisibleConsumed, isDark, onToggleTheme }) {
  const [lightboxImage, setLightboxImage] = useState(null);
  const [holdNavHidden, setHoldNavHidden] = useState(false);
  useEffect(() => {
    if (!holdNavHidden) return undefined;
    let lastY = window.scrollY;
    const releaseOnUpwardScroll = () => {
      const y = window.scrollY;
      if (y < 16 || y < lastY - 8) setHoldNavHidden(false);
      lastY = y;
    };
    window.addEventListener("scroll", releaseOnUpwardScroll, { passive: true });
    return () => window.removeEventListener("scroll", releaseOnUpwardScroll);
  }, [holdNavHidden]);
  const closeLightbox = () => {
    const lockedScrollY = Math.abs(parseFloat(document.body.style.top || "0")) || 0;
    const shouldHoldNav = lockedScrollY > 80 || window.scrollY > 80;
    setLightboxImage(null);
    if (shouldHoldNav) setHoldNavHidden(true);
  };
  return <main className={`personal-site lightbox-page-shell ${lightboxImage ? "is-lightbox-open" : ""} min-h-screen antialiased transition-colors duration-[300ms] ${isDark ? "dark bg-[#0f0e0d] text-[#efe9df]" : "bg-[#d8d0c4] text-black"}`} style={{ fontFamily: fontStack }}><Navbar onLogoClick={onLogoClick} forceVisible={forceNavVisible} forceHidden={Boolean(lightboxImage) || holdNavHidden} onForceVisibleConsumed={onForceNavVisibleConsumed} isDark={isDark} onToggleTheme={onToggleTheme} navItems={personalNavItems} showContact={false} showResume={false} showMenu={false} showSocial={false} /><PersonalFeedIntro /><PersonalFeed onExpand={setLightboxImage} /><CleanFooter /><ImageLightbox src={lightboxImage} alt="Personal image" onClose={closeLightbox} /></main>;
}

function App() {
  const [isDark, setIsDark] = useState(false);
  const [sitePath, setSitePath] = useState(() => normalizeSitePath(window.location.pathname));
  const [pageLoaded, setPageLoaded] = useState(false);
  const [contactOpen, setContactOpen] = useState(false);
  const [selectedProject, setSelectedProject] = useState(null);
  const [selectedJournalPost, setSelectedJournalPost] = useState(() => getJournalPostFromPath(window.location.pathname));
  const [resumeOpen, setResumeOpen] = useState(false);
  const [scrollTarget, setScrollTarget] = useState(null);
  const [forceNavVisible, setForceNavVisible] = useState(false);
  const [routeVeilVisible, setRouteVeilVisible] = useState(false);
  const routeVeilTimers = useRef([]);
  const themeSwitchTimerRef = useRef(0);
  useEffect(() => {
    const handlePopState = () => {
      setSitePath(normalizeSitePath(window.location.pathname));
      setSelectedJournalPost(getJournalPostFromPath(window.location.pathname));
    };
    window.addEventListener("popstate", handlePopState);
    return () => window.removeEventListener("popstate", handlePopState);
  }, []);
  useEffect(() => {
    if (sitePath === "/visualization" || sitePath === "/portfolio") return;
    setContactOpen(false);
    setSelectedProject(null);
    setSelectedJournalPost(null);
    setResumeOpen(false);
    setScrollTarget(null);
    setForceNavVisible(false);
  }, [sitePath]);
  useEffect(() => {
    let fallbackTimer = 0;
    let revealTimer = 0;
    const reveal = () => {
      window.clearTimeout(fallbackTimer);
      revealTimer = window.setTimeout(() => setPageLoaded(true), 180);
    };
    if (document.readyState === "complete") reveal();
    else {
      window.addEventListener("load", reveal, { once: true });
      fallbackTimer = window.setTimeout(reveal, 1800);
    }
    return () => {
      window.clearTimeout(fallbackTimer);
      window.clearTimeout(revealTimer);
      window.removeEventListener("load", reveal);
    };
  }, []);
  useEffect(() => () => routeVeilTimers.current.forEach((timer) => window.clearTimeout(timer)), []);
  useEffect(() => () => window.clearTimeout(themeSwitchTimerRef.current), []);
  useEffect(() => {
    if ("scrollRestoration" in window.history) window.history.scrollRestoration = "manual";
    window.scrollTo({ top: 0, behavior: "auto" });
  }, []);
  useLayoutEffect(() => {
    document.documentElement.classList.toggle("dark", isDark);
    document.documentElement.style.colorScheme = isDark ? "dark" : "light";
  }, [isDark]);
  useEffect(() => {
    let favicon = document.querySelector('link[data-dynamic-favicon]');
    if (!favicon) {
      favicon = document.createElement("link");
      favicon.rel = "icon";
      favicon.type = "image/png";
      favicon.dataset.dynamicFavicon = "true";
      document.head.appendChild(favicon);
    }
    favicon.type = "image/png";
    favicon.href = `${isDark ? media.faviconDark : media.faviconLight}?v=20260603-${isDark ? "dark" : "light"}`;
  }, [isDark]);
  useEffect(() => {
    const metadata = metadataForPath(sitePath);
    const canonicalUrl = canonicalUrlForPath(sitePath);
    document.title = metadata.title;
    updateMetaTag('meta[name="description"]', "name", "description", metadata.description);
    updateMetaTag('meta[property="og:title"]', "property", "og:title", metadata.title);
    updateMetaTag('meta[property="og:description"]', "property", "og:description", metadata.description);
    updateMetaTag('meta[property="og:url"]', "property", "og:url", canonicalUrl);
    updateMetaTag('meta[name="twitter:title"]', "name", "twitter:title", metadata.title);
    updateMetaTag('meta[name="twitter:description"]', "name", "twitter:description", metadata.description);
    updateCanonical(canonicalUrl);
  }, [sitePath]);
  useEffect(() => {
    if (selectedProject || selectedJournalPost || resumeOpen) {
      window.scrollTo({ top: 0, behavior: "auto" });
      return undefined;
    }
    if (scrollTarget) {
      const frame = requestAnimationFrame(() => {
        document.querySelector(`#${scrollTarget}`)?.scrollIntoView({ behavior: "auto", block: "start" });
        setForceNavVisible(true);
        setScrollTarget(null);
      });
      return () => cancelAnimationFrame(frame);
    }
    return undefined;
  }, [selectedProject, selectedJournalPost, resumeOpen, scrollTarget]);
  useEffect(() => {
    preloadImages(getInitialPracticePreloadSources());
  }, []);
  const runVeiledTransition = (action, preloadSources = []) => {
    routeVeilTimers.current.forEach((timer) => window.clearTimeout(timer));
    setRouteVeilVisible(true);
    const actionTimer = window.setTimeout(async () => {
      await preloadImages(preloadSources);
      action();
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          const revealTimer = window.setTimeout(() => setRouteVeilVisible(false), 120);
          routeVeilTimers.current = [revealTimer];
        });
      });
    }, 500);
    routeVeilTimers.current = [actionTimer];
  };
  const beginThemeSwitch = () => {
    window.clearTimeout(themeSwitchTimerRef.current);
    document.documentElement.classList.add("theme-switching");
    themeSwitchTimerRef.current = window.setTimeout(() => {
      document.documentElement.classList.remove("theme-switching");
    }, 360);
  };
  const toggleTheme = () => {
    beginThemeSwitch();
    flushSync(() => {
      setIsDark((current) => !current);
    });
  };
  const navigateToSite = (path) => {
    const normalizedPath = normalizeSitePath(path);
    const preloadSources = normalizedPath === "/visualization" ? [media.logo, media.hero, media.heroMobile] : normalizedPath === "/portfolio" ? [media.logo, portfolioHeroImages[0]] : normalizedPath === "/personal" ? [media.logo] : [media.logo];
    runVeiledTransition(() => {
      window.history.pushState({}, "", normalizedPath);
      setSitePath(normalizedPath);
      setContactOpen(false);
      setSelectedProject(null);
      setSelectedJournalPost(null);
      setResumeOpen(false);
      setScrollTarget(null);
      setForceNavVisible(false);
      window.scrollTo({ top: 0, behavior: "auto" });
    }, preloadSources);
  };
  const openProject = (project) => runVeiledTransition(() => {
    setSelectedJournalPost(null);
    setSelectedProject(project);
  }, getProjectPreloadSources(project));
  const openIndex = () => navigateToSite("/");
  const openResume = () => runVeiledTransition(() => setResumeOpen(true));
  const openJournalPost = (post) => runVeiledTransition(() => {
    window.history.pushState({}, "", `/visualization/journal/${post.slug}`);
    setSitePath("/visualization");
    setSelectedProject(null);
    setSelectedJournalPost(post);
    setResumeOpen(false);
    setScrollTarget(null);
    setForceNavVisible(false);
    window.scrollTo({ top: 0, behavior: "auto" });
  }, [media.logo, post.image]);
  const returnToJournal = () => runVeiledTransition(() => {
    window.history.pushState({}, "", "/visualization");
    setScrollTarget("blog");
    setSelectedJournalPost(null);
    setSelectedProject(null);
  }, [media.logo, media.hero, media.heroMobile]);
  const returnToWork = () => runVeiledTransition(() => {
    setScrollTarget("practice");
    setSelectedProject(null);
  }, [media.logo, media.hero, media.heroMobile]);
  const returnToPortfolioWork = () => runVeiledTransition(() => {
    setScrollTarget("portfolio-work");
    setSelectedProject(null);
  }, [media.logo, media.pineVillasMeco8Hq]);
  const returnHome = () => runVeiledTransition(() => setResumeOpen(false), sitePath === "/portfolio" ? [media.logo, portfolioHeroImages[0]] : [media.logo, media.hero, media.heroMobile]);
  const activeBranch = ecosystemBranches.find((branch) => branch.path === sitePath);
  const isVisualizationRoute = sitePath === "/visualization";
  const isPortfolioRoute = sitePath === "/portfolio";
  const isPersonalRoute = sitePath === "/personal";
  const page = sitePath === "/" ? <RootSelector key="root-selector" isDark={isDark} onToggleTheme={toggleTheme} onNavigate={navigateToSite} /> : isVisualizationRoute ? (selectedProject ? <ProjectPage key={selectedProject.id} project={selectedProject} onBack={returnToWork} onContact={() => setContactOpen(true)} isDark={isDark} onToggleTheme={toggleTheme} /> : selectedJournalPost ? <JournalArticlePage key={selectedJournalPost.slug} post={selectedJournalPost} onBack={returnToJournal} onContact={() => setContactOpen(true)} isDark={isDark} onToggleTheme={toggleTheme} /> : <HomePage key="visualization-home" onContact={() => setContactOpen(true)} onLogoClick={openIndex} onSelectProject={openProject} onSelectPost={openJournalPost} forceNavVisible={forceNavVisible} onForceNavVisibleConsumed={() => setForceNavVisible(false)} isDark={isDark} onToggleTheme={toggleTheme} />) : isPortfolioRoute ? (resumeOpen ? <ResumePage key="portfolio-resume" onBack={returnHome} isDark={isDark} onToggleTheme={toggleTheme} /> : selectedProject ? <ProjectPage key={`portfolio-${selectedProject.id}`} project={selectedProject} onBack={returnToPortfolioWork} onContact={() => setContactOpen(true)} isDark={isDark} onToggleTheme={toggleTheme} backLabel="Back to Portfolio" /> : <PortfolioHomePage key="portfolio-home" onContact={() => setContactOpen(true)} onResume={openResume} onLogoClick={openIndex} onSelectProject={openProject} forceNavVisible={forceNavVisible} onForceNavVisibleConsumed={() => setForceNavVisible(false)} isDark={isDark} onToggleTheme={toggleTheme} />) : isPersonalRoute ? <PersonalHomePage key="personal-home" onLogoClick={openIndex} forceNavVisible={forceNavVisible} onForceNavVisibleConsumed={() => setForceNavVisible(false)} isDark={isDark} onToggleTheme={toggleTheme} /> : <BranchPlaceholder key={activeBranch?.id || "branch-placeholder"} branch={activeBranch || ecosystemBranches[0]} isDark={isDark} onToggleTheme={toggleTheme} onNavigate={navigateToSite} />;
  return <div className={`theme-sync ${isDark ? "dark" : ""}`}><LiquidGlassDefs /><LiquidGlassRefraction />{isVisualizationRoute && <ScrollProgressIndicator />}<CustomCursor />{page}{isVisualizationRoute && <ContactCurtain open={contactOpen} onClose={() => setContactOpen(false)} />}{isPortfolioRoute && <PortfolioContactCurtain open={contactOpen} onClose={() => setContactOpen(false)} />}<PageLoadVeil hidden={pageLoaded && !routeVeilVisible} /></div>;
}


createRoot(document.getElementById("root")).render(<App />);


