*{box-sizing:border-box}
html,body{margin:0;background:#0b1020;font-family:system-ui,-apple-system,sans-serif}
.hero{position:relative;width:100%;overflow:hidden;background:#4f8fd0}
.plate-sky{background:linear-gradient(180deg,#4f8fd0 0%,#93c3e4 52%,#d6ecf2 100%)}

/* PLATE SYSTEM -- multiplane-camera layering. Every plate is absolute/inset:0
   with no z-index: depth is DOM order in index.html, later plates paint on top.
   To move something in depth, put it in a different plate. Plates ignore the
   pointer so clicks reach the UI plate's tabs and buttons. */
.plate{position:absolute;inset:0;pointer-events:none;overflow:hidden}
/* Grade layer: an HTML wrapper for one world svg, carrying that layer's season/time-of-day CSS
   filter (see gradeLayer in scene.js). Same box as its plate. */
.grade-layer{position:absolute;inset:0;pointer-events:none;overflow:hidden}
/* overflow:hidden (the plate clips here anyway): keeps each colour-grade filter's working
   area to the visible plate instead of the full width of oversized art (the foothills svg is
   ~2.4x the world's width), which matters for memory on iPhone. */
/* No will-change layer hints anywhere. ?bench on real Safari (MBP, iMac, iPhone, Sept 23)
   measured every hint set as slower, some by 3-4x, and the iPhone reloaded the page under
   the extra layer memory. Animated transforms still get GPU layers from the browser itself. */
.plate-ui{pointer-events:none;z-index:6} /* children (buttons, tabs) opt back in via their own rules */

/* PAGE-LOAD REVEAL -- each plate rises into place once on first load, in three
   waves: near layers (0ms), the rest of the illustration (260ms), then the hero
   text (620ms). Near plates travel further, like a multiplane camera. scene.js
   adds .scene-loaded after every plate is built (double rAF so this hidden
   state paints first); these rules only define the hidden state. */
.scene-core .plate{
  transition-property: transform, opacity;
  transition-duration: 820ms;
  transition-timing-function: cubic-bezier(.19,.85,.34,1);
}
.scene-core .plate-foreground,
.scene-core .plate-fx,
.scene-core .plate-midground,
.scene-core .plate-birds{transition-delay:0ms}
.scene-core .plate-background,
.scene-core .plate-clouds,
.scene-core .plate-aurora,
.scene-core .plate-celestial,
.scene-core .plate-sky{transition-delay:260ms}
.scene-core .plate-ui{transition-delay:620ms}

.scene-core:not(.scene-loaded) .plate-foreground{transform:translateY(10%);opacity:0}
.scene-core:not(.scene-loaded) .plate-fx{transform:translateY(8%);opacity:0}
.scene-core:not(.scene-loaded) .plate-midground{transform:translateY(7%);opacity:0}
.scene-core:not(.scene-loaded) .plate-birds{transform:translateY(6%);opacity:0}
.scene-core:not(.scene-loaded) .plate-background{transform:translateY(5%);opacity:0}
.scene-core:not(.scene-loaded) .plate-clouds{transform:translateY(4%);opacity:0}
.scene-core:not(.scene-loaded) .plate-aurora{transform:translateY(2%);opacity:0}
.scene-core:not(.scene-loaded) .plate-celestial{transform:translateY(2%);opacity:0}
.scene-core:not(.scene-loaded) .plate-sky{transform:translateY(1%);opacity:0}
.scene-core:not(.scene-loaded) .plate-ui{transform:translateY(6%);opacity:0}

/* Accessibility: skip the motion for anyone who's asked for less of it --
   the scene still appears, just instantly rather than sliding in. */
@media (prefers-reduced-motion: reduce){
  .scene-core .plate{transition:none}
}

#skyGradient{position:absolute;inset:0;transition:background .8s linear}
.pine-detailed [data-bucket] { transition: fill 0.7s linear; }


/* ===== chrome (ported from old site) ===== */
:root{
    --paper: 245,248,239;
    --ink: 47,74,42;
    --accent: 216,97,143;
    --accent2: 124,179,66;
    --font-display: "Nunito","Poppins","Arial Rounded MT Bold","Avenir Next Rounded","Avenir Next","Segoe UI",Arial,sans-serif;
    --font-body: "Nunito","Avenir Next","Avenir","Segoe UI","Helvetica Neue",Arial,sans-serif;
    /* motion tokens: Bézier fallback, replaced by generated springs where linear() is supported */
    --spring-soft: cubic-bezier(.2,.9,.25,1.15);
    --spring-soft-time: 1500ms;
    --spring-pop: cubic-bezier(.3,1.4,.4,1);
    --spring-pop-time: 900ms;
  }
:root {
      /* stiffness 170, damping 14 */
      --spring-soft: linear(0.000, 0.056 2.5%, 0.194 5.0%, 0.373 7.5%, 0.561 10.0%, 0.735 12.5%, 0.882 15.0%, 0.994 17.5%, 1.071 20.0%, 1.116 22.5%, 1.134 25.0%, 1.132 27.5%, 1.117 30.0%, 1.095 32.5%, 1.069 35.0%, 1.045 37.5%, 1.023 40.0%, 1.006 42.5%, 0.994 45.0%, 0.986 47.5%, 0.982 50.0%, 0.982 52.5%, 0.983 55.0%, 0.986 57.5%, 0.989 60.0%, 0.993 62.5%, 0.996 65.0%, 0.998 67.5%, 1.000 70.0%, 1.002 72.5%, 1.002 75.0%, 1.002 77.5%, 1.002 80.0%, 1.002 82.5%, 1.002 85.0%, 1.001 87.5%, 1.001 90.0%, 1.000 92.5%, 1.000 95.0%, 1.000 97.5%, 1.000);
      /* stiffness 300, damping 11 */
      --spring-pop: linear(0.000, 0.069 2.5%, 0.247 5.0%, 0.486 7.5%, 0.740 10.0%, 0.974 12.5%, 1.159 15.0%, 1.283 17.5%, 1.342 20.0%, 1.343 22.5%, 1.298 25.0%, 1.223 27.5%, 1.135 30.0%, 1.048 32.5%, 0.974 35.0%, 0.920 37.5%, 0.888 40.0%, 0.878 42.5%, 0.886 45.0%, 0.908 47.5%, 0.937 50.0%, 0.968 52.5%, 0.997 55.0%, 1.019 57.5%, 1.035 60.0%, 1.042 62.5%, 1.042 65.0%, 1.036 67.5%, 1.027 70.0%, 1.017 72.5%, 1.006 75.0%, 0.997 77.5%, 0.990 80.0%, 0.986 82.5%, 0.985 85.0%, 0.986 87.5%, 0.989 90.0%, 0.992 92.5%, 0.996 95.0%, 1.000 97.5%, 1.000);
    }
.site-head{
    display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap;
    padding:22px clamp(20px,5vw,72px) 8px;position:relative;z-index:3;background:#fff
  }
.brand{display:flex;align-items:center;gap:12px;font-family:var(--font-display);font-weight:800;font-size:24px;line-height:1;letter-spacing:-.01em;text-decoration:none;color:#0f4d47}
.brand .mark{width:44px;height:44px;border-radius:50%;background:rgb(var(--accent));box-shadow:inset -8px -10px 0 rgba(0,0,0,.15);transition:background .8s}
.tabs{display:flex;gap:6px;padding:0;margin:0;list-style:none}
.tab{
    appearance:none;border:0;background:none;cursor:pointer;font:inherit;
    font-family:var(--font-display);font-weight:700;font-size:17px;color:#0f4d47;
    padding:10px 14px 12px;border-radius:10px;position:relative;
    transition:color 220ms ease-out,background 220ms ease-out,transform var(--spring-pop-time) var(--spring-pop)
  }
.tab::after{content:"";position:absolute;left:14px;right:14px;bottom:6px;height:3px;border-radius:3px;background:rgb(var(--accent));transform:scaleX(0);transform-origin:0 50%;transition:transform 240ms ease-out,background .8s}
.tab:hover{background:rgba(15,77,71,.06)}
.tab:hover::after,.tab:focus-visible::after{transform:scaleX(1)}
.tab:active{transform:scale(.94);transition-duration:80ms}
.tab[aria-selected="true"]{color:rgb(var(--accent))}
.tab[aria-selected="true"]::after{transform:scaleX(1)}
.tab:focus-visible{outline:3px solid rgb(var(--accent));outline-offset:2px}
.hero-copy{position:absolute;z-index:2;left:clamp(20px,7vw,110px);top:44%;transform:translateY(-50%);width:min(520px,80%);color:#fff}
.hero-copy::before{content:"";position:absolute;inset:-60px -80px;background:radial-gradient(closest-side,rgba(10,25,40,.34),rgba(10,25,40,0));z-index:-1;pointer-events:none}
.hero-copy h1{font-family:var(--font-display);font-weight:800;font-size:clamp(22px,4.6vw,60px);line-height:1.05;letter-spacing:-.01em;margin:0 0 14px;text-shadow:0 2px 0 rgba(0,0,0,.12)}
.hero-copy p{font-size:clamp(13px,2.4vw,17px);line-height:1.5;margin:0 0 20px;text-shadow:0 1px 0 rgba(0,0,0,.18);max-width:44ch}
.btn{
    display:inline-block;font-family:var(--font-display);font-weight:700;font-size:15px;text-decoration:none;
    padding:13px 26px;border-radius:12px;color:#fff;background:#1c4f8f;
    box-shadow:0 8px 20px -10px rgba(0,0,0,.5);
    transition:transform var(--spring-pop-time) var(--spring-pop),background .8s
  }
.btn:hover{transform:translateY(-2px)}
.btn:active{transform:scale(.96);transition-duration:80ms}
.btn.solid{background:rgb(var(--accent))}
.btn:focus-visible{outline:3px solid rgb(var(--accent));outline-offset:3px}
.btn + .btn{margin-left:10px}
@media (max-width:520px){.btn + .btn{margin:10px 0 0 0}}
.view{display:none}
.view.on{display:block;animation:rise var(--spring-soft-time) var(--spring-soft) both}
.view.on .works li{animation:rowIn 320ms ease-out both}
.view.on .works li:nth-child(1){animation-delay:140ms}
.view.on .works li:nth-child(2){animation-delay:200ms}
.view.on .works li:nth-child(3){animation-delay:260ms}
.hiding .view.on{animation:sink .35s ease-in both}
.hiding .view.on .works li{animation:none}
/* ---------- white section ---------- */
  .about{position:relative;padding:40px clamp(20px,6vw,80px) 90px;text-align:center;overflow:hidden;background:#fff}
.about-inner{position:relative;z-index:1;max-width:760px;margin:0 auto}
.season-line{font-size:14px;color:rgb(var(--accent));margin:0 0 6px;transition:color .8s}
.lede{font-size:18px;line-height:1.6;color:#3c5561;margin:0 auto 38px;max-width:58ch}
.works{margin:0 0 38px;padding:0;list-style:none;display:grid;grid-template-columns:repeat(3,1fr);gap:28px;text-align:left}
.works li{display:grid;grid-template-columns:1fr auto;gap:4px 12px;padding-top:14px;border-top:3px solid rgb(var(--accent));transition:border-color .8s}
.works b{font-family:var(--font-display);font-weight:700;font-size:17px;color:#0f4d47;line-height:1.25}
.works i{font-style:normal;font-family:var(--font-display);font-weight:700;font-size:13px;color:rgb(var(--accent));padding-top:3px}
.works p{grid-column:1/3;margin:0;font-size:14px;line-height:1.5;color:#4a6470}
.decor{position:absolute;bottom:0;width:clamp(180px,26vw,360px);height:auto;z-index:0;pointer-events:none}
.decor.left{left:-30px}
.decor.right{right:-30px}
.decor .puff{fill:rgb(var(--accent));opacity:.28;transition:fill .8s}
.decor .puff .lit{fill:#fff;opacity:.7}
.decor.left .puff{animation:puffDrift 9s ease-in-out infinite alternate}
.decor.right .puff{animation:puffDrift 11s ease-in-out infinite alternate-reverse}
.credit{font-size:12px;color:#7a8a94;margin:30px 0 0}
.clock{
    position:relative;z-index:1;display:inline-flex;gap:14px;align-items:center;margin-top:48px;
    font-size:13px;color:#3c5561;background:#f1f5f7;padding:6px 6px 6px 16px;border-radius:99px
  }
.clock button{
    appearance:none;font:inherit;font-family:var(--font-display);font-weight:700;font-size:13px;cursor:pointer;
    border:0;background:rgb(var(--accent));color:#fff;padding:8px 14px;border-radius:99px;transition:background .8s,filter .2s
  }
.clock button:hover{filter:brightness(1.1)}
.clock #modeBtn{background:#243b46}
.clock button:focus-visible{outline:3px solid rgb(var(--accent));outline-offset:2px}
.clock button[disabled]{opacity:.5;cursor:default}
/* These two blocks came over from the original site inside media queries;
   the wrappers were lost in the port, so until Sept 23 they applied at every
   width (no spring motion, small tabs, one-column works list on desktop). */
@media (prefers-reduced-motion:reduce){
  :root{--spring-soft:ease-out;--spring-soft-time:200ms;--spring-pop:ease-out;--spring-pop-time:150ms}
  .view.on{animation-duration:200ms}
  .view.on .works li{animation:none}
  .hiding .view.on{animation:none}
  .tab:not([aria-selected="true"]){transform:none}
}
@media (max-width:760px){
  .site-head{padding:14px 16px 6px}
  .tab{padding:8px 10px 10px;font-size:15px}
  .tab::after{left:10px;right:10px}
  .works{grid-template-columns:1fr;gap:18px}
  .about{padding-bottom:70px}
}

.aurora-fill{position:absolute;inset:0;z-index:0;background:linear-gradient(180deg,#1a2f52 0%,#2d4a7a 30%,#3d6299 55%,#5a85b8 100%)}
.aurora-fill::before{content:"";position:absolute;inset:0;background:
  radial-gradient(ellipse 60% 40% at 20% 15%,rgba(120,220,190,.22),transparent 70%),
  radial-gradient(ellipse 50% 35% at 80% 10%,rgba(150,140,220,.18),transparent 70%);
  mix-blend-mode:screen}
/* Shorter than the world's full 5003.8931/3333.3333: every world svg is
   bottom-anchored (bottom:0) and .plate's overflow:hidden crops the extra off
   the TOP, trimming empty sky. 2955 leaves ~220 units above the tallest peak
   (world y=600) and keeps the whole shore/foreground stack. */
.scene-core{position:relative;z-index:1;width:min(100%,1800px);margin:0 auto;aspect-ratio:5003.8931/2955;overflow:hidden}

/* Bottom wave: masks the hero into the white page below with one smooth
   cubic wave. It rolls: two seamless tiles slide left (heroRoll) while the band gently rises
   and falls (heroSwell), after the 'Waves' / '1 meter swell' pens. Transform only, so the
   compositor moves it without repainting. A faint second wave line rolls slower above it
   (.hero-swell). It eats roughly the bottom 18% of the hero, so nothing that
   must stay visible belongs below world y≈2735. */
.hero::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 200%;                     /* two tiles of the wave, so it can roll (see heroRoll) */
  height: 18%;
  transform-origin: 50% 100%;
  animation: heroRoll 48s linear infinite, heroSwell 7s ease-in-out infinite alternate;
  background: #fff;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath d='M0,60 C200,100 400,0 600,60 C800,120 1000,20 1200,60 L1200,120 L0,120 Z' fill='%23fff'/%3E%3C/svg%3E");
  -webkit-mask-size: 50% 100%; -webkit-mask-repeat: repeat-x;
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath d='M0,60 C200,100 400,0 600,60 C800,120 1000,20 1200,60 L1200,120 L0,120 Z' fill='%23fff'/%3E%3C/svg%3E");
  mask-size: 50% 100%; mask-repeat: repeat-x;
  pointer-events: none;
  z-index: 5;
}
.hero-copy{position:absolute;z-index:10;left:6%;top:8%;max-width:34%;pointer-events:none}
/* Only the links/buttons take taps; the rest of the text lets taps through to the scene's
   hidden spots (on phones the copy covers most of the scene). */
.hero-copy .view{pointer-events:none}.hero-copy .view a,.hero-copy .view button{pointer-events:auto}
.hero-copy .view:not(.on){display:none}
.about .view:not(.on){display:none}

/* WIND RUFFLE -- each material expresses the shared --wind field its own way.
   (The early pine-tier / leaf-flutter / grass-bend classes live only in the
   catalog now, with the builders that use them.)
   FLOWER: whole bloom nods on its stem; slow, low amplitude, not per petal. */
.flower-nod{
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: flowerNod var(--dur, 3.6s) ease-in-out infinite alternate;
  animation-delay: var(--delay, 0s);
}
@keyframes flowerNod{ from{ transform: rotate(-1.5deg); } to  { transform: rotate(2.5deg); } }

/* ============================================================
   DEVICE-TIER visibility -- deliberate subset per width (blueprint ch.11)
   data-tier: d = desktop-only, md = tablet+up, (untagged/mobile = always)
   ============================================================ */
/* tablet and below: hide desktop-only richness */
@media (max-width: 1024px){
  [data-tier="d"]{ display:none !important; }
}
/* mobile: also hide tablet-tier, keep only mobile/always elements */
@media (max-width: 640px){
  [data-tier="md"]{ display:none !important; }
}

/* keep hero copy clear of the header and never clipped at the top */
.hero{padding-top:0}
.hero-copy{top:7%;transform:none;}
.hero-copy h1{margin-top:0;line-height:1.05;}
@media (max-width:1024px){ .hero-copy{max-width:60%;top:6%;} }
@media (max-width:640px){ .hero-copy{max-width:82%;left:6%;top:5%;} }

/* The pine clusters used to be nudged inward and scaled up below 1024px. That lifted trunks
   off the sloping banks (Sept 24 screenshots), so the pines now keep their world positions at
   every width, like the rest of the scene. */

/* Detailed pine: the whole traced tree rocks slowly as one mass. Pivot is set
   inline per instance at the art's own measured base point. */
.pine-detailed-sway{
  animation: pineDetailedSway var(--dur, 5.5s) ease-in-out infinite alternate;
  animation-delay: var(--delay, 0s);
}
/* Pines sway as HTML layers (scene.js buildTrees) so the rotation stays on the GPU. */
.fg-layer{ position:absolute; }
.fg-layer > svg{ pointer-events:none; }
.cloud-layer{ position:absolute; }
.cloud-layer > svg{ position:absolute; inset:0; width:100%; height:100%; overflow:visible; }
@keyframes pineDetailedSway{ from{ transform: rotate(-0.6deg); } to  { transform: rotate(0.9deg); } }

/* Hero tree (<image>): a tiny rotation about the tree's own base point,
   set inline in scene.js. */
.tree-isolated-sway{
  animation: treeIsolatedSway var(--dur, 6s) ease-in-out infinite alternate;
  animation-delay: var(--delay, 0s);
}
@keyframes treeIsolatedSway{ from{ transform: rotate(-0.4deg); } to  { transform: rotate(0.5deg); } }

/* Grass clump sway: boulder-side grass and foreground plants (fetched SVGs). */
.grass-clump-sway{
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: grassClumpSway 1.4s cubic-bezier(.65,0,.35,1) infinite alternate;
  animation-duration: var(--dur, 1.4s);
}
/* Reeds: each fan of blades rocks a little on its own inside the clump's sway, so it shimmers. */
.reed-fan{
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: reedFan var(--rdur, 2.6s) ease-in-out var(--rdel, 0s) infinite alternate;
}
@keyframes reedFan{ from{ transform: skewX(1.2deg); } to{ transform: skewX(-1.6deg); } }
.sway-grass{ animation: grassClumpSway var(--dur, 1.4s) cubic-bezier(.65,0,.35,1) infinite alternate; }
.sway-grass-mirror{ animation: grassClumpSwayMirror var(--dur, 1.4s) cubic-bezier(.65,0,.35,1) infinite alternate; }
@keyframes grassClumpSwayMirror{ from{ transform: skewX(-3deg); } to{ transform: skewX(4deg); } }
.sway-flower{ animation: flowerNod var(--dur, 3.6s) ease-in-out infinite alternate; animation-delay: var(--delay, 0s); }
/* Plant bases (scene.js liftSways): a still wrapper masks each swaying clump below an edge made
   for where it stands, so whatever is behind (water, grass, snow, rock) shows through the cut.
   Water: U scallops sliding sideways, after the 'Outline Pure CSS' pen, with .waterline-line
   drawing their outline over the stems, in step (same period, same duration). */
.plant-edge{ pointer-events:none;
  -webkit-mask: var(--edge) 0 0 / var(--edge-size) repeat-x; mask: var(--edge) 0 0 / var(--edge-size) repeat-x; }
.edge-water{ animation: edgeSlide 2.6s linear infinite; }
@keyframes edgeSlide{ to{ -webkit-mask-position: var(--edge-to) 0; mask-position: var(--edge-to) 0; } }
html[data-season="winter"] .edge-ground{ -webkit-mask-image: var(--edge-winter); mask-image: var(--edge-winter); }
.waterline-line{ pointer-events:none; overflow:hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent); }
.wl-strip{ position:absolute; top:0; height:100%; animation: wlSlide 2.6s linear infinite; }
.wl-strip svg{ display:block; width:100%; height:100%; overflow:visible; }
.wl-strip path{ fill:none; stroke:rgba(255,255,255,.75); stroke-width:1.6px; vector-effect:non-scaling-stroke; stroke-linecap:round; }
@keyframes wlSlide{ to{ transform: translateX(var(--edge-to)); } }
@media (prefers-reduced-motion:reduce){ .edge-water, .wl-strip{ animation:none; } }
/* Plants standing in the lake: thin rings spread from the stems at the waterline. */
.water-rings{ pointer-events:none; }
.water-rings i{
  position:absolute; left:var(--rx); top:var(--ry); width:var(--rw); aspect-ratio:5/1;
  border:1.5px solid rgba(255,255,255,.6); border-radius:50%; opacity:0;
  transform:translate(-50%,-50%) scale(.35);
  animation:waterRing 4.2s cubic-bezier(.2,.6,.4,1) infinite;
}
.water-rings i:nth-child(2){ animation-delay:-1.4s; }
.water-rings i:nth-child(3){ animation-delay:-2.8s; }
@keyframes waterRing{
  0%  { transform:translate(-50%,-50%) scale(.35); opacity:0; }
  12% { opacity:.85; }
  100%{ transform:translate(-50%,-50%) scale(1.6); opacity:0; border-width:.5px; }
}
@media (prefers-reduced-motion:reduce){ .water-rings{ display:none; } }
/* Grass and plants bend with a shear pinned at the base, not a rotation: the base line stays
   level and still (as if held by water or roots) while the tips sway. A rotation tilted the
   base, one corner dipping as the other lifted. */
@keyframes grassClumpSway{
  from{ transform: skewX(3deg); }
  to  { transform: skewX(-4deg); }
}

/* Water ripple: expands outward from impact, heavy at first, thinning
   as it spreads. Rain fades faster/harder; snow holds longer, softer. */
.water-ripple{ transform-box: fill-box; transform-origin: 50% 50%; }
.water-ripple-rain{ animation: waterRippleGrow var(--dur) cubic-bezier(.15,.6,.35,1) forwards; }
.water-ripple-snow{ stroke: #ffffff !important; animation: waterRippleGrowSnow var(--dur) cubic-bezier(.2,.5,.4,1) forwards; }
/* The mid keyframe holds a strong, thick ring for ~60% of its life so rain
   rings read clearly instead of fading the instant they land. */
@keyframes waterRippleGrow{
  0%{ opacity:1; stroke-width:4.5; transform:scale(.08); }
  60%{ opacity:.85; stroke-width:1.8; }
  100%{ opacity:0; stroke-width:.6; transform:scale(1); }
}
@keyframes waterRippleGrowSnow{
  0%{ opacity:1; stroke-width:3; transform:scale(.08); }
  55%{ opacity:.75; stroke-width:1.6; }
  100%{ opacity:0; stroke-width:.4; transform:scale(1); }
}
.water-drop-rain{ animation: waterFallRain var(--dur) linear forwards; }
.water-drop-snow{ animation: waterFallSnow var(--dur) ease-in-out forwards; }
@keyframes waterFallRain{
  0%{ transform:translateY(0); opacity:0; }
  12%{ opacity:1; }
  85%{ opacity:1; }
  100%{ transform:translateY(var(--dist)); opacity:0; }
}
@keyframes waterFallSnow{
  0%{ transform:translate(0,0); opacity:0; }
  10%{ opacity:1; }
  50%{ transform:translate(calc(var(--sway) * -1), calc(var(--dist) * .5)); opacity:1; }
  85%{ opacity:1; }
  100%{ transform:translate(0,var(--dist)); opacity:0; }
}

/* Aurora + stars (winter/fall night sky). Each star twinkles on its own
   timing (--dur/--delay set per-instance in scene.js) so the field reads as
   alive rather than a static dusting of dots. Aurora ribbons drift gently
   side to side and breathe in height, slow enough to feel atmospheric
   rather than like a UI animation. */
.star-twinkle{ animation: starTwinkle var(--dur) ease-in-out var(--delay) infinite alternate; }
@keyframes starTwinkle{ 0%{ opacity:.25; } 100%{ opacity:1; } }
.aurora-drift{
  animation: auroraDrift var(--dur) ease-in-out infinite alternate;
}
.star{ position:absolute; border-radius:50%; background:#fff; }
@keyframes auroraDrift{
  0%{ transform: translateX(-2%) scaleY(1); }
  100%{ transform: translateX(2%) scaleY(1.1); }
}
/* Shooting stars are animated from JS (spawnShootingStar in scene.js), not
   @keyframes: custom properties read inside keyframes raced the class-add and
   froze streaks at their invisible first frame. */

/* ── Trigger guide (footer link + modal) ── */
.tg-open{appearance:none;background:none;border:0;padding:0;margin-left:6px;font:inherit;color:#0f4d47;text-decoration:underline;text-underline-offset:2px;cursor:pointer}
.tg-open:hover{color:rgb(var(--accent))}
.tg-open:focus-visible{outline:2px solid rgb(var(--accent));outline-offset:2px;border-radius:3px}
.tg{width:min(860px,calc(100vw - 32px));max-height:calc(100vh - 48px);padding:0;border:0;border-radius:14px;
  box-shadow:0 20px 60px rgba(8,20,24,.35);color:#1d2b24;background:#f7f9f6;text-align:left;font:16px/1.55 system-ui,-apple-system,sans-serif;overflow:hidden}
.tg[open]{display:flex;flex-direction:column}
.tg::backdrop{background:rgba(10,24,28,.55);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}
.tg-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px 20px;border-bottom:1px solid #dbe4dc;background:#fff}
.tg-head h2{margin:0;font-family:var(--font-display);font-weight:700;font-size:20px;color:#0f4d47}
.tg-x{appearance:none;border:0;background:#eef3ef;color:#0f4d47;width:34px;height:34px;border-radius:50%;font-size:22px;line-height:1;cursor:pointer}
.tg-x:hover{background:#e0eae3}
.tg-x:focus-visible{outline:2px solid rgb(var(--accent));outline-offset:2px}
.tg-body{overflow:auto;padding:18px 20px 24px}
.tg-body h3{font-family:var(--font-display);font-weight:700;font-size:16px;color:#0f4d47;margin:22px 0 8px}
.tg-body p{margin:0 0 8px;max-width:68ch}
.tg-lede{color:#4d5e55}
.tg figure{margin:14px 0 0}
.tg figure img{display:block;width:100%;height:auto;border-radius:10px;border:1px solid #dbe4dc;background:#dde9ef}
.tg-rocks{list-style:none;counter-reset:r;margin:0;padding:0;display:grid;gap:8px}
.tg-rocks li{counter-increment:r;position:relative;padding:10px 12px 10px 52px;background:#fff;border:1px solid #dbe4dc;border-radius:10px}
.tg-rocks li::before{content:counter(r);position:absolute;left:12px;top:10px;width:28px;height:28px;border-radius:50%;display:grid;place-items:center;
  font:700 14px var(--font-display),sans-serif;color:#fff;background:#1b2622;border:3px solid var(--c)}
.tg-rocks b{color:#1d2b24}
.tg-rocks span{color:#6a7a70;font-size:14px;margin-left:6px}
.tg-rocks p{margin:4px 0 0;font-size:15px}
.tg table{border-collapse:collapse;width:100%;font-size:15px}
.tg th,.tg td{text-align:left;padding:7px 8px;border-bottom:1px solid #dbe4dc;vertical-align:top}
.tg th{font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:#6a7a70}
.tg code{font-family:ui-monospace,Menlo,monospace;font-size:.9em;background:#fff;border:1px solid #dbe4dc;padding:1px 5px;border-radius:4px}
.tg-note{color:#6a7a70;font-size:14px;margin-top:12px}
@media (max-width:560px){.tg-rocks span{display:block;margin:2px 0 0}.tg-head{padding:12px 14px}.tg-body{padding:14px}}

/* Marley & Me tent (scene.js "THE TENT"). The image fills its world box; the night light is a
   separate ungraded frame. The canvas glow is masked by the tent art itself, so only the fabric
   lights up. Only the small lantern halo animates (opacity/transform: compositor only). */
.scene-tent img{position:absolute;inset:0;width:100%;height:100%;display:block;-webkit-user-drag:none;user-select:none}
.tent-night{opacity:0}
.tent-glow{position:absolute;inset:0;
  -webkit-mask-image:url(../assets/tent-scene.svg);mask-image:url(../assets/tent-scene.svg);
  -webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  background:radial-gradient(ellipse 34% 58% at var(--gx) var(--gy),rgba(255,214,140,.95),rgba(255,170,80,.55) 45%,rgba(255,150,60,.18) 75%,rgba(255,150,60,0) 100%)}
.tent-lamp{position:absolute;width:16%;aspect-ratio:1;margin:-8% 0 0 -8%;border-radius:50%;
  background:radial-gradient(closest-side,#fff6d8,rgba(255,206,110,.9) 18%,rgba(255,170,70,.35) 48%,rgba(255,170,70,0));
  animation:tentLamp 2.6s ease-in-out infinite alternate}
@keyframes tentLamp{0%{opacity:.85;transform:scale(.92)}35%{opacity:1;transform:scale(1.04)}60%{opacity:.9;transform:scale(.97)}100%{opacity:1;transform:scale(1.07)}}
.tent-spill{position:absolute;width:62%;height:14%;margin:-7% 0 0 -31%;border-radius:50%;
  background:radial-gradient(closest-side,rgba(255,196,110,.42),rgba(255,180,90,0))}
@media (prefers-reduced-motion:reduce){.tent-lamp{animation:none}}

.tg .tg-cast .tg-sec td{padding-top:12px;font-weight:700;color:#0f4d47;border-bottom:1px solid rgba(15,77,71,.15)}
.tg .tg-cast code{white-space:nowrap}

/* PHONES: the scene is only ~200-250px tall here, and the bottom wave eats its lowest ~15%, so
   the hero copy is compacted to keep its button clear of the wave (measured at 320-640px wide,
   all four tabs). Below 360px there is no room for the paragraph; the section under the scene
   says the same thing. */
@media (max-width:480px){
  .hero-copy{max-width:90%;left:5%;top:3.5%}
  .hero-copy::before{inset:-30px -40px;background:radial-gradient(closest-side,rgba(10,25,40,.46),rgba(10,25,40,0))}
  .hero-copy h1{font-size:clamp(17px,5.4vw,22px);margin:0 0 6px}
  .hero-copy p{font-size:12px;line-height:1.38;margin:0 0 9px;max-width:none}
  .hero-copy .btn{font-size:13px;padding:8px 16px;border-radius:10px}
}
@media (max-width:359px){ .hero-copy p{display:none} }

/* ============================================================
   SPYGLASS -- phones held upright (js/camera.js is the camera)
   Instead of shrinking the whole 5000-unit world to a 390px strip, the core (every plate)
   becomes about twice the phone's width and most of the screen tall, and the camera slides it
   sideways under the hero like a spyglass: shots per season, swipe to look around. The world
   svgs size themselves from the core, so nothing inside changes.
   Height: 80% of the small viewport (toolbars showing). Width: the world's own aspect at 80% of
   that height (so ~20% of extra sky sits above the peaks for the hero text), but never less than
   1.85 screens, which is the zoom.
   ============================================================ */
@media (max-width:820px) and (orientation:portrait){
  .hero{
    --spy-h:clamp(480px, 80vh, 800px);
    --spy-h:clamp(480px, 80svh, 800px);
    --spy-w:max(185vw, calc(var(--spy-h) * .8 * 1.50117));
    height:var(--spy-h);
  }
  /* The core is 3% of its width taller than the hero, which clips it: that hides the bottom strip
     of the world (y 3200-3333, 2.7% of the width) where the foreground grass band stops and the
     lake shows through. Desktop's 18% wave covers it; the phone's 44px wave doesn't. */
  .scene-core{
    width:var(--spy-w);
    height:calc(var(--spy-h) + var(--spy-w) * .03);
    max-width:none;aspect-ratio:auto;margin:0;
  }
  .hero::after{height:44px}
}
.hero.spyglass{touch-action:pan-y;-webkit-user-select:none;user-select:none}
/* The hero text: camera.js lifts it out of the plate stack into the hero, so it stays put while
   the world pans under it (above the lens vignette, below the position track and iris). */
.hero.spyglass .hero-copy{left:18px;right:18px;top:20px;width:auto;max-width:none;z-index:5}
.hero.spyglass .hero-copy h1{font-size:clamp(24px,7.2vw,34px);margin-bottom:10px}
.hero.spyglass .hero-copy p{font-size:15px;margin-bottom:16px}
/* Lens: a soft vignette, deeper while the glass swings. */
.spy-lens{position:absolute;inset:0;z-index:4;pointer-events:none;
  background:radial-gradient(ellipse 95% 75% at 50% 48%,rgba(8,16,30,0) 62%,rgba(8,16,30,.28) 100%);
  transition:opacity 900ms ease;opacity:.7}
.spy-lens.sweep,.hero.panning .spy-lens{opacity:1}
/* Where the glass is pointing: the world as a pill, the view as the bright window in it. Tap to jump. */
.spy-track{position:absolute;z-index:6;left:50%;bottom:58px;width:84px;height:18px;margin-left:-42px;cursor:pointer}
.spy-track::before{content:"";position:absolute;left:0;right:0;top:6px;height:6px;border-radius:6px;background:rgba(255,255,255,.35);box-shadow:0 1px 3px rgba(0,0,0,.25)}
.spy-track i{position:absolute;top:5px;height:8px;border-radius:8px;background:#fff;box-shadow:0 1px 4px rgba(0,0,0,.35)}
/* Iris shutter: six solid blades (no radius, border or shadow, so they stay bitmap-free layers). */
.spy-iris{position:absolute;inset:0;z-index:7;overflow:hidden;pointer-events:none}
.spy-iris[hidden]{display:none}
.spy-iris b{position:absolute;left:50%;top:50%;transform-origin:50% 100%;background:#0e1628}
.spy-iris b:nth-child(even){background:#172238}

/* ── Night mode page (html.night-page, set by the Night mode button or ?night / ?darkmode) ──
   The header, the white section under the scene, the wave that masks the scene into it and the
   trigger guide all turn dark with the sky. Tab changes never set this: their dip to dark is
   only the scene's. Timed to the 3 s sky fade in toggleNightMode(). */
.site-head,.about,.clock,.lede,.works b,.works p,.credit,.brand,.tab,.tg-open{transition:background-color 2.4s ease,color 2.4s ease}
.hero::after{transition:background-color 2.4s ease}
html.night-page{--night-bg:#0e1823;--night-card:#16232f;--night-line:#27384a;--night-text:#dde8e6;--night-soft:#a6b8bf;--night-dim:#7d909a}
html.night-page .site-head,html.night-page .about,html.night-page .hero::after{background:var(--night-bg)}
html.night-page .brand,html.night-page .tab{color:var(--night-text)}
html.night-page .tab[aria-selected="true"]{color:rgb(var(--accent))}
html.night-page .tab:hover{background:rgba(255,255,255,.07)}
html.night-page .lede{color:var(--night-soft)}
html.night-page .works b{color:var(--night-text)}
html.night-page .works p{color:var(--night-soft)}
html.night-page .credit{color:var(--night-dim)}
html.night-page .tg-open{color:#8fd3c6}
html.night-page .decor .puff .lit{fill:var(--night-line);opacity:.9}
html.night-page .clock{background:var(--night-card);color:var(--night-soft)}
html.night-page .clock #modeBtn{background:#f2d27b;color:#1b2733}
html.night-page .tg{background:var(--night-bg);color:var(--night-text)}
html.night-page .tg-head{background:var(--night-card);border-color:var(--night-line)}
html.night-page .tg-head h2,html.night-page .tg-body h3,html.night-page .tg-rocks b{color:var(--night-text)}
html.night-page .tg-x{background:var(--night-line);color:var(--night-text)}
html.night-page .tg-lede,html.night-page .tg-rocks span,html.night-page .tg th,html.night-page .tg-note{color:var(--night-soft)}
html.night-page .tg-rocks li,html.night-page .tg code{background:var(--night-card);border-color:var(--night-line)}
html.night-page .tg th,html.night-page .tg td,html.night-page .tg figure img{border-color:var(--night-line)}
html.night-page{color-scheme:dark}
@media (prefers-reduced-motion:reduce){.site-head,.about,.clock,.lede,.works b,.works p,.credit,.brand,.tab,.tg-open,.hero::after{transition:none}}

/* Share links in the trigger guide (js/tg-links.js): label · link · Copy, stacking on phones. */
.tg-links{border:1px solid #dbe4dc;border-radius:10px;overflow:hidden;background:#fff;margin:6px 0 4px}
.tg-lgroup{padding:7px 12px;font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:#6a7a70;background:#eef3ef;border-top:1px solid #dbe4dc}
.tg-lgroup:first-child{border-top:0}
.tg-lrow{display:grid;grid-template-columns:minmax(0,14rem) minmax(0,1fr) auto;gap:4px 12px;align-items:center;padding:8px 12px;border-top:1px solid #dbe4dc}
.tg-lwhat{font-weight:700;font-size:15px;min-width:0}
.tg-lwhat small{display:block;font-weight:400;font-size:13px;color:#6a7a70}
.tg-lrow a{font-family:ui-monospace,Menlo,monospace;font-size:13.5px;color:rgb(var(--accent));text-decoration:none;overflow-wrap:anywhere;min-width:0}
.tg-lrow a:hover{text-decoration:underline}
.tg-lcopy{appearance:none;font:700 13px var(--font-display),sans-serif;color:#0f4d47;background:#eef3ef;border:1px solid #dbe4dc;border-radius:8px;padding:5px 10px;cursor:pointer}
.tg-lcopy:hover{border-color:rgb(var(--accent))}
.tg-lcopy.done{color:#fff;background:#0f4d47;border-color:#0f4d47}
.tg-lcopy:focus-visible,.tg-lrow a:focus-visible{outline:2px solid rgb(var(--accent));outline-offset:2px}
@media (max-width:560px){.tg-lrow{grid-template-columns:minmax(0,1fr) auto}.tg-lrow a{grid-column:1/-1;grid-row:2}}
html.night-page .tg-links{background:var(--night-card);border-color:var(--night-line)}
html.night-page .tg-lgroup{background:var(--night-bg);color:var(--night-soft);border-color:var(--night-line)}
html.night-page .tg-lrow{border-color:var(--night-line)}
html.night-page .tg-lwhat small{color:var(--night-soft)}
html.night-page .tg-lcopy{background:var(--night-line);color:var(--night-text);border-color:var(--night-line)}
html.night-page .tg-lcopy.done{background:#8fd3c6;color:#10202a}

/* Sun and moon glitter path on the lake (scene.js buildGlitter), after the 'Sunrise over still
   lake' pen: bars flash wide, drift a little and shrink away in turn, over a soft glow. */
.glitter{ position:absolute; left:0; bottom:0; width:100%; height:0; pointer-events:none; opacity:0; --glint: 255, 243, 204; }
.glitter i, .glitter b{ position:absolute; display:block; }
.glitter b{ background: radial-gradient(ellipse 50% 50% at 50% 30%, rgba(var(--glint), .45), rgba(var(--glint), 0) 72%); }
.glitter i{ border-radius:50%; background: linear-gradient(90deg, rgba(var(--glint), 0), rgba(var(--glint), .95) 30%, rgba(var(--glint), .95) 70%, rgba(var(--glint), 0));
  opacity:0; transform: scaleX(.15); animation: glint 4s ease-in-out infinite; }
@keyframes glint{
  0%  { opacity:0;   transform: scaleX(.15); }
  12% { opacity:1;   transform: scaleX(1); }
  32% { opacity:.75; transform: translateX(5%) scaleX(1); }
  52% { opacity:.75; transform: translateX(-5%) scaleX(1); }
  72% { opacity:0;   transform: scaleX(.2); }
  100%{ opacity:0;   transform: scaleX(.15); }
}
@media (prefers-reduced-motion:reduce){ .glitter i{ animation:none; opacity:.5; transform:none; } }

@keyframes heroRoll{ from{ translate: 0 0; } to{ translate: -50% 0; } }
@keyframes heroSwell{ from{ scale: 1 .9; } to{ scale: 1 1.08; } }
.hero-swell{ position:absolute; left:0; bottom:0; width:200%; height:24%; z-index:4; pointer-events:none;
  background: linear-gradient(rgba(255,255,255,.3), rgba(255,255,255,0) 30%);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath d='M0,40 C250,10 350,70 600,40 C850,10 950,70 1200,40 L1200,120 L0,120 Z' fill='%23fff'/%3E%3C/svg%3E") 0 0 / 50% 100% repeat-x;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath d='M0,40 C250,10 350,70 600,40 C850,10 950,70 1200,40 L1200,120 L0,120 Z' fill='%23fff'/%3E%3C/svg%3E") 0 0 / 50% 100% repeat-x;
  animation: heroRoll 75s linear infinite reverse; }
html.night-page .hero-swell{ background: linear-gradient(rgba(12,18,32,.35), rgba(12,18,32,0) 30%); }
@media (prefers-reduced-motion:reduce){ .hero::after, .hero-swell{ animation:none; } }

/* Storm (scene.js window.__storm): a darker sky and a lightning flash now and then. The shade's
   opacity is the storm's eased level, written by its loop. */
.storm-shade, .storm-flash{ position:absolute; inset:0; pointer-events:none; opacity:0; }
.storm-shade{ background: linear-gradient(rgba(28,36,52,.55), rgba(28,36,52,.2) 60%, rgba(28,36,52,.1)); }
.storm-flash{ background: rgba(235,242,255,.85); }
.storm-flash.on{ animation: bolt 1.1s ease-out; }
@keyframes bolt{ 0%{opacity:0} 4%{opacity:.8} 9%{opacity:.1} 14%{opacity:.6} 100%{opacity:0} }

/* Storm weather on the page copy (scene.js copyWeather): rain beads on the headline and button
   and drops running down the lines; in winter, snow building up on the letters and the button top,
   then melting away. Transform/opacity animations only. */
.hero-copy .view{ position:relative; }
.copy-wx{ position:absolute; inset:0; pointer-events:none; overflow:visible; transition: opacity .6s; }
.copy-wx i, .copy-wx b{ position:absolute; display:block; }
.wx-bead, .wx-drip, .wx-run{ border-radius:50% 50% 50% 50% / 45% 45% 55% 55%;
  background: radial-gradient(circle at 34% 28%, rgba(255,255,255,1) 0 14%, rgba(255,255,255,0) 24%),
              radial-gradient(circle at 50% 62%, rgba(170,205,240,.55) 0 45%, rgba(25,50,85,.7) 68%, rgba(255,255,255,.6) 80%, rgba(255,255,255,0) 86%);
  box-shadow: 0 1px 1.5px rgba(10,25,45,.35); opacity:0; }
.wx-bead{ animation: wxBead 8s ease-in-out infinite; transform-origin:50% 100%; }
@keyframes wxBead{ 0%{ opacity:0; transform:scale(.3); } 20%{ opacity:.95; transform:scale(1); } 75%{ opacity:.95; transform:scale(1); } 100%{ opacity:0; transform:scale(.8); } }
.wx-drip{ animation: wxDrip 6s linear infinite; transform-origin:50% 0; }
.wx-run{ left:0; top:0; transform-origin:50% 100%; }
.wx-flake{ left:0; top:0; border-radius:50%; transform-origin:50% 100%; opacity:0;
  background: radial-gradient(circle at 45% 40%, #fff 0 30%, #e9f1fb 52%, rgba(233,241,251,.5) 64%, rgba(233,241,251,0) 74%);
  filter: drop-shadow(0 1px 1px rgba(25,45,75,.55)); }
@keyframes wxDrip{
  0%   { opacity:0;   transform: translateY(0) scale(.2); animation-timing-function: ease-out; }
  50%  { opacity:.95; transform: translateY(0) scale(1); }
  66%  { opacity:.95; transform: translateY(2px) scale(.92, 1.4); animation-timing-function: cubic-bezier(.55, 0, 1, .45); }
  76%  { opacity:.85; transform: translateY(var(--fall, 90px)) scale(.7, 1.9); }
  80%, 100% { opacity:0; transform: translateY(calc(var(--fall, 90px) + 14px)) scale(.6, 2); }
}
}
@media (prefers-reduced-motion:reduce){ .copy-wx{ display:none; } }


/* Weather on the animals in a storm (wildlife.js weatherOnActor): flakes in winter, rain beads
   and drips otherwise, inside the animal's box so they travel with it. */
.wl-weather{ position:absolute; inset:0; pointer-events:none; }
.wl-drop{ position:absolute; display:block; opacity:0; transform-origin:50% 0; border-radius:50% 50% 50% 50% / 45% 45% 55% 55%;
  background: radial-gradient(circle at 34% 28%, rgba(255,255,255,1) 0 14%, rgba(255,255,255,0) 24%),
              radial-gradient(circle at 50% 62%, rgba(170,205,240,.5) 0 45%, rgba(25,50,85,.6) 68%, rgba(255,255,255,.55) 80%, rgba(255,255,255,0) 86%);
  box-shadow: 0 1px 1.5px rgba(10,25,45,.3); }
.wl-flake{ position:absolute; display:block; border-radius:50%; transform-origin:50% 100%; opacity:0;
  background: radial-gradient(circle at 45% 40%, #fff 0 30%, #e9f1fb 52%, rgba(233,241,251,.5) 64%, rgba(233,241,251,0) 74%);
  filter: drop-shadow(0 1px 1px rgba(25,45,75,.45)); }

/* The distant foothills are always fully opaque (their haze is a colour, not transparency). */
#bgFoothills, #foothillsWrap, #foothillsWrap *{ opacity:1 !important; fill-opacity:1 !important; }
