/* world.css — the front page below the hero, plus the light theme.
 *
 * Loaded by home.html AFTER home.css and lib/menu.css. home.css owns the
 * autumn tree hero, the auth sheet and every transition between them; this
 * file only (1) turns the page into a scrolling document while the sheet is
 * closed, (2) draws the short page under the hero (templates/_world.html):
 * the hero's dissolve, the desk, the team, the footer, plus the Affiliated slider
 * that sits in the hero under Enter Dashboard, and (3) adds a light theme to
 * both halves. Namespace: hw- (home world).
 *
 * COLOUR. Dark is the scene's own palette (home.css :root, the #09090b night
 * with the amber light). Light is the dashboard's "Riviera Terrace" parchment
 * from dashboard/static/css/tokens.css (the colour SSOT), copied here the way
 * landing.css copied it; keep the two in step when tokens.css changes. Light
 * works by redefining home.css's own tokens (--bg-*, --text-*, --accent-warm)
 * under html[data-theme="light"], so the hero, the account chip and the shared
 * menu all follow without per-rule overrides; only literal rgba() values in
 * home.css need the explicit overrides in section 3.
 *
 * SCALE. home rides a 1.25x root whose clamp floors at 20px below ~1875px
 * (see the note at the top of home.css). Like --ha-u on the top right cluster,
 * this file measures itself in its own fluid unit, --hw-u, pinned to 25px
 * (= 1.25 x 20) at the ~3072px CSS 27" reference:
 *     390 -> 15px   1280 -> 15.7px   1920 -> 19px   3072 -> 25px
 * Every size is calc(<n> * var(--hw-u)); px appear only for hairlines.
 *
 * MOTION. Reveals are armed by html.hw-js (set pre paint) and cleared by an
 * IntersectionObserver in world.js. The monitors (monitors.css) slide
 * between the two features. The slider is a pure CSS marquee. prefers-reduced-motion
 * switches every one of these off.
 */

/* ── 1. Tokens ───────────────────────────────────────────────────────────── */
:root{
  --hw-u:clamp(15px, calc(9px + 0.52vw), 25px);
  --hw-max:calc(76 * var(--hw-u));
  /* never inside a notch or rounded corner (landscape phones; the page
     sets viewport-fit=cover, so env() is live there) */
  --hw-gutter:max(clamp(16px, 5vw, 120px),
                  calc(env(safe-area-inset-left, 0px) + 16px),
                  calc(env(safe-area-inset-right, 0px) + 16px));

  --hw-line:   color-mix(in srgb, var(--text-primary) 9%, transparent);
  --hw-line-2: color-mix(in srgb, var(--text-primary) 16%, transparent);
  --hw-tint:   color-mix(in srgb, var(--text-primary) 3.5%, transparent);
  --hw-grid:   rgba(255,255,255,0.035);
  --hw-brass:  #d9b178;
  --hw-amber-glow: rgba(255,150,70,0.16);
  --hw-shadow: 0 40px 90px -40px rgba(0,0,0,0.85), 0 14px 36px -20px rgba(0,0,0,0.7);
  --hw-bar-bg: color-mix(in srgb, var(--bg-primary) 90%, transparent);
  color-scheme:dark;
}
html[data-theme="light"]{
  /* home.css tokens, re-pointed at the tokens.css light palette */
  --bg-primary:    #f4f0e6;
  --bg-secondary:  #ede8db;
  --bg-tertiary:   #e4decd;
  --bg-elevated:   #fbf8f1;
  --bg-hover:      #e4decd;
  --border-subtle: #e0d9c7;
  --border-default:#cfc7b2;
  --text-primary:  #212836;
  --text-secondary:#4a5160;
  --text-muted:    #6b7280;
  --text-xmuted:   #8a8f99;
  --accent-warm:   #8a6a33;
  --accent-warm-dim: rgba(138,106,51,0.10);
  --shadow-pop:    0 8px 24px rgba(33,40,54,.16), 0 24px 64px rgba(33,40,54,.14);

  --hw-grid:   rgba(33,40,54,0.05);
  --hw-brass:  #8a6a33;
  --hw-amber-glow: rgba(255,160,80,0.16);
  --hw-shadow: 0 30px 70px -36px rgba(33,40,54,0.38), 0 10px 24px -16px rgba(33,40,54,0.2);
  color-scheme:light;
}



/* ── 2. A scrolling document while the sheet is closed ───────────────────
   home.css pins html/body to height:100% + overflow:hidden for the one
   screen hero. The front page scrolls; the sheet (body.auth-open) locks it
   again, and home.js jumps to the top before opening so the art roll plays
   in view. The grid moves from .welcome to the canvas so hero and page
   share ONE grid (same origin, so it runs on unbroken below the hero).
   The hero is exactly one screen and clips its art at its bottom edge, but
   that edge is never seen: section 8 fades every art layer to nothing
   before it, so the hill dissolves into the page ground. */
html:has(> body.hm){ height:auto; overflow-x:hidden; overflow-y:auto; scrollbar-gutter:stable; }
html:has(> body.hm.auth-open){ overflow:hidden; }
body.hm{
  height:auto; min-height:100%;
  overflow:visible;
  display:block;
  background-color:var(--bg-primary);
  background-image:
    linear-gradient(var(--hw-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--hw-grid) 1px, transparent 1px);
  background-size:60px 60px;
}
/* The lock keeps the document at its full height: shrinking body to 100%
   clamped scrollY to 0 and threw a reader opening the sheet from further
   down back to the hero. html's overflow:hidden alone stops the scroll and
   keeps the position (see 5c). */
body.hm.auth-open{ overflow:visible; }
body.hm .main{
  height:100vh;
  /* the SMALL viewport on phones: with the browser bars showing, 100vh is
     taller than the screen and the hill (and the dissolve) sat under them */
  height:100svh;
  min-height:0;
  overflow:visible;
}
body.hm .hero-track,
body.hm #welcome{ overflow:clip; }
body.hm .welcome{ background-image:none; }
/* Phones and portrait tablets seat the hill ON the hero's bottom edge
   (home.css), which would show the bitmap's soft lower fringe (its last
   ~5.5%) ending just above the hero's edge. Sink it by 6% of the tree's
   height so the hill reaches the edge at its widest row, as on desktop
   (where the stage sits 5.15vh low, the same 5.4% of the tree), and the
   dissolve (section 8) fades that row out. */
@media screen and (max-width:800px) and (max-aspect-ratio:1426/1000){
  body.hm #welcome .tree-stage{
    background-position:calc(50% + 13.5vw + var(--m-tree) * 0.1) calc(100% - 5.15vh + var(--m-tree) * 0.06);
  }
}
/* The hero's light. The amber fan is expressed in a box 80vh taller than
   the hero; the hero clips at its edge, so only the part over the hero ever
   shows, and it fades out with the rest of the art (section 8).
   Dark, restrained (owner, 2026-09-29: the warm glow around the lower hero
   "looks muddy"): the fan keeps its place behind the wordmark but at about
   half its old strength, and its dissolve band is the tallest on the page
   (section 8), so the lower hero is the page's own clean near black by the
   time the hill meets it. No fog, no mist. */
body.hm .welcome .welcome-light{ bottom:-80vh; }
/* the ember glow behind the canopy, at about half: on phones the canopy
   sits low and the old glow read as a brown smudge behind the hero copy */
body.hm #welcome .tree-glow{
  background:radial-gradient(ellipse 60% 50% at 38% 42%,
    rgba(255, 138, 60, 0.11) 0%,
    rgba(220, 90, 40, 0.045) 30%,
    rgba(0, 0, 0, 0) 65%);
}
body.hm #welcome .welcome-light{
  background:radial-gradient(ellipse 140% 72.2% at 96% 56.67%,
    rgba(255, 210, 140, 0.30) 0%,
    rgba(255, 180, 100, 0.22) 12%,
    rgba(255, 150, 70,  0.14) 26%,
    rgba(255, 120, 50,  0.08) 42%,
    rgba(255,  95, 35,  0.035) 60%,
    rgba(255,  80, 25,  0.012) 76%,
    rgba(0, 0, 0, 0)            90%);
}
body.hm #welcome-eva .welcome-light{
  background:radial-gradient(ellipse 135% 69.4% at 98% 57.8%,
    rgba(255, 176, 206, 0.34) 0%,
    rgba(255, 150, 190, 0.23) 16%,
    rgba(255, 135, 178, 0.13) 36%,
    rgba(250, 165, 200, 0.06) 58%,
    rgba(0, 0, 0, 0)          82%);
}
@media (prefers-reduced-motion: no-preference){
  html:has(> body.hm){ scroll-behavior:smooth; }
}

/* ── 3. Light theme: hero literals ───────────────────────────────────────
   Everything home.css draws from tokens already followed section 1. These
   are the hard-coded rgba() values that assumed a dark ground. */
html[data-theme="light"] body{ -webkit-font-smoothing:auto; }
html[data-theme="light"] .welcome .tree-vignette{
  background:radial-gradient(ellipse at center, rgba(0,0,0,0) 55%, rgba(120,90,50,0.10) 100%);
}
/* Screen blending adds light, which a parchment ground cannot show, and
   multiply only darkens it (it read as a grey smudge on phones). Lay the
   same warm fan on with plain alpha instead: paper warms, never greys. */
html[data-theme="light"] body.hm #welcome .welcome-light{
  mix-blend-mode:normal;
  background:radial-gradient(ellipse 140% 72.2% at 96% 56.67%,
    rgba(255,186,120,0.30) 0%,
    rgba(255,200,145,0.18) 26%,
    rgba(255,214,170,0.08) 50%,
    rgba(255,230,200,0) 80%);
}
html[data-theme="light"] body.hm #welcome-eva .welcome-light{
  mix-blend-mode:normal;
  background:radial-gradient(ellipse 135% 69.4% at 98% 57.8%,
    rgba(255,170,205,0.26) 0%,
    rgba(255,195,220,0.12) 34%,
    rgba(255,220,235,0) 78%);
}
/* the ember glow on parchment: fainter still, or it prints a peach smudge
   behind the hero copy where phones leave the canopy low */
html[data-theme="light"] body.hm #welcome .tree-glow{
  background:radial-gradient(ellipse 60% 50% at 38% 42%,
    rgba(255, 150, 80, 0.07) 0%,
    rgba(240, 120, 60, 0.025) 30%,
    rgba(0, 0, 0, 0) 65%);
}
html[data-theme="light"] .welcome .tree-leaf img{
  filter:drop-shadow(0 5px 7.5px rgba(80,50,20,0.22));
}
html[data-theme="light"] .home-auth-link{
  --fb-ring:rgba(33,40,54,0.28);
  --fb-ink:rgba(33,40,54,0.82);
  --fb-fill:#212836;
  --fb-ink-on:#f4f0e6;
}
html[data-theme="light"] .home-auth-cta{
  --fb-ring:rgba(138,106,51,0.55);
  --fb-ink:#7a5a26;
  --fb-fill:#8a6a33;
  --fb-ink-on:#f4f0e6;
}
html[data-theme="light"] .home-account-chip{
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--text-primary) 30%, transparent),
    0 8px 24px rgba(33,40,54,0.14);
}
html[data-theme="light"] .fbtn--lancer{
  color:#6f5122;
  background:rgba(255,251,242,0.55);
  box-shadow:inset 0 0 0 1px rgba(138,106,51,0.5);
}
html[data-theme="light"] .fbtn--lancer ~ .fbtn-corner{ border-color:rgba(138,106,51,0.6); }
html[data-theme="light"] .fbtn-wrap:hover .fbtn--lancer{
  color:#5a4119;
  background:rgba(255,248,232,0.8);
  box-shadow:inset 0 0 0 1px rgba(138,106,51,0.7);
}
html[data-theme="light"] .fbtn-wrap:hover .fbtn--lancer ~ .fbtn-corner{ border-color:rgba(138,106,51,0.85); }

/* ── 4. Theme switch (top right cluster, --ha-u scale) ─────────────────── */
.hw-theme{
  display:inline-flex; align-items:center; justify-content:center;
  width:calc(1.8125 * var(--ha-u)); height:calc(1.8125 * var(--ha-u));
  padding:0; border:none; border-radius:50%;
  background:transparent;
  color:color-mix(in srgb, var(--text-primary) 72%, transparent);
  box-shadow:inset 0 0 0 1.5px color-mix(in srgb, var(--text-primary) 22%, transparent);
  cursor:pointer;
  transition:color .2s var(--ease-out), background .2s var(--ease-out), transform .15s var(--ease-out);
}
.hw-theme:hover{ color:var(--bg-primary); background:var(--text-primary); }
.hw-theme:active{ transform:scale(0.95); }
.hw-theme:focus{ outline:none; }
.hw-theme:focus-visible{ box-shadow:inset 0 0 0 1.5px var(--text-primary), 0 0 0 4px color-mix(in srgb, var(--text-primary) 18%, transparent); }
.hw-theme svg{
  width:calc(0.85 * var(--ha-u)); height:calc(0.85 * var(--ha-u));
  fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round;
}
.hw-ico-sun{ display:none; }
html[data-theme="light"] .hw-ico-sun{ display:block; }
html[data-theme="light"] .hw-ico-moon{ display:none; }
@media screen and (max-width:800px) and (max-aspect-ratio:1426/1000){
  .hw-theme{ width:44px; height:44px; }
  .hw-theme svg{ width:18px; height:18px; }
}

/* ── 5. Top bar (after the hero has scrolled away) ─────────────────────── */
.hw-bar{
  --ha-u:clamp(16px, calc(8px + 0.55vw), 25px);
  position:fixed; top:0; left:0; right:0; z-index:45;
  height:calc(50px + 1.8125 * var(--ha-u));
  display:flex; align-items:center;
  padding:0 max(30px, calc(env(safe-area-inset-right, 0px) + 16px)) 0 max(30px, calc(env(safe-area-inset-left, 0px) + 16px));
  background:var(--hw-bar-bg);
  box-shadow:0 1px 0 var(--hw-line);
  transform:translateY(-102%);
  transition:transform 380ms var(--ease-out);
}
body.hw-scrolled:not(.auth-open) .hw-bar{ transform:none; }
.hw-bar-brand{
  font-family:var(--font-brand);
  /* 1.4x the first cut (owner, 2026-09-29: "make the logo bigger") */
  font-size:calc(1.47 * var(--ha-u)); font-weight:700;
  letter-spacing:0.14em;
  color:var(--text-primary); text-decoration:none;
}
@media screen and (max-width:800px) and (max-aspect-ratio:1426/1000){
  .hw-bar{ height:calc(72px + env(safe-area-inset-top, 0px)); padding:env(safe-area-inset-top, 0px) 16px 0; }
  .hw-bar-brand{ font-size:21px; }
}
@media (prefers-reduced-motion: reduce){ .hw-bar{ transition-duration:0.01ms; } }

/* ── 6. Hero scroll cue ───────────────────────────────────────────────── */
#welcome .hw-cue{
  position:absolute; z-index:4;
  bottom:calc(22px + env(safe-area-inset-bottom, 0px));
  right:calc(clamp(20px, 1.8vw, 40px) + min(750px, 41vw) / 2);
  transform:translateX(50%);
  display:flex; flex-direction:column; align-items:center; gap:10px;
  padding:8px 12px;
  text-decoration:none;
  opacity:0;
  transition:opacity 500ms ease;
}
body.tree-ready:not(.auth-open):not(.hw-scrolled) #welcome .hw-cue{ opacity:1; transition-delay:1400ms; }
#welcome .hw-cue:hover{ color:var(--text-secondary); }
/* Landscape phones: no room under the copy; the cue printed over the
   Affiliated slider. The page plainly continues, so it steps out. */
@media screen and (max-height:520px) and (min-aspect-ratio:1/1){ #welcome .hw-cue{ display:none; } }
.hw-cue-line{
  position:relative; width:1px; height:36px; overflow:hidden;
  background:color-mix(in srgb, var(--text-primary) 16%, transparent);
}
.hw-cue-line::after{
  content:""; position:absolute; left:0; top:-40%; width:1px; height:40%;
  background:var(--hw-brass);
  animation:hw-cue 2.4s cubic-bezier(0.45, 0, 0.15, 1) infinite;
}
@keyframes hw-cue{ to{ top:100%; } }
@media screen and (max-width:1250px) and (max-aspect-ratio:1426/1000){
  #welcome .hw-cue{ display:none; }
}
@media (prefers-reduced-motion: reduce){ .hw-cue-line::after{ animation:none; top:30%; } }


/* ── 7. World shell ───────────────────────────────────────────────────── */
.hw, .hw-foot{
  position:relative;
  font-family:var(--font-sans);
  letter-spacing:var(--tr-body);
  color:var(--text-primary);
}
.hw-wrap{
  width:100%; max-width:calc(var(--hw-max) + 2 * var(--hw-gutter));
  margin:0 auto; padding:0 var(--hw-gutter);
}
.hw{ overflow-x:clip; }
.hw [hidden]{ display:none !important; }
.hw a{ color:inherit; }

/* TYPE. One family (Geist, home.css :root) in three weights, and a short
   scale on --hw-u:
     section heading  .hw-h2          600, ~2.1u, tight
     block title      .hw-title       600, 1.25u  (Team, For firms)
     body             .hw-body        400, 1.0625u, at most 60ch a line
     small            names, roles, footer, 500/400 at 0.8 to 1u
   and ONE tiny label style, .hw-label (Geist Mono), used only where a word
   has to sit small beside something bigger: "Affiliated" and the scroll cue
   in the hero. No other uppercase or tracked-out text on the page. */
.hw-label{
  font-family:var(--font-mono);
  font-size:11px; font-weight:500;
  letter-spacing:0.12em; text-transform:uppercase;
  line-height:1.4;
  color:var(--text-muted);
}
.hw-h2{
  font-family:var(--font-display);
  font-size:clamp(28px, calc(2.1 * var(--hw-u)), 52px);
  font-weight:600; line-height:1.08;
  letter-spacing:-0.028em;
  color:var(--text-primary);
  text-wrap:balance;
}
.hw-title{
  font-family:var(--font-display);
  font-size:calc(1.25 * var(--hw-u)); font-weight:600; line-height:1.2;
  letter-spacing:-0.018em;
  color:var(--text-primary);
}
.hw-body{
  max-width:60ch;
  font-size:calc(1.0625 * var(--hw-u));
  line-height:1.6;
  color:var(--text-secondary);
  text-wrap:pretty;
}


/* ── 8. The dissolve (bottom of the hero) ───────────────────────────────
   No line and no edge between the hero and the page (owner, 2026-09-29):
   every art layer of the tree scene is masked to nothing over a band that
   ends exactly at the hero's bottom edge, so the hill, the warm light and
   the falling leaves melt into the page ground (and its grid, which runs
   under the hero too) instead of being cut. Masks, not an overlay: an
   overlay would paint the page colour over the grid and leave a step where
   the grid resumes.
   Each layer states where the hero's edge sits in its OWN box (--hw-edge,
   measured up from the box's bottom) and how tall its fade is (--hw-band):
     tree stage, leaves   the box sits 5.15vh low (home.css). The band is a
                          ninth of the TREE's own height (home.css sizes the
                          bitmap min(95% of the hero, 66.6vw)): the hill under
                          the shield, on every shape of screen, so the knight
                          and his shield stay whole (owner, 09-29: "it starts
                          disappearing too soon")
     glow, vignette       the box is the hero
     warm light           the box runs 80vh below the hero (section 2)
   Two curves, both slow at the start and soft at the landing so no band
   edge shows: the light layers over a tall band, the art over its short
   one.
   Static, so reduced motion needs nothing.
   The tree's band was a ninth of its height; owner, 09-29 (later): "fade
   away a little faster", so it is now 0.15 of it, still wholly under the
   shield.
   THE BRIDGE (the login scene, #welcome-eva) dissolves the same way: its
   bottom edge is the bottom of the screen both when the sheet opens from
   the top and when the hero is lifted out over the page (5c: the lifted
   track is fixed, inset 0, the same box). Its art box is transformed
   (translate(-20.5vw, 25.15vh) scale(0.95) about the box centre, home.css)
   and a mask is laid out BEFORE the transform, so the screen's bottom edge
   sits 23.84vh up from the box's bottom in the box's own units
   ((100 - 25.15 - 50) / 0.95 + 50 = 76.16vh from the top), and the band is
   the tree's band over the scale. Phones move the box (home.css, the
   bottom sheet): their edge is set in the phone block below. */
body.hm #welcome .tree-stage,
body.hm #welcome .leaf-field{ --hw-edge:5.15vh; --hw-band:calc(min(95vh, 66.6vw) * 0.15); }
body.hm #welcome .tree-glow,
body.hm #welcome .tree-vignette{ --hw-edge:0px; --hw-band:42vh; }
body.hm #welcome .welcome-light{ --hw-edge:80vh; --hw-band:62vh; }
body.hm #welcome-eva .tree-stage{ --hw-edge:23.84vh; --hw-band:calc(min(95vh, 66.6vw) * 0.15 / 0.95); }
body.hm #welcome-eva .petal-field{ --hw-edge:0px; --hw-band:calc(min(95vh, 66.6vw) * 0.15); }
body.hm #welcome-eva .tree-glow{ --hw-edge:0px; --hw-band:42vh; }
body.hm #welcome-eva .welcome-light{ --hw-edge:80vh; --hw-band:62vh; }
body.hm #welcome .tree-stage,
body.hm #welcome .leaf-field,
body.hm #welcome .tree-glow,
body.hm #welcome .tree-vignette,
body.hm #welcome .welcome-light,
body.hm #welcome-eva .tree-stage,
body.hm #welcome-eva .petal-field,
body.hm #welcome-eva .tree-glow,
body.hm #welcome-eva .welcome-light{
  --hw-e:calc(100% - var(--hw-edge));
  /* the light layers: a slow start and a soft landing over a tall band */
  --hw-dissolve:linear-gradient(to bottom,
    #000 calc(var(--hw-e) - var(--hw-band)),
    rgba(0,0,0,0.95) calc(var(--hw-e) - var(--hw-band) * 0.82),
    rgba(0,0,0,0.82) calc(var(--hw-e) - var(--hw-band) * 0.64),
    rgba(0,0,0,0.6)  calc(var(--hw-e) - var(--hw-band) * 0.46),
    rgba(0,0,0,0.36) calc(var(--hw-e) - var(--hw-band) * 0.3),
    rgba(0,0,0,0.15) calc(var(--hw-e) - var(--hw-band) * 0.15),
    rgba(0,0,0,0.04) calc(var(--hw-e) - var(--hw-band) * 0.05),
    transparent var(--hw-e));
  -webkit-mask-image:var(--hw-dissolve);
          mask-image:var(--hw-dissolve);
}
/* the art: a short band that sits entirely under the knight. His shield
   ends just above the hill, so the band is 0.15 of the tree's height (the
   hill below the shield) and the curve runs slow, fast, slow across it: the
   knight stands whole and the hill melts into the page. The bridge and its
   petals use the same curve. */
body.hm #welcome .tree-stage,
body.hm #welcome .leaf-field,
body.hm #welcome-eva .tree-stage,
body.hm #welcome-eva .petal-field{
  --hw-dissolve:linear-gradient(to bottom,
    #000 calc(var(--hw-e) - var(--hw-band)),
    rgba(0,0,0,0.97) calc(var(--hw-e) - var(--hw-band) * 0.8),
    rgba(0,0,0,0.9)  calc(var(--hw-e) - var(--hw-band) * 0.65),
    rgba(0,0,0,0.75) calc(var(--hw-e) - var(--hw-band) * 0.5),
    rgba(0,0,0,0.58) calc(var(--hw-e) - var(--hw-band) * 0.38),
    rgba(0,0,0,0.4)  calc(var(--hw-e) - var(--hw-band) * 0.27),
    rgba(0,0,0,0.25) calc(var(--hw-e) - var(--hw-band) * 0.18),
    rgba(0,0,0,0.12) calc(var(--hw-e) - var(--hw-band) * 0.1),
    rgba(0,0,0,0.04) calc(var(--hw-e) - var(--hw-band) * 0.04),
    transparent var(--hw-e));
}

/* Phones and portrait tablets: the bridge's box runs 60vh above and below
   the screen (home.css, the bottom sheet), 220vh tall about the same
   centre, so the screen's bottom edge sits 83.84vh up from the box's
   bottom ((100 - 25.15 - 50) / 0.95 + 110 = 136.16vh from its top). The
   sheet covers that edge there; this keeps the dissolve where it belongs
   if the sheet is ever shorter. */
@media screen and (max-width:800px) and (max-aspect-ratio:1426/1000){
  body.hm #welcome-eva .tree-stage{ --hw-edge:83.84vh; }
}

/* ── 9. Affiliated slider (hero, under Enter Dashboard) ───────────────────
   After 21st.dev @ibelick/infinite-slider, as a CSS marquee: the track holds
   two identical sets (each with a trailing gap equal to the inner gap), so
   sliding it by -50% lands copy two exactly where copy one began. It never
   stops (owner, 2026-09-29): no pause on hover, focus or click. The logos
   are not links, so the whole view is inert to the pointer (no hover state,
   no text selection on a click or drag, no tap flash) and nothing in it can
   take focus. Only reduced motion holds it still. The view fades out at
   both ends. Sized to the button area;
   rides the hero content's own entrance and step aside (home.css). */
.hw-aff{
  --hw-aff-gap:clamp(22px, 2.2vw, 44px);
  -webkit-user-select:none; user-select:none;
  width:clamp(260px, 26vw, 460px);
  max-width:100%;
  margin-top:clamp(26px, 4.4vh, 60px);
}
.hw-aff-cap{
  margin:0 0 clamp(8px, 1.1vh, 14px);
  text-align:center;
}
.hw-aff-view{
  overflow:hidden;
  pointer-events:none;
  -webkit-user-select:none; user-select:none;
  -webkit-tap-highlight-color:transparent;
  -webkit-mask-image:linear-gradient(90deg, transparent 0%, #000 20%, #000 80%, transparent 100%);
          mask-image:linear-gradient(90deg, transparent 0%, #000 20%, #000 80%, transparent 100%);
}
.hw-aff-track{
  display:flex; width:max-content;
  animation:hw-aff-loop 26s linear infinite;
}
@keyframes hw-aff-loop{ from{ transform:translateX(0); } to{ transform:translateX(-50%); } }
.hw-aff-set{
  list-style:none; margin:0; padding:0 var(--hw-aff-gap) 0 0;
  display:flex; align-items:center; gap:var(--hw-aff-gap);
  flex:none;
}
.hw-aff-item{
  display:flex; align-items:center; gap:clamp(7px, 0.55vw, 11px);
  white-space:nowrap;
  color:var(--text-secondary);
}
.hw-aff-logo{
  flex:none; display:block;
  width:clamp(18px, 1.35vw, 26px); height:clamp(18px, 1.35vw, 26px);
  background-color:currentColor;
  -webkit-mask:var(--logo) center / contain no-repeat;
          mask:var(--logo) center / contain no-repeat;
  opacity:0.85;
}
.hw-aff-name{
  font-size:clamp(12.5px, 0.82vw, 16px);
  font-weight:500; letter-spacing:0; line-height:1.3;
}
/* entrance: the same rise as the button, a beat later */
.welcome .hw-aff{ opacity:0; }
.welcome.is-active .hw-aff{
  animation:welcome-piece-in 720ms cubic-bezier(0.22, 1, 0.36, 1) 620ms forwards;
}
@media screen and (max-width:1250px) and (max-aspect-ratio:1426/1000){
  .hw-aff{ width:min(380px, 78vw); }
}
@media screen and (max-width:775px) and (max-aspect-ratio:1426/1000){
  .hw-aff{ width:min(320px, 84vw); margin-top:clamp(18px, 2.8vh, 28px); }
  .hw-aff-name{ font-size:12px; }
  .hw-aff-logo{ width:17px; height:17px; }
}
@media (prefers-reduced-motion: reduce){
  .welcome.is-active .hw-aff{ animation:none; opacity:1; }
  .hw-aff-track{ animation:none; width:auto; }
  .hw-aff-view{ -webkit-mask-image:none; mask-image:none; }
  .hw-aff-set{ flex-wrap:wrap; justify-content:center; gap:8px 18px; padding:0; width:100%; }
  .hw-aff-set[aria-hidden="true"]{ display:none; }
}

/* ── 10. The product monitors ────────────────────────────────────────────
   In static/css/monitors.css (one big pre rendered monitor per feature and
   the arrows), linked after this file. */

/* ── 10b. For firms ─────────────────────────────────────────────────────
   One line of contact under the team: licences are arranged directly. A
   quiet tinted strip, the mail link as an outlined pill. */
.hw-firms{ padding:calc(2.4 * var(--hw-u)) 0 calc(3.2 * var(--hw-u)); }
.hw-firms-box{
  max-width:calc(34 * var(--hw-u)); margin:0 auto;
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:calc(1 * var(--hw-u)) calc(2 * var(--hw-u));
  padding:calc(1.4 * var(--hw-u)) calc(1.4 * var(--hw-u));
  background:var(--hw-tint);
  box-shadow:inset 0 0 0 1px var(--hw-line);
}
.hw-firms-box > div{ flex:1 1 18em; min-width:0; }
.hw-firms-line{
  margin-top:calc(0.35 * var(--hw-u));
  max-width:60ch;
  font-size:calc(0.95 * var(--hw-u)); line-height:1.55;
  color:var(--text-secondary);
  text-wrap:balance;
}
.hw .hw-firms-cta{
  flex:none;
  display:inline-flex; align-items:center; min-height:44px;
  padding:0 calc(1.3 * var(--hw-u));
  font-size:calc(0.9 * var(--hw-u)); font-weight:500;
  color:var(--text-primary); text-decoration:none;
  border-radius:999px;
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--text-primary) 28%, transparent);
  transition:background-color var(--dur-fast) ease, color var(--dur-fast) ease;
}
.hw .hw-firms-cta:hover{ background:var(--text-primary); color:var(--bg-primary); }
.hw .hw-firms-cta:focus-visible{
  outline:none;
  box-shadow:inset 0 0 0 1px var(--hw-brass), 0 0 0 3px color-mix(in srgb, var(--hw-brass) 35%, transparent);
}

/* ── 11. Footer ───────────────────────────────────────────────────────── */
.hw-foot{ border-top:1px solid var(--hw-line); padding:calc(1.6 * var(--hw-u)) 0 calc(1.8 * var(--hw-u) + env(safe-area-inset-bottom, 0px)); }
.hw-foot-row{ display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:calc(1 * var(--hw-u)); }
.hw-foot-brand{ display:flex; align-items:center; }
/* the top bar's lockup (5b) at the footer's scale: the word, then the
   knight pulled in over the webm's transparent left margin */
.hw-foot-lockup{ display:inline-flex; align-items:center; line-height:1; font-size:calc(1.05 * var(--hw-u)); }
.hw-foot-word{ display:block; line-height:1; font-family:var(--font-brand); font-weight:700; letter-spacing:0.14em; color:var(--text-primary); }
.hw-foot-knight{
  display:block; flex-shrink:0;
  height:2.1em; width:auto; aspect-ratio:3 / 2;
  margin:-0.5em 0 -0.5em -0.65em;
}
.hw-foot-links{ display:flex; flex-wrap:wrap; gap:calc(0.4 * var(--hw-u)) calc(1.3 * var(--hw-u)); }
.hw-foot-links a{
  font-size:calc(0.8 * var(--hw-u)); color:var(--text-muted); text-decoration:none;
  padding:6px 0;
  transition:color var(--dur-fast) var(--ease-out);
}
.hw-foot-links a:hover{ color:var(--text-primary); }

/* ── 12. Reveals ──────────────────────────────────────────────────────── */
html.hw-js .hw-reveal{
  opacity:0; transform:translateY(22px);
  transition:opacity 800ms var(--ease-out), transform 900ms var(--ease-out);
}
html.hw-js .hw-reveal.is-in{ opacity:1; transform:none; }

@media (prefers-reduced-motion: reduce){
  html.hw-js .hw-reveal{ opacity:1; transform:none; transition:none; }
}
@media (max-width:640px){
  .hw-foot-row{ flex-direction:column; align-items:flex-start; }
}

/* ── 13. Team ─────────────────────────────────────────────────────────
   One heading over a hairline, then square portraits with name, role and
   university below. Flat and square like the rest of home; the placeholder
   is a neutral silhouette in the ink colour, never a face. The portraits
   are square files shown at their own framing (object-fit on a square box,
   no scale, no zoom on hover: only the colour treatment lifts). */
.hw-team{ padding:calc(3 * var(--hw-u)) 0 calc(1 * var(--hw-u)); }
.hw-team-box{ max-width:calc(34 * var(--hw-u)); margin:0 auto; }
.hw-team-head{
  padding-bottom:calc(0.8 * var(--hw-u));
  box-shadow:inset 0 -1px 0 var(--hw-line);
}
.hw-team-grid{
  list-style:none;
  display:grid; grid-template-columns:repeat(2, minmax(0, 1fr));
  gap:calc(2 * var(--hw-u)) calc(1.4 * var(--hw-u));
  margin-top:calc(1.5 * var(--hw-u));
}
html.hw-js .hw-member.hw-reveal{ transition-delay:calc(var(--i, 0) * 120ms); }
.hw-member-photo{
  position:relative; aspect-ratio:1 / 1; overflow:hidden;
  background:
    radial-gradient(ellipse 70% 60% at 50% 100%, color-mix(in srgb, var(--hw-brass) 14%, transparent), transparent 70%),
    var(--bg-secondary);
  box-shadow:inset 0 0 0 1px var(--hw-line-2);
}
.hw-member-photo img{
  display:block; width:100%; height:100%; object-fit:cover;
  /* one quiet treatment for both portraits, so two different skies read
     as a pair: a little less colour, a touch more contrast, and a soft
     shade at the foot. Full colour on hover. */
  filter:saturate(0.78) contrast(1.03);
  transition:filter 400ms ease;
}
.hw-member:hover .hw-member-photo img{ filter:none; }
.hw-member-photo::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(to bottom, transparent 62%, rgba(0,0,0,0.16));
  box-shadow:inset 0 0 0 1px var(--hw-line-2);
}
html[data-theme="light"] .hw-member-photo::after{
  background:linear-gradient(to bottom, transparent 62%, rgba(33,40,54,0.1));
}
.hw-member-ph{
  position:absolute; left:50%; bottom:0; transform:translateX(-50%);
  width:62%; height:auto;
  fill:color-mix(in srgb, var(--text-primary) 13%, transparent);
  stroke:none;
}
.hw-member-meta{
  display:flex; align-items:flex-start; justify-content:space-between; gap:calc(0.8 * var(--hw-u));
  margin-top:calc(0.9 * var(--hw-u));
}
.hw-member-name{
  font-family:var(--font-display);
  font-size:calc(1.05 * var(--hw-u)); font-weight:600; line-height:1.3; letter-spacing:-0.012em;
  color:var(--text-primary);
}
.hw-member-role{ margin-top:2px; font-size:calc(0.9 * var(--hw-u)); color:var(--text-secondary); }
.hw-member-line{
  margin-top:calc(0.35 * var(--hw-u));
  font-size:calc(0.8 * var(--hw-u)); line-height:1.45;
  color:var(--text-muted);
  text-wrap:pretty;
}
.hw-member-link{
  flex-shrink:0;
  display:inline-flex; align-items:center; justify-content:center;
  width:40px; height:40px;
  color:var(--text-muted);
  box-shadow:inset 0 0 0 1px var(--hw-line-2);
  transition:color 200ms ease, background 200ms ease;
}
.hw-member-link:hover{ color:var(--bg-primary); background:var(--text-primary); }
.hw-member-link svg{ width:16px; height:16px; fill:currentColor; }
@media (max-width:420px){
  .hw-team-grid{ gap:calc(1.6 * var(--hw-u)) calc(0.9 * var(--hw-u)); }
}

/* ══ Theme fade + the light Cavalry mark ═══════════════════════════════════
   Owned by world.js "Theme switch". Kept as one block at the end of the file
   so it layers over everything above without editing it.

   THE MARK. Every Cavalry knight on the page comes as a pair: .cv-mark--dark
   (the white knight1.webm) and .cv-mark--light (light_knight.png, the black
   line art the dashboard's light sidebar shows; the master lives in
   dashboard/static and home/static holds a byte identical copy). Exactly one
   of the pair is displayed per theme. Only the INACTIVE one is set here
   (display:none), so each mark keeps its own display value and layout.
   A new logo anywhere on the page joins by carrying the same two classes.

   The hero pair shares every sizing rule in home.css (`.welcome-wordmark
   video, .welcome-wordmark .brand-img`), both files are 3:2, so the lockup
   lands on the same pixels in either theme. Their entrance pop (ch-logo-in)
   would REPLAY when display:none lifts, so world.js marks the wordmark
   .hw-mark-settled on the first switch after the entrance has run, which
   pins both at rest. */
html[data-theme="light"] .cv-mark--dark,
html:not([data-theme="light"]) .cv-mark--light{ display:none; }
.welcome .welcome-wordmark.hw-mark-settled .cv-mark{ animation:none; opacity:1; }
/* The still mark does not play on hover, so it does not promise to. */
.welcome-wordmark .brand-img.cv-mark{ cursor:auto; }

/* THE FADE. Where the View Transitions API exists world.js flips the theme
   inside document.startViewTransition, and the browser cross fades a
   snapshot of the old page into the live new one: colours, gradients, the
   scene layers, the screenshots and the marks all in one pass, no flash and
   no layout work. html.hw-vt is on only for that transition, so no other
   (future) view transition inherits this timing. */
html.hw-vt::view-transition-old(root),
html.hw-vt::view-transition-new(root){
  animation-duration:340ms;
  animation-timing-function:cubic-bezier(0.4, 0, 0.2, 1);
}
/* Fallback, no View Transitions: colour properties transition for the length
   of the switch only (html.hw-theme-fading, added and removed by world.js),
   so hover and scroll never pay for it. Gradients and bitmaps cannot
   interpolate; the bitmaps that swap (screenshots, the mark) fade in from
   @starting-style instead. !important because it must beat every element's
   own transition list for those 340ms, and only for those. */
html.hw-theme-fading,
html.hw-theme-fading *,
html.hw-theme-fading *::before,
html.hw-theme-fading *::after{
  transition-property:background-color, color, border-color, outline-color,
    text-decoration-color, fill, stroke, box-shadow, opacity !important;
  transition-duration:340ms !important;
  transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1) !important;
  transition-delay:0s !important;
}
@starting-style{
  html.hw-theme-fading .hw-mon-img,
  html.hw-theme-fading .cv-mark,
  html.hw-theme-fading .welcome .welcome-wordmark.hw-mark-settled .cv-mark{ opacity:0; }
}
/* world.js already switches instantly under reduced motion; this covers a
   preference changed while a transition is in flight. */
@media (prefers-reduced-motion: reduce){
  html.hw-vt::view-transition-old(root),
  html.hw-vt::view-transition-new(root){ animation:none; }
  html.hw-theme-fading,
  html.hw-theme-fading *,
  html.hw-theme-fading *::before,
  html.hw-theme-fading *::after{ transition:none !important; }
}


/* ── 5b. Top bar lockup: CAVALRY + the knight ─────────────────────────────
   The hero's wordmark in miniature (home.css .welcome-wordmark): word first,
   knight after, the knight taller than the caps and pulled in over the
   WebM's transparent left margin (~25.6% of its 1.5:1 frame) so the visible
   rider sits close to the Y. Sized off the bar's own font size, so it tracks
   every width the word does. The knight never takes the click: the link does.
   Both marks of the pair (.cv-mark, see the theme block above) share these
   rules; world.css hides the one the theme does not use. */
.hw-bar-brand{
  display:inline-flex; align-items:center;
  line-height:1;
}
.hw-bar-word{ display:block; line-height:1; }
.hw-bar-knight{
  display:block; flex-shrink:0;
  /* frame 2.1em tall: the rider's ink (59% of the frame) is ~1.25em, a touch
     over the caps, as in the hero. The left pull lands the ink ~0.16em past
     the word's trailing tracking, the hero's gap at this scale. */
  height:2.1em; width:auto; aspect-ratio:3 / 2;
  margin:-0.5em 0 -0.5em -0.65em;
  pointer-events:none;
}
/* Phones: the pills + theme button take most of the bar, so the lockup
   tightens its tracking to keep clear of them, and below 380px (where even
   the bare word ran under the pills) the knight carries the brand alone. */
@media screen and (max-width:440px){
  .hw-bar-brand{ font-size:18px; letter-spacing:0.08em; }
}
/* 381 to 400px (most phones): with Log in + Join on the right the 18px
   lockup ran into them, so it steps down a little (still above the old 13px) */
@media screen and (max-width:400px){
  .hw-bar-brand{ font-size:15px; }
}
@media screen and (max-width:380px){
  .hw-bar-brand{ font-size:20px; }
  .hw-bar-word{ display:none; }
  .hw-bar-knight{ margin-left:-0.8em; }
}

/* ── 5c. Auth sheet opened in place (below the hero) ─────────────────────
   From the top, body.auth-open rolls the hero art: the tree (#welcome) goes
   off to the RIGHT and the rose bridge (#welcome-eva) follows it in from the
   left, 1400ms on one curve (home.css). Opened further down, the SAME motion
   plays over whatever is on screen (owner, 2026-09-29: the old dissolve read
   as a jump):
     1. home.js stamps --hw-sy (scrollY) and body.auth-inplace. The hero track
        goes fixed over the viewport, BELOW the page (z 1 vs 2), with the tree
        scene kept exactly where it sat in the flow (top: -scrollY), so
        nothing on screen changes on that frame. The art is still in its
        pre-open pose: the bridge waits off to the left of the viewport.
     2. auth-open then runs the hero's own transitions, untouched: the bridge
        rolls in, the tree (if any of it is on screen) rolls out, and the page
        itself (.hw, the footer, the top bar) slides right by the tree's own
        throw (--hw-roll) on the tree's own curve and length, a transform
        only, so layout and scrollY never move. The sheet follows as it does
        from the top.
     3. Closing drops auth-open first: everything rolls back the same way;
        auth-inplace comes off once the roll has landed (home.js), when the
        lifted tree sits exactly on its place in the flow again.
   #hero-main keeps its 100vh in the flow while the track is lifted, so the
   document height, and scrollY, never change (html's lock, section 2; held
   through the return roll by the auth-inplace lock below). */
body.hm{ --hw-roll:113.5vw; }          /* -13.5vw rest to 100vw rolled away */
@media screen and (max-width:1250px) and (max-aspect-ratio:1426/1000){
  body.hm{ --hw-roll:178.5vw; }        /* phones and tablets throw the tree to 165vw */
}
html:has(> body.hm.auth-inplace){ overflow:hidden; }
/* The page rolled off to the right must not count as overflow: a phone
   widens its layout viewport to fit overflowing content (it zoomed out and
   clamped scrollY once the roll landed). Clip the x axis at the body. */
body.hm.auth-inplace{ overflow-x:clip; }
body.auth-inplace .hero-track{
  position:fixed; inset:0; z-index:1;
}
body.auth-inplace #welcome{
  top:calc(-1 * var(--hw-sy, 0px));
  bottom:auto;
  /* the hero's own height (section 2), not the fixed layer's: on a phone
     the two differ by the browser bars */
  height:100vh; height:100svh;
}
body.hm.auth-inplace .hw,
body.hm.auth-inplace .hw-foot{
  z-index:2;
  transform:translate3d(0, 0, 0);
  transition:transform 1400ms cubic-bezier(0.45, 0, 0.15, 1);
}
body.hm.auth-inplace.hw-scrolled .hw-bar{
  transform:translate3d(0, 0, 0);
  transition:transform 1400ms cubic-bezier(0.45, 0, 0.15, 1);
}
body.hm.auth-inplace.auth-open .hw,
body.hm.auth-inplace.auth-open .hw-foot,
body.hm.auth-inplace.auth-open.hw-scrolled .hw-bar{
  transform:translate3d(var(--hw-roll), 0, 0);
}
/* The lock hides the scrollbar, and scrollbar-gutter:stable (section 2) keeps
   its lane so nothing shifts sideways. The lane sits beside the sheet's right
   edge, so it takes the sheet's white instead of the page grid. */
html:has(> body.hm.auth-open){ background:#ffffff; }
@media (prefers-reduced-motion: reduce){
  body.hm.auth-inplace .hw,
  body.hm.auth-inplace .hw-foot,
  body.hm.auth-inplace.hw-scrolled .hw-bar{ transition-duration:0.01ms; transition-delay:0ms; }
}


/* ── 13. Touch targets (coarse pointers) ─────────────────────────────────
   Responsive audit, 2026-09-29: on tablets and landscape phones the desktop
   sizes applied (the phone block only fires in portrait under 800px), and
   a few controls were under 44px everywhere on touch. Every control a
   finger uses gets a 44px box; the visible size changes only where it has
   to. The account menu rows are the shared MPMenu primitive (menu.css is a
   byte copy of the dashboard's), so home raises them here, after it. */
@media (pointer:coarse){
  .home-account .flow-btn,
  .home-account .home-account-chip{ min-height:44px; }
  .hw-theme{ width:44px; height:44px; }
  .hw-theme svg{ width:18px; height:18px; }
  .welcome .fbtn{ min-height:44px; }
  .hw-bar-brand{ min-height:44px; }
  .hw-member-link{ width:44px; height:44px; }
  .hw-foot-links a{
    display:inline-flex; align-items:center; justify-content:center;
    min-height:44px; min-width:44px; padding:0;
  }
  .home-account .mpm-item{ min-height:44px; }
  /* the sheet: the phone scale (every input >= 16px, no iOS focus zoom)
     and 44px rendered targets for the icon and text buttons in it */
  .auth-panel{ --ap-z:0.86; }
  .ap-close{ zoom:1; }
  .ap-eye{ width:52px; height:52px; right:4px; }
  .ap-switch, .ap-mini-link, .ap-legal a{
    display:inline-flex; align-items:center; justify-content:center;
    min-height:52px; min-width:52px; margin:-16px 0;
  }
}
