{
 "site": "Kōzōwood Industries, Portugal (site by Büro)",
 "slug": "kozowood",
 "urls": [
  "https://kozowood.com/en/building-with-kozowood",
  "https://kozowood.com/en/why-wood",
  "https://kozowood.com/en/about",
  "https://kozowood.com/en/journal"
 ],
 "stack": {
  "framework": "Nuxt 2 full static (nuxt generate) on Vue 2.7.14, vue-router 3.6.5, Vuex 3.6.2, vue-meta 2.4.0, vue-i18n 8.28.2, Nuxt Image static provider, vue-screen breakpoints",
  "cms": "Sanity (GROQ in page chunks, cdn.sanity.io); hosting Vercel behind Cloudflare; video on DigitalOcean Spaces",
  "animation": [
   "GSAP 3.15.0 (bundled, no global)",
   "ScrollTrigger",
   "SplitText 3.15.0",
   "lottie-web 5.13.0 (SVG renderer)",
   "Pizzicato.js sound (Web Audio)"
  ],
  "scroll": "Lenis 1.0.11, default options, ticked by gsap.ticker, ScrollTrigger.update on scroll, stopped and started through an event emitter",
  "three_d": "three.js only for one why-wood section (GLB block, lazy chunk 9a6eb3c.js), gated by a GPU, WebGL2 and reduced-motion check; not rendered on fsit2",
  "fonts": [
   "PP Mori variable (/fonts/PPMori-Variable.ttf)",
   "Ogg 400 (/fonts/OggRegular.woff2)",
   "Ogg 500 (/fonts/OggMedium.woff2)"
  ],
  "evidence": [
   "probe globals: only __NUXT__; data-n-head=\"ssr\"; /_nuxt/static/1781253132/en/<route>/state.js and payload.js",
   "version strings: Vue 2.7.14, vue-router 3.6.5, vuex 3.6.2, vue-meta 2.4.0 in /_nuxt/cc3ed3d.js; GSAP 3.15.0, lottie 5.13.0, window.lenisVersion=1.0.11, vue-i18n 8.28.2 in /_nuxt/08c1294.js; SplitText 3.15.0 in /_nuxt/9e0dbb9.js",
   "live: page module 0 answered gsap.version 3.15.0 (steps/slowmo-*.meta.json eval slowmo); html class 'lenis lenis-smooth'",
   "headers: x-vercel-cache HIT, x-vercel-id <edge region> (pdx1 on the agent's read, iad1 on the verifier's), last-modified 2026-10-19 (the 1781253132 static version dates the build, 2026-06-12, not the last deploy), server cloudflare, cf-ray, speculation-rules /cdn-cgi/speculation",
   "CSP: cdn.sanity.io, *.apicdn.sanity.io, *.cdn.digitaloceanspaces.com, googletagmanager, analytics.ahrefs.com, hubspot, connect.facebook.net",
   "HTML scripts: analytics.ahrefs.com/analytics.js, analytics.madebyburo.com/umami.js, stats.withburo.com/script.js, assets.awwwards.com/assets/js/embed_ribbon.js, static.cloudflareinsights.com beacon, inline GTM-KGD4WFGL; live DOM (injected by GTM): gtag G-RQBB0MEHZW and AW-16846997892, Meta Pixel fbevents.js, six HubSpot EU1 scripts, Leadfeeder sc.lfeeder.com; the two Büro scripts and the Awwwards script are absent from the live DOM in all five probes",
   "@font-face in inline CSS: PPMori-Variable.ttf, OggRegular.woff2, OggMedium.woff2",
   "Nuxt default transition object in 08c1294.js: {name:'default', mode:'in-out', css:false, leave, enter}",
   "probe: 0 view-transition CSS rules, startViewTransition present but unused; no canvases; 4 [data-component=lottie] by eval (the probe's .lottie count is 2)",
   "verifier live module scan (shots/verify/meta.json eval stack): one GSAP core 3.15.0 (module 0), ScrollTrigger and SplitText 3.15.0, Vue 2.7.14, vue-router 3.6.5, Vuex 3.6.2, vue-meta 2.4.0, vue-i18n 8.28.2, lottie-web 5.13.0, window.lenisVersion 1.0.11; live Lenis options are the 1.0.11 defaults (lerp 0.1, smoothWheel true, smoothTouch false)"
  ]
 },
 "components": [
  {
   "name": "Page-to-page transition",
   "owner_words": "Everything moves and shifts as if you are never really leaving a page and going to another. It just continuously flows through content … it feels more like a continuous flow almost as if you're playing a 2D video game than flipping through a website.",
   "selectors": [
    "#__layout [data-slug]",
    "[data-component=secondary-header] .top-row",
    "[data-component=secondary-header] [data-component=background]",
    "[data-component=secondary-header] .letter-wrapper [data-component=letter]",
    "[data-component=secondary-header] .left-col",
    "[data-component=secondary-header] .description"
   ],
   "mechanism": "Nuxt 2 page <transition mode='in-out' :css='false'> with JS hooks: enter pins the old page (position fixed, top -scrollY, z-index 0), hides the new page (opacity 0), waits for the new page loader's 'loaded' promise (Lottie DOMLoaded etc.), then runs old.transitionLeave() (y -=100px, 1.5 s expo.inOut) and new.transitionEnter(done) (y 100vh to 0, 1.5 s expo.inOut, dispatches 'ready' after 64 ms); 'ready' starts the hero timeline and closes the menu; done() removes the old page. Phone (<=1023 px): crossfade, 1 s power4.out after 0.5 s, new page from y 20.",
   "libraries": [
    "Nuxt 2 / Vue 2.7 transition hooks",
    "GSAP 3.15 timelines",
    "ScrollTrigger.refresh",
    "Lenis stop/start",
    "event emitter",
    "per-page loader promises"
   ],
   "states": [
    "idle",
    "old pinned + new mounted at opacity 0 (t 0)",
    "both moving (t ~280 to ~1780 ms from the click)",
    "old removed, hero building (t ~1800 to ~6100 ms from the click)",
    "settled, Lenis unlocked"
   ],
   "timings": "measured real time from the click (menu link building to why-wood, fsit2, verifier's per-frame recorder in shots/verify/meta.json): rise and drift start about 280 ms (loaded); ready about 585 ms (hero and menu leave start; source says 64 ms after the rise, fsit2 took about 300 in both runs); panels 1.5 s expo.inOut; old page removed 1772 to 1829 ms; hero clip wipe 2 s power4.inOut from about 585 ms; background crop, letter, left column 2 to 2.5 s power4.inOut from about 2085 ms; description 2.5 s power4.out from about 3585 ms; menu colour switch about 1840 ms and Lenis restart about 2100 ms, both on real time; settled about 6.1 s",
   "shots": [
    "shots/slowmo-navmenu-sheet.png",
    "shots/smnav-a-02.png",
    "shots/slowmo-navinpage-sheet.png",
    "shots/slowmo-navback-sheet.png",
    "shots/timing-99-settled.png",
    "shots/navmenu-sheet.png",
    "shots/navback-sheet.png",
    "../_examples/kozowood-transition-sheet.png"
   ],
   "rebuild": "Two live stage layers; pin the outgoing one fixed at -scrollY and reset scroll; gate the incoming one on a loader (mounted, loaded, ready, destroyed) fed by its components' asset promises; run leave (y -100px) and enter (y 100vh to 0) together for 1.5 s expo.inOut (cubic-bezier(0.87,0,0.13,1)); mark ready 64 ms in so the hero builds while rising (clip-path wipe, background 1.4 to 1.1 then crop, glyph from 1.4, text from -5 %, one slower last element); clear styles, unmount, refresh triggers, restart scroller. GSAP or Motion; Vue <Transition mode='in-out'>, React AnimatePresence, or Barba/Swup for multi-page; View Transitions only if snapshots of the old page are acceptable. Add a reduced-motion path and a reverse direction for back.",
   "confidence": "high"
  },
  {
   "name": "Menu sheet",
   "owner_words": "(not named by the owner; part of the flow he describes) Everything moves and shifts as if you are never really leaving a page and going to another. It just continuously flows through content … it feels more like a continuous flow almost as if you're playing a 2D video game than flipping through a website.",
   "selectors": [
    "[data-component=menu]",
    "[data-component=menu] .bg",
    "[data-component=menu] .close-bg",
    "button[data-component=burger]",
    "[data-component=menu-items] a.link",
    "[data-component=menu] .menu-container .left",
    "[data-component=menu] .menu-container .right",
    "[data-component=section-label].section-btn"
   ],
   "mechanism": "One paused GSAP timeline played forward to open and reversed at timeScale 2 to close; a second 'transitionAnimation' used when a link is chosen on desktop, started by the new page's 'ready' (links out and sheet up together) so the sheet leaves as the next page rises; the sheet takes the destination theme's colour about 1.8 s after the click on a real-time trigger, after it has left at full speed (the dark sheet in the 0.3x sheets is an artifact of slowing GSAP alone); Lenis stopped while open; Escape and the veil close it; phone closes with the plain reverse.",
   "libraries": [
    "GSAP 3.15",
    "lottie-web (burger, logo)",
    "Pizzicato.js (hover notes, low-pass while open)",
    "Lenis stop/start"
   ],
   "states": [
    "closed (sheet translateY(-690.53px))",
    "opening",
    "open (translateY(0), controls .color-alternate, burger X, logo wordmark)",
    "item hover (arrow, lighter word, note)",
    "closing (reverse x2)",
    "leaving with navigation (sheet yPercent -100 in 1.5 s)"
   ],
   "timings": "open: sheet 1 s expo.inOut; links x -40 to 0, 0.6 s power4.out, stagger 0.075 s from 0.3 s; icons flip at 0.4 s; right column 0.6 s; thumbnail 0.7 s (+1.4 s image scale); pointer events at 1 s; close: reverse at 2x (~1 s); navigation: links out 0.3 s and sheet up 1.5 s expo.inOut, both from the destination's ready",
   "shots": [
    "shots/slowmo-menu-open-sheet.png",
    "shots/slowmo-menu-close-sheet.png",
    "shots/menu-02-hover-item.png",
    "shots/menu-01-open.png",
    "shots/phone-01-menu-open.png",
    "shots/slowmo-phone-nav-sheet.png",
    "shots/menu-open-sheet.png",
    "shots/menu-close-sheet.png"
   ],
   "rebuild": "One timeline per sheet, forward to open, reverse at double speed to close; a separate leave-by-navigation timeline that hands over to the page change (leaves on the destination's ready; Kōzōwood also switches it to the destination colour, on a timer that lands as it leaves). Measured box: 1440 x 690.5 px, rgb(238,233,204), bottom radius 21.66 px, dark veil behind. GSAP or WAAPI with playbackRate -2.",
   "confidence": "high"
  },
  {
   "name": "Scroll choreography",
   "owner_words": "(not named by the owner; part of the flow he describes) Everything moves and shifts as if you are never really leaving a page and going to another. It just continuously flows through content … it feels more like a continuous flow almost as if you're playing a 2D video game than flipping through a website.",
   "selectors": [
    ".letter-container .letter-wrapper",
    "[data-component=secondary-header] .description-container",
    "[data-component=menu] .logo",
    "[data-type-enter]",
    "[data-reading-text]",
    "[data-box-enter-parent] [data-box-enter]",
    "[data-component=photos-parallax] .background-container",
    "[data-section-intersect]",
    "[data-element-intersect]"
   ],
   "mechanism": "Lenis smooth wheel feeding ScrollTrigger: sticky giant letter (top calc((100vh - h)/2) = 155.7 px, 588.5 px tall) scrubbed yPercent -10 to 0 and scale 1 to 0.95 with its Lottie scrubbed frame 0 to 100; description fades by a quarter screen; logo Lottie 0 to 30 past 100 px; type-enter headings (y 40, scaleY 1.1, opacity) and box-enter cards (yPercent 40, scale .9, rotateX 4 under 1000 px perspective) scrubbed, desktop only; SplitText words light 0.2 to 1; fixed photo background with drifting photo cards; fixed controls recolour or hide per section; ambient sound fades over two screens. No custom page scrollbar: native bar measured into --native-scrollbar-width; sliders hide theirs and draw a GSAP-ticked progress bar.",
   "libraries": [
    "Lenis 1.0.11",
    "GSAP ScrollTrigger",
    "SplitText",
    "lottie-web goToAndStop",
    "Pizzicato.js"
   ],
   "states": [
    "top (hero split)",
    "header scrolling (letter held, growing; logo monogram)",
    "sections revealing",
    "photo stack over a held image",
    "footer (controls hidden)"
   ],
   "timings": "all scrubbed to scroll except the logo (2 s power4.out tween); measured: description opacity 0 and letter scale 0.9628 at 435 px; letter scale 0.95 from 794 px; letter leaves sticky at about 744 px (measured on building and why-wood)",
   "shots": [
    "shots/scroll-building-sheet.png",
    "shots/scroll-why-wood-sheet.png",
    "shots/scrollb-02.png",
    "shots/phone-scroll-sheet.png"
   ],
   "rebuild": "position: sticky for the held glyph; ScrollTrigger or CSS scroll-driven animations (animation-timeline: view()) for scrubbed reveals; scrubbed Lottie or SVG stroke for the growing glyph; per-section data attributes that recolour the fixed bar; keep scrolling short (owner row 9).",
   "confidence": "high"
  },
  {
   "name": "Lottie elements (letter, logo, burger, audio)",
   "owner_words": "(not named by the owner; part of the flow he describes) Everything moves and shifts as if you are never really leaving a page and going to another. It just continuously flows through content … it feels more like a continuous flow almost as if you're playing a 2D video game than flipping through a website.",
   "selectors": [
    "[data-component=letter] [data-component=lottie]",
    "[data-component=menu] .logo[data-component=lottie]",
    "button[data-component=burger] [data-component=lottie]",
    "button[data-component=audio-button] [data-component=lottie]"
   ],
   "mechanism": "One Vue wrapper: lottie.loadAnimation({renderer:'svg', path:'/lotties/<slug>.json', autoplay:false}); DOMLoaded resolves a promise given to the page loader so page changes wait. Letter (<theme>-<locale>, 120 frames) scrubbed by scroll 0 to 100; logo tweened 0 to 30 on scroll past 100 px; burger played forward and reversed with the menu; audio rests at 134, plays 1 to 60 then loops 61 to 121, stops with 122 to 135. The probe's count of 2 matched only the .lottie class (burger, audio).",
   "libraries": [
    "lottie-web 5.13.0",
    "GSAP (frame tweens)",
    "ScrollTrigger (scrub)"
   ],
   "states": [
    "letter: plain initial, growing, full with leaves and side labels",
    "logo: wordmark, monogram",
    "burger: lines, X",
    "audio: muted line, wave loop"
   ],
   "timings": "letter: scroll-scrubbed from half a screen into the header to its end; logo: 2 s power4.out; burger: at 0.4 s of the menu open; audio: segment playback on click",
   "shots": [
    "shots/scrollb-00.png",
    "shots/scrollb-01.png",
    "shots/scrollb-02.png",
    "shots/scrollw-02.png",
    "shots/menu-02-hover-item.png",
    "shots/states-phone-sheet.png"
   ],
   "rebuild": "Hand-drawn Lottie or SVG stroke animation whose frame is driven by scroll or by the conversation (goToAndStop on each update), wrapped in one component that reports when its file is loaded; fits the marker look (row 8).",
   "confidence": "medium"
  }
 ],
 "transitions": [
  {
   "name": "Footer link (first burst, by the coordinator)",
   "from": "/en/building-with-kozowood (footer)",
   "to": "/en/why-wood",
   "layers": [
    "footer wordmark slides down (inferred scroll-reset side effect)",
    "old page pinned, drifts up 100 px",
    "new page rises 100vh to 0",
    "hero clip-path wipe up",
    "background full-bleed 1.4 to 1.1, then crop right",
    "letter and text in"
   ],
   "duration_ms": 6000,
   "shots": [
    "../_examples/kozowood-transition-sheet.png"
   ]
  },
  {
   "name": "Menu link, measured in real time",
   "from": "/en/building-with-kozowood (scrolled 569 px, menu open)",
   "to": "/en/why-wood",
   "layers": [
    "old page fixed at top -569px, y 0 to -100px",
    "new page opacity 0, then y 900px to 0",
    "menu sheet yPercent 0 to -100",
    "hero .top-row clip-path",
    "background scale and clip",
    "letter opacity and scale",
    "left column",
    "description"
   ],
   "duration_ms": 6100,
   "shots": [
    "shots/timing-99-settled.png"
   ]
  },
  {
   "name": "Menu link",
   "from": "/en/building-with-kozowood",
   "to": "/en/about",
   "layers": [
    "menu links out",
    "sheet lifts away (dark only in the slowed sheet)",
    "About page rises from below",
    "About type header settles, inline image widens"
   ],
   "duration_ms": 3000,
   "shots": [
    "shots/slowmo-navmenu-sheet.png",
    "shots/navmenu-sheet.png",
    "shots/smnav-a-02.png"
   ]
  },
  {
   "name": "In-page link 'View more articles'",
   "from": "/en/why-wood (scrolled to the articles block)",
   "to": "/en/journal",
   "layers": [
    "old page pinned, drifts up",
    "off-white journal page rises",
    "journal hero photo wipes up",
    "featured card and running caption settle"
   ],
   "duration_ms": 3000,
   "shots": [
    "shots/slowmo-navinpage-sheet.png",
    "shots/sminpage-99-journal.png"
   ]
  },
  {
   "name": "Browser back (history.back())",
   "from": "/en/journal",
   "to": "/en/why-wood",
   "layers": [
    "one flat frame of the why-wood colour (seen once)",
    "journal pinned",
    "why-wood page rises from below",
    "hero wipe, full-bleed photo, crop right",
    "text and W in"
   ],
   "duration_ms": 6000,
   "shots": [
    "shots/slowmo-navback-sheet.png",
    "shots/smback-00.png",
    "shots/navback-sheet.png"
   ]
  },
  {
   "name": "Menu link on a phone",
   "from": "/en/building-with-kozowood",
   "to": "/en/why-wood",
   "layers": [
    "sheet closes with the plain reverse (it darkens only in the slowed sheet)",
    "links fade",
    "old page fades out after 0.5 s",
    "new page fades in from y 20 after 0.5 s",
    "sheet clears"
   ],
   "duration_ms": 1500,
   "shots": [
    "shots/slowmo-phone-nav-sheet.png",
    "shots/phone-nav-sheet.png"
   ]
  },
  {
   "name": "Menu open and close",
   "from": "page",
   "to": "menu sheet",
   "layers": [
    "veil",
    "sheet yPercent -100 to 0",
    "links stagger from x -40",
    "icons flip",
    "columns fade in",
    "close: reverse at 2x"
   ],
   "duration_ms": 1000,
   "shots": [
    "shots/slowmo-menu-open-sheet.png",
    "shots/slowmo-menu-close-sheet.png",
    "shots/menu-open-sheet.png",
    "shots/menu-close-sheet.png",
    "shots/phone-menu-open-sheet.png"
   ]
  },
  {
   "name": "First load",
   "from": "(none)",
   "to": "/en/building-with-kozowood",
   "layers": [
    "pale loader screen",
    "brown panel with the hero image rising at its foot",
    "video full-bleed",
    "split hero with letter and text"
   ],
   "duration_ms": 6000,
   "shots": [
    "shots/intro-sheet.png",
    "shots/intro-99-settled.png"
   ]
  }
 ],
 "not_confirmed": [
  "Real-time 120 ms bursts: fsit2 software rendering took 0.3 to 2 s per screenshot; order and timing come from 0.3x slowed GSAP runs and a real-time eval series. A GPU screen recording or a CDP screencast step would settle it.",
  "Reduced motion live: capture.cjs has no media emulation; source shows no reduced-motion path except the three.js block gate.",
  "First-load curtain and loader component code and timing not read or measured.",
  "Lottie JSON content not downloaded; drawings known from screenshots only.",
  "three.js comparison block on why-wood never rendered (GPU check failed on fsit2).",
  "About and Journal hero entrance code (0a93fda.js, 3269820.js) not read for exact values.",
  "Menu colour per destination theme: only why-wood (rgb 62, 54, 46) and building (rgb 238, 233, 204) measured; what fires the real-time switch not read.",
  "Footer wordmark sliding down in the first burst: explanation (scroll-scrubbed reveal running back on scroll reset) inferred.",
  "One flat colour frame on browser back seen once in capture; real-browser behaviour unknown.",
  "Custom mouse cursor component not captured.",
  "Sound never heard; behaviour from code.",
  "Focus trap and screen reader behaviour of the menu not tested (closed links stay in the tab order, verifier).",
  "Approximate durations for the About, Journal and back transitions are read off slowed frames.",
  "Menu sheet darkening on a phone at full speed not measured (desktop: the colour switch lands after the sheet has left).",
  "three.js chunks (9a6eb3c.js, ffd8939.js and the other lazy WebGL chunks) and ef45739.js not re-read by the verifier; the WebGL2 and reduced-motion gate was found in 3f34d05.js."
 ]
}
