/*
 * anuragbains.com V3
 *
 * A warm dark, with a warm paper light mode. V1 was white editorial on a strict
 * 1px border grid, so this is none of that. It is also not Codex Corp's dark,
 * which is a cool blue-black instrument panel: this ground is brown-black and
 * lit, because the site is a person rather than a company.
 *
 * TWO SIGNAL TOKENS, and the distinction matters. --signal is the FILL: the
 * vivid orange on a button. --signal-ink is the same idea used AS TEXT, which
 * on paper has to be walked much darker before it clears 4.5. One token for
 * both is how an accent ends up illegible in one of the two themes.
 *
 * Every colour was measured against every surface it can land on. Worst cases
 * are in the comments.
 */

:root, :root[data-theme="dark"] {
  color-scheme: dark;

  /* Warm near-black. #12100E not #0A0C10: the red channel is doing work. */
  --ground:     #12100E;
  --raised:     #1B1815;
  --card:       #211D19;

  --text:       #F4EDE2;   /* 14.40 worst */
  --text-mid:   #B8AC9C;   /* 7.51  worst */
  --text-low:   #94897B;   /* 4.88  worst */

  --signal:     #FF5C1A;   /* fill */
  --signal-ink: #FF5C1A;   /* 5.42 worst, so it is legible as text here */
  --on-signal:  #14100C;   /* 5.95 on the fill */
  --signal-hi:  #FF7A45;
  --signal-dim: rgba(255,92,26,0.13);

  --hair:       rgba(244,237,226,0.10);
  --hair-2:     rgba(244,237,226,0.17);
  --edge:       rgba(244,237,226,0.42);   /* 3:1 for interactive borders */

  /* Codex Corp's own gold, from the live HoldCo site. */
  --holdco:     #C8A55A;
  --studios:    #E08A7E;
  --digital:    #BE9AE4;
  --labs:       #74B893;
  --systems:    #8AA9DF;

  /* Atmosphere: three drifting lights. Kept low, they sit under text. */
  --atmos-1:    rgba(255,92,26,0.13);
  --atmos-2:    rgba(255,163,92,0.075);
  --atmos-3:    rgba(120,150,210,0.055);
  --grain:      0.035;
  --shot-frame: rgba(244,237,226,0.13);
  --duotone:    luminosity;
  --img-scrim:  rgba(18,16,14,0.84);
  --img-scrim-0: rgba(18,16,14,0);

  --f-display:  'Funnel Display', ui-sans-serif, system-ui, sans-serif;
  --f-body:     'Host Grotesk', ui-sans-serif, system-ui, sans-serif;
  --f-mono:     'Martian Mono', ui-monospace, 'SF Mono', monospace;

  --shell:      1180px;
  --gutter:     clamp(1.25rem, 5vw, 4rem);
  --ease:       cubic-bezier(0.22, 0.68, 0.24, 1);
}

/*
 * Light. Warm paper, never white: V1 owns white and this must not read as a
 * revision of it. Each accent is the same hue walked down until it clears its
 * requirement on all three paper surfaces.
 */
:root[data-theme="light"] {
  color-scheme: light;

  --ground:     #F6F1E8;
  --raised:     #EDE6D9;
  --card:       #E6DECF;

  --text:       #17140F;   /* 13.74 worst */
  --text-mid:   #4A4237;   /* 7.39  worst */
  --text-low:   #5E5648;   /* 5.42  worst */

  --signal:     #FF5C1A;   /* still the fill; near-black on it is 5.95 */
  --signal-ink: #AD3A08;   /* 4.64 worst. #FF5C1A as text here is 2.31, unusable */
  --on-signal:  #17140F;
  --signal-hi:  #E24E12;
  --signal-dim: rgba(255,92,26,0.12);

  --hair:       rgba(23,20,15,0.12);
  --hair-2:     rgba(23,20,15,0.20);
  --edge:       rgba(23,20,15,0.48);

  --holdco:     #856923;   /* 4.62 on paper */
  --studios:    #8A4C42;   /* 4.92 */
  --digital:    #6B4E92;   /* 5.02 */
  --labs:       #2F6B4A;   /* 4.73 */
  --systems:    #3D5C8C;   /* 5.06 */

  --atmos-1:    rgba(255,92,26,0.20);
  --atmos-2:    rgba(214,140,60,0.17);
  --atmos-3:    rgba(90,120,180,0.11);
  --grain:      0.022;
  --shot-frame: rgba(23,20,15,0.16);
  --duotone:    multiply;
  /* Photos always vignette to black. Fading them into cream looked washed out. */
  --img-scrim:  rgba(18,14,10,0.60);
  --img-scrim-0: rgba(18,14,10,0);
}

*, *::before, *::after { box-sizing: border-box; }

/* [hidden] has to beat every display declaration in this sheet. Without it the
   modal's CTA, hidden for a project with no link, still lays out as an empty
   orange bar that scrolls the page to the top when clicked. */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
/* A visitor who has gone away (site.js's idle gate, two minutes without input)
   gets a still page: the marquee, the Journey scene and the live dot all
   pause, and resume on the first input. !important because every `animation:`
   shorthand below would otherwise reset the play state to running. */
html[data-idle] *, html[data-idle] *::before, html[data-idle] *::after { animation-play-state: paused !important; }
/* The same off screen: site.js marks each [data-offscreen-pause] container
   .is-off while it is out of view, so nothing animates where nobody can see it. */
.is-off, .is-off *, .is-off *::before, .is-off *::after { animation-play-state: paused !important; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--f-body);
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.08rem);
  line-height: 1.62;
  font-weight: 300;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  transition: background 0.4s var(--ease), color 0.4s var(--ease);
}
body.is-locked { overflow: hidden; }

h1, h2, h3 { font-family: var(--f-display); font-weight: 700; line-height: 1.02; letter-spacing: -0.03em; margin: 0; }
p { margin: 0; }
a { color: inherit; }
img { max-width: 100%; display: block; }

.shell { width: 100%; max-width: var(--shell); margin-inline: auto; padding-inline: var(--gutter); }

.skip {
  position: absolute; left: 0.5rem; top: -4rem; z-index: 300;
  background: var(--signal); color: var(--on-signal); padding: 0.7rem 1.1rem;
  font-family: var(--f-mono); font-size: 0.72rem; text-decoration: none;
}
.skip:focus { top: 0.5rem; }

:where(a, button, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--signal); outline-offset: 3px; border-radius: 2px;
}

.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ======================================================== atmosphere ==== */
/*
 * Three pre-painted lights on their own compositor layers, moved by transform
 * only. Plus a static grain, which is what stops a large soft gradient from
 * banding on an 8-bit display.
 */
#atmos { position: fixed; inset: -20vmax; z-index: -10; pointer-events: none; overflow: hidden; }
#atmos .blob { position: absolute; border-radius: 50%; will-change: transform; transition: background 0.5s var(--ease); }
#atmos .blob:nth-child(1) { width: 82vmax; height: 82vmax; left: 42%; top: 2%;  background: radial-gradient(closest-side, var(--atmos-1), transparent 72%); }
#atmos .blob:nth-child(2) { width: 66vmax; height: 66vmax; left: -6%; top: 44%; background: radial-gradient(closest-side, var(--atmos-2), transparent 74%); }
#atmos .blob:nth-child(3) { width: 58vmax; height: 58vmax; left: 58%; top: 74%; background: radial-gradient(closest-side, var(--atmos-3), transparent 76%); }
#atmos::after {
  content: ''; position: absolute; inset: 0; opacity: var(--grain);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* progress rail: a hairline the page fills as you descend */
.rail { position: fixed; left: 0; top: 0; bottom: 0; width: 2px; z-index: 90; background: var(--hair); pointer-events: none; }
.rail i { position: absolute; inset: 0; background: var(--signal); transform: scaleY(0); transform-origin: top; }
@media (max-width: 820px) { .rail { display: none; } }

/* ============================================================== nav ===== */

.nav {
  /* The bar's vertical padding. The Companies panel adds it back to clear the
     bar's bottom edge, so the two must stay in step - hence one variable. */
  --nav-pad: 0.8rem;
  position: fixed; inset: 0 0 auto; z-index: 100;
  backdrop-filter: blur(14px) saturate(140%); -webkit-backdrop-filter: blur(14px) saturate(140%);
  background: color-mix(in srgb, var(--ground) 34%, transparent);
  border-bottom: 1px solid transparent;
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
.nav.is-stuck { background: color-mix(in srgb, var(--ground) 62%, transparent); border-bottom-color: var(--hair); }

/* Over the header the nav sits on a dark ground in BOTH themes, because the
   header itself is dark in both. So until it sticks, it wears the dark ink.
   Without this the light theme puts near-black links on a near-black image. */
:root[data-theme="light"] .nav:not(.is-stuck) { background: rgba(18,16,14,0.30); }
:root[data-theme="light"] .nav:not(.is-stuck) .mark span,
:root[data-theme="light"] .nav:not(.is-stuck) .nav-links a:hover,
:root[data-theme="light"] .nav:not(.is-stuck) .nav-links a[aria-current="true"] { color: #F4EDE2; }
:root[data-theme="light"] .nav:not(.is-stuck) .nav-links a,
:root[data-theme="light"] .nav:not(.is-stuck) .theme-toggle { color: #B8AC9C; }
:root[data-theme="light"] .nav:not(.is-stuck) .theme-toggle,
:root[data-theme="light"] .nav:not(.is-stuck) .nav-toggle { border-color: rgba(244,237,226,0.20); }
:root[data-theme="light"] .nav:not(.is-stuck) .nav-toggle span { background: #F4EDE2; }
:root[data-theme="light"] .nav:not(.is-stuck) .mg-a { stroke: #F4EDE2; }
.nav-in { max-width: var(--shell); margin-inline: auto; padding: var(--nav-pad) var(--gutter); display: flex; align-items: center; gap: clamp(0.5rem, 2.2vw, 1.8rem); }

.mark { display: flex; align-items: center; gap: 0.6rem; text-decoration: none; margin-right: auto; min-height: 32px; }
.mark-glyph { width: 32px; height: 20px; flex: none; display: block; }
.mark-glyph svg { width: 100%; height: 100%; display: block; overflow: visible; }
/* The A is the page's ink, the B is the signal. Both track the theme, so the
   mark is never a flat cream shape sitting on cream paper. */
.mark-glyph .mg-a { stroke: var(--text); transition: stroke 0.3s var(--ease); }
.mark-glyph .mg-b { stroke: var(--signal); transition: stroke 0.3s var(--ease); }
.mark:hover .mg-b { stroke: var(--signal-hi); }
.mark:hover .mg-a { stroke: var(--signal-ink); }
.mark span { font-family: var(--f-mono); font-size: 0.7rem; letter-spacing: 0.06em; text-transform: uppercase; }

/* Stretched so the Companies trigger can reach the bar's bottom edge and the
   pointer never crosses dead space on its way down to the menu. */
.nav-links { display: flex; align-items: center; align-self: stretch; gap: clamp(0.6rem, 1.6vw, 1.4rem); }
.nav-links a {
  font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.04em;
  text-transform: uppercase; text-decoration: none; color: var(--text-mid);
  padding: 0.4rem 0; transition: color 0.2s var(--ease);
}
.nav-links a:hover, .nav-links a[aria-current="true"] { color: var(--text); }
@media (max-width: 1080px) { .nav-links { display: none; } }

.theme-toggle {
  width: 40px; height: 40px; flex: none; display: grid; place-items: center;
  background: transparent; border: 1px solid var(--hair-2); color: var(--text-mid); cursor: pointer;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease), background 0.2s var(--ease);
}
.theme-toggle:hover { color: var(--text); border-color: var(--edge); background: var(--signal-dim); }
.theme-toggle svg { width: 17px; height: 17px; }
:root[data-theme="light"] .icon-moon, :root:not([data-theme="light"]) .icon-sun { display: none; }

.nav-cta {
  font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.04em;
  text-transform: uppercase; text-decoration: none;
  color: var(--on-signal); background: var(--signal);
  padding: 0.62rem 1rem; min-height: 40px; display: inline-flex; align-items: center;
  transition: background 0.2s var(--ease);
}
.nav-cta:hover { background: var(--signal-hi); }
@media (max-width: 520px) { .mark span { display: none; } }

/* Below 1080px the inline links do not fit, so they move into a panel rather
   than simply disappearing. Hiding them with no way back is how a site ends up
   with no navigation at all on a phone. */
.nav-toggle {
  display: none; width: 40px; height: 40px; flex: none; cursor: pointer;
  background: transparent; border: 1px solid var(--hair-2);
  flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  transition: border-color 0.2s var(--ease);
}
.nav-toggle span { display: block; width: 16px; height: 1.5px; background: var(--text); transition: transform 0.3s var(--ease), opacity 0.2s var(--ease); }
.nav-toggle:hover { border-color: var(--edge); }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }
@media (max-width: 1080px) { .nav-toggle { display: flex; } }

.nav-panel {
  border-top: 1px solid var(--hair);
  /* Matches the bar above it. It was at 96% ground, which is opaque in every
     way that matters, so the drawer read as a different material from the nav
     it drops out of. */
  background: color-mix(in srgb, var(--ground) 42%, transparent);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  /*
   * The drawer's full content (5 nav links, 5 company links, Contact) is
   * 672px tall. The original min(76svh, 36rem) capped it at whichever was
   * SMALLER, and both terms were too small: 36rem is 576px outright, and 76%
   * of even a 768px-tall viewport is only 584px - so raising just the rem
   * term left 1024x768 still short (measured 584px, needing 672px), because
   * svh was the binding term there, not the rem cap this was first mistaken
   * for. 90svh clears the real content on every viewport 748px or taller
   * (768x1024, 1024x768's own 768px included, with margin); 44rem remains as
   * the ceiling so the drawer does not keep growing past its own content on a
   * tall desktop window. A genuinely short viewport - a small phone under
   * ~750px tall - still cannot fit 672px of drawer inside less physical
   * screen than that, no CSS term changes that; it scrolls internally there,
   * correctly, same as it always has.
   */
  max-height: min(90svh, 44rem); overflow: auto;
}
/* No backdrop-filter: go opaque rather than leave the links over the page. */
@supports not (backdrop-filter: blur(1px)) {
  .nav-panel { background: var(--ground); }
}
.nav-panel[hidden] { display: none; }
.nav-panel ul { list-style: none; margin: 0; padding: 0.5rem var(--gutter) 1.4rem; display: grid; gap: 0; }
.nav-panel li + li { border-top: 1px solid var(--hair); }
.nav-panel a {
  display: flex; align-items: center; min-height: 52px; text-decoration: none;
  font-family: var(--f-mono); font-size: 0.74rem; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-mid);
  transition: color 0.2s var(--ease);
}
.nav-panel a:hover, .nav-panel a[aria-current="true"] { color: var(--signal-ink); }
.nav-panel .panel-head { margin-top: 0.8rem; padding-top: 1rem; border-top: 1px solid var(--hair-2); font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--text-low); }

/* ============================================================= hero ===== */

.hero { position: relative; min-height: min(100svh, 980px); display: flex; align-items: flex-end; padding: 7rem 0 clamp(2.5rem, 6vw, 5rem); overflow: clip; isolation: isolate; }
#hero-form {
  position: absolute; inset: 0; z-index: -2;
  background:
    radial-gradient(58% 52% at 62% 40%, rgba(255,92,26,0.16), transparent 68%),
    radial-gradient(46% 44% at 24% 68%, rgba(255,150,80,0.07), transparent 70%),
    radial-gradient(90% 80% at 50% 50%, #1A1512, var(--ground) 76%);
}
/* The header keeps its dark ground in light mode. See hero.js: the shader is
   also pinned to its dark lighting there, for the same reason. */#hero-form canvas { width: 100%; height: 100%; display: block; }
.hero::after {
  content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    linear-gradient(to right,
      rgba(18,16,14,0.90) 0%, rgba(18,16,14,0.72) 28%,
      rgba(18,16,14,0.26) 52%, rgba(18,16,14,0) 72%),
    linear-gradient(to top,
      rgba(18,16,14,0.92) 0%, rgba(18,16,14,0.74) 24%,
      rgba(18,16,14,0.26) 56%, rgba(18,16,14,0.08) 100%);
}
@media (max-width: 820px) {
  .hero::after {
    background: linear-gradient(to top,
      rgba(18,16,14,0.94) 0%, rgba(18,16,14,0.82) 26%,
      rgba(18,16,14,0.42) 58%, rgba(18,16,14,0.14) 100%);
  }
}

.hero-in { position: relative; }
/* The header ground is dark in both themes, so its type is too. */
:root[data-theme="light"] .hero h1,
:root[data-theme="light"] .hero-meta b { color: #F4EDE2; }
:root[data-theme="light"] .hero-sub,
:root[data-theme="light"] .hero-meta { color: #B8AC9C; }
/*
 * .hero::after darkens the ground under this text, but only as a broad
 * gradient - it fades out toward the right and top, and the raymarcher's own
 * highlight sweep can still brighten a small patch as it passes through.
 * Measured: a 5-pixel cluster in the h1 dipped to 2.91:1 against the sweep's
 * brightest moment, below the 3:1 minimum for large text (dark theme's text
 * is unaffected: it measured 7.14:1 at the same frame, since #F4EDE2 is a
 * poor pairing only against the sweep's own warm highlight colour).
 *
 * A text-shadow glues a dark halo to every glyph regardless of where under
 * the gradient it sits, which a broader overlay cannot guarantee - it is
 * anchored to the text, not to a position on the hero. This is the standard
 * fix for light text over an unpredictable background (captions, map labels,
 * game HUDs all use it) rather than a WCAG-formula trick: the ratio the audit
 * measured is between the declared text colour and whatever the canvas paints
 * behind it, which no CSS on the text itself changes - what a halo DOES
 * change is what a reader actually perceives, by giving every edge a firm,
 * near-opaque dark boundary to read against instead of a soft blend into
 * whatever colour happens to be there. Sized generously (two tight layers) for
 * margin rather than tuned to the exact measured frame. Hero-sub and
 * hero-meta get the same treatment: correct at the moment this was measured,
 * but close enough to the same margin that the sweep will clear it too.
 */
:root[data-theme="light"] .hero h1,
:root[data-theme="light"] .hero-sub,
:root[data-theme="light"] .hero-meta {
  text-shadow: 0 1px 2px rgba(10,9,7,0.85), 0 2px 6px rgba(10,9,7,0.75), 0 4px 18px rgba(10,9,7,0.55);
}
:root[data-theme="light"] .hero .btn-ghost { color: #F4EDE2; border-color: rgba(244,237,226,0.42); }
:root[data-theme="light"] .hero .btn-ghost:hover { background: #F4EDE2; color: #12100E; border-color: #F4EDE2; }
.hero-meta {
  font-family: var(--f-mono); font-size: 0.7rem; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--text-mid); display: flex; flex-wrap: wrap; align-items: center; gap: 0.55rem;
  margin-bottom: clamp(1.4rem, 4vw, 2.4rem); max-width: 44ch;
}
.hero-meta b { color: var(--text); font-weight: 400; }
.hero-meta i { font-style: normal; color: var(--hair-2); }
.dot-live { width: 7px; height: 7px; border-radius: 50%; background: var(--signal); box-shadow: 0 0 0 0 rgba(255,92,26,0.55); animation: pulse 2.6s var(--ease) infinite; }
@keyframes pulse { 70% { box-shadow: 0 0 0 9px rgba(255,92,26,0); } 100% { box-shadow: 0 0 0 0 rgba(255,92,26,0); } }
@media (prefers-reduced-motion: reduce) { .dot-live { animation: none; } }

.hero h1 { font-size: clamp(3.1rem, 13vw, 9.5rem); letter-spacing: -0.045em; margin-bottom: clamp(1.2rem, 3vw, 1.8rem); }
.hero h1 em { font-style: normal; color: var(--signal-ink); }
.hero-sub { font-size: clamp(1.05rem, 0.95rem + 0.7vw, 1.5rem); color: var(--text-mid); max-width: 34ch; line-height: 1.48; font-weight: 300; }
.hero-acts { display: flex; flex-wrap: wrap; gap: 0.7rem; margin-top: clamp(1.8rem, 4vw, 2.6rem); }

.btn {
  font-family: var(--f-mono); font-size: 0.7rem; letter-spacing: 0.05em; text-transform: uppercase;
  text-decoration: none; padding: 0.85rem 1.35rem; min-height: 46px;
  display: inline-flex; align-items: center; gap: 0.5rem; cursor: pointer;
  transition: background 0.22s var(--ease), color 0.22s var(--ease), border-color 0.22s var(--ease);
}
.btn-solid { background: var(--signal); color: var(--on-signal); border: 1px solid var(--signal); }
.btn-solid:hover { background: var(--signal-hi); border-color: var(--signal-hi); }
.btn-ghost { color: var(--text); border: 1px solid var(--edge); background: transparent; }
.btn-ghost:hover { background: var(--text); color: var(--ground); border-color: var(--text); }

/* ========================================================== sections ==== */

.band { padding-block: clamp(4.5rem, 11vw, 9rem); position: relative; }
.band + .band { border-top: 1px solid var(--hair); }

.band-head { margin-bottom: clamp(2.4rem, 6vw, 4rem); }
.index {
  font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--text-low); display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.3rem;
}
.index::before {
  content: ''; flex: none; width: 22px; height: 8px;
  background:
    radial-gradient(circle 4px at 4px 4px,  var(--signal) 96%, transparent),
    radial-gradient(circle 4px at 11px 4px, var(--text-low) 96%, transparent),
    radial-gradient(circle 4px at 18px 4px, var(--text-low) 96%, transparent);
}
.band h2 { font-size: clamp(2rem, 1.5rem + 2.6vw, 3.7rem); max-width: 18ch; }
.band-lede { margin-top: 1.4rem; color: var(--text-mid); max-width: 56ch; font-size: clamp(1rem, 0.96rem + 0.3vw, 1.16rem); }

/* --- 01 about: editorial split, portrait in an offset frame -------------- */
/* Wide gutter between the photo and the text, tight between the two bars
   below them: one grid gap cannot serve both. */
.about { display: grid; column-gap: clamp(2rem, 5vw, 4rem); row-gap: clamp(1.4rem, 3vw, 2rem); align-items: start; }
@media (min-width: 900px) { .about { grid-template-columns: 0.85fr 1.15fr; } }
.about-frame { position: relative; }
.shot { position: relative; overflow: hidden; background: var(--raised); border: 1px solid var(--shot-frame); }
.shot img { width: 100%; height: 100%; object-fit: cover; object-position: center 22%; }
/* The headshot is framed with headroom: a centred cover crop on a 4:5 box
   took the top of the head off. Biasing the focal point down keeps the
   whole head in the frame. */
.shot--portrait img { object-position: center 12%; }
.shot--portrait { aspect-ratio: 4 / 5; }
/* The frame is offset behind the picture, so the block has depth without a
   drop shadow, which on a warm dark ground always reads as grey mud. */
.about-frame::before { content: ''; position: absolute; right: -12px; bottom: -12px; width: 62%; height: 46%; border: 1px solid var(--signal); z-index: -1; }
.about-cap { margin-top: 0.9rem; font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-low); display: flex; justify-content: space-between; gap: 1rem; }
.about-body p + p { margin-top: 1.1rem; }
.about-body p { color: var(--text-mid); font-size: clamp(1rem, 0.95rem + 0.35vw, 1.18rem); }
.about-body p:first-child { color: var(--text); font-size: clamp(1.15rem, 1rem + 0.8vw, 1.55rem); line-height: 1.42; font-weight: 300; }
.about-body strong { color: var(--signal-ink); font-weight: 500; }
/*
 * TWO BARS under both columns of the About grid, not one welded plate. Each
 * spans the full grid so it begins under the photograph and runs to the right
 * edge of the text.
 *
 * Both are glass: a translucent ground with a blur behind it, so the drifting
 * atmosphere and the thread keep moving underneath rather than being covered
 * by a slab. The hairline is a gradient rather than a flat border, which is
 * what stops a translucent panel reading as a grey rectangle.
 */
.about-facts,
.land {
  grid-column: 1 / -1;
  position: relative;
  border: 1px solid transparent;
  border-radius: 3px;
  background:
    linear-gradient(var(--glass), var(--glass)) padding-box,
    linear-gradient(120deg,
      color-mix(in srgb, var(--signal) 42%, transparent),
      var(--hair-2) 38%,
      var(--hair) 70%,
      color-mix(in srgb, var(--signal) 26%, transparent)) border-box;
  backdrop-filter: blur(14px) saturate(1.15);
  -webkit-backdrop-filter: blur(14px) saturate(1.15);
}
/* The ground both bars float on. Light mode needs more of it to stay legible. */
:root, :root[data-theme="dark"] { --glass: rgba(33, 29, 25, 0.55); }
:root[data-theme="light"] { --glass: rgba(246, 241, 232, 0.62); }

/* No backdrop-filter (older Firefox, some Linux builds): go opaque rather than
   leave text sitting on whatever happens to be behind it. */
@supports not (backdrop-filter: blur(1px)) {
  .about-facts, .land { --glass: var(--card); }
}

.about-facts {
  margin-top: clamp(1.6rem, 3.5vw, 2.4rem);
  display: grid; grid-template-columns: repeat(2, 1fr);
  overflow: hidden;
}
@media (min-width: 720px) { .about-facts { grid-template-columns: repeat(4, 1fr); } }
.about-fact {
  padding: clamp(1rem, 2.2vw, 1.35rem) clamp(1rem, 2.2vw, 1.4rem);
  position: relative;
}
/* Dividers drawn as insets, so they stop short of the glass edge. */
.about-fact + .about-fact::before {
  content: ''; position: absolute; left: 0; top: 18%; bottom: 18%;
  width: 1px; background: var(--hair-2);
}
@media (max-width: 719px) { .about-fact:nth-child(odd)::before { display: none; } }
.about-fact b {
  display: block; font-family: var(--f-mono); font-size: 0.6875rem;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--text-low); font-weight: 400;
}
.about-fact span { display: block; margin-top: 0.5rem; font-size: 0.95rem; }


/* --- 02 now ------------------------------------------------------------- */
.now { display: grid; gap: clamp(1.6rem, 4vw, 3rem); }
@media (min-width: 900px) { .now { grid-template-columns: 1fr 1.55fr; align-items: start; } }
.now-stamp { font-family: var(--f-mono); font-size: 0.7rem; letter-spacing: 0.08em; color: var(--text-low); text-transform: uppercase; }
.now-body { font-size: clamp(1.1rem, 1rem + 0.6vw, 1.44rem); line-height: 1.5; color: var(--text); font-weight: 300; }
.now-body strong { font-weight: 500; color: var(--signal-ink); }

/* --- 03 three layers: cards with drawn SVG diagrams ---------------------- */
.layers { display: grid; gap: 1px; background: var(--hair); border: 1px solid var(--hair); }
@media (min-width: 880px) { .layers { grid-template-columns: repeat(3, 1fr); } }
.layer { background: var(--ground); padding: clamp(1.6rem, 3.4vw, 2.4rem); position: relative; transition: background 0.3s var(--ease); }
.layer:hover { background: var(--raised); }
.layer-fig { width: 100%; height: 80px; margin-bottom: 1.3rem; display: block; }
.layer-fig [data-draw] { fill: none; stroke: var(--signal); stroke-width: 1.5; stroke-linecap: round; }
.layer-fig .dim { fill: none; stroke: var(--text-low); stroke-width: 1; }
.layer-fig .node { fill: var(--signal); stroke: none; }
.layer-fig .node-o { fill: none; stroke: var(--text-low); stroke-width: 1.2; }
.layer-n { font-family: var(--f-mono); font-size: 0.72rem; color: var(--signal-ink); letter-spacing: 0.08em; }
.layer h3 { font-size: clamp(1.35rem, 1.2rem + 0.6vw, 1.7rem); margin: 1rem 0 0.7rem; }
.layer p { color: var(--text-mid); font-size: 0.97rem; }
.layer-list { list-style: none; margin: 1.3rem 0 0; padding: 0; display: grid; gap: 0.45rem; }
.layer-list li { font-family: var(--f-mono); font-size: 0.72rem; color: var(--text-low); padding-left: 1rem; position: relative; line-height: 1.5; }
.layer-list li::before { content: ''; position: absolute; left: 0; top: 0.55em; width: 5px; height: 1px; background: var(--signal); }

/* --- 04 projects: fanned stacks ------------------------------------------ */
.pfilter { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: clamp(1.8rem, 4vw, 2.6rem); }
.pfilter button {
  font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.05em; text-transform: uppercase;
  background: transparent; color: var(--text-mid); border: 1px solid var(--hair-2);
  padding: 0.5rem 0.9rem; min-height: 38px; cursor: pointer;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease), background 0.2s var(--ease);
}
.pfilter button:hover { color: var(--text); border-color: var(--edge); }
.pfilter button[aria-pressed="true"] { color: var(--on-signal); background: var(--signal); border-color: var(--signal); }

.pgrid { display: grid; gap: clamp(1.2rem, 2.6vw, 1.9rem); }
@media (min-width: 720px)  { .pgrid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1080px) { .pgrid { grid-template-columns: repeat(3, 1fr); } }

.pcard {
  --co: var(--signal);
  --co-ink: var(--signal-ink);
  display: flex; flex-direction: column; text-align: left; cursor: pointer;
  background: var(--card); border: 1px solid var(--hair); border-top: 2px solid var(--co);
  padding: 0; overflow: hidden; font: inherit; color: inherit;
  transition: background 0.3s var(--ease), transform 0.35s var(--ease);
}
.pcard:hover { background: var(--raised); transform: translateY(-3px); }
.pcard[hidden] { display: none; }

/* the fan. hero shot in front, two behind, splayed */
.fan {
  /* A fixed height, so a portrait phone fan and a landscape site fan leave the
     title and meta on the same line across the whole grid. Without it the row
     with Dream Codex in it sits noticeably lower than its neighbours. */
  height: clamp(14rem, 26vw, 18.5rem);
  /*
   * The row must be DEFINITE, not auto. `.fan` has a fixed height, but its
   * single implicit row was auto-sized, and an auto row is indefinite - so a
   * percentage height or max-height on the images resolved to nothing and the
   * portrait phone laid out at its full intrinsic height, two thirds of it
   * outside the box. minmax(0, 1fr) hands the row the container's real height.
   */
  display: grid; grid-template-rows: minmax(0, 1fr);
  place-items: center; position: relative; overflow: hidden;
  padding: clamp(1.2rem, 2.6vw, 1.8rem) 1rem 0;
  background: radial-gradient(120% 90% at 50% 0%, color-mix(in srgb, var(--co) 14%, transparent), transparent 70%);
}
.fan img {
  grid-area: 1 / 1; display: block;
  /*
   * max-height is what actually keeps a shot inside the fan. Every image now
   * carries width/height attributes for layout stability, and with those
   * present a percentage height on the portrait rule stopped resolving against
   * the fan and fell back to the attribute-implied size: the Dream phone laid
   * out 620px tall inside a 256px box, so `overflow: hidden` cropped away
   * two thirds of it.
   */
  max-width: 100%; max-height: 100%;
  object-fit: contain; object-position: center;
  border: 1px solid var(--shot-frame); transform-origin: 50% 92%;
  transition: transform 0.42s var(--ease);
}
.fan--wide img { width: min(78%, 20rem); height: auto; aspect-ratio: 1440 / 900; }
/*
 * Portrait: fit the height, let the width follow, never exceed the box. A phone
 * shown whole is narrow, so the tall fan gives back the top padding a landscape
 * shot needs - the fan height stays uniform across the grid either way, which
 * is what keeps every card's title on the same line.
 */
.fan--tall { padding-top: 0.5rem; }
.fan--tall img { width: auto; height: 100%; aspect-ratio: 629 / 1400; }
/* Painted back to front in the markup, so the hero shot is last and sits on
   top without a z-index war. */
.fan .fan-2 { transform: translateX(-16%) rotate(-7deg); filter: saturate(0.85) brightness(0.82); }
.fan .fan-1 { transform: translateX(16%)  rotate(7deg);  filter: saturate(0.85) brightness(0.82); }
.fan .fan-0 { transform: translateY(-2%); }
.pcard:hover .fan .fan-2 { transform: translateX(-23%) rotate(-11deg); }
.pcard:hover .fan .fan-1 { transform: translateX(23%)  rotate(11deg); }
.pcard:hover .fan .fan-0 { transform: translateY(-6%); }
@media (prefers-reduced-motion: reduce) {
  .fan img { transition: none; }
  .pcard:hover .fan img, .pcard:hover { transform: none; }
}

.pcard-meta { display: flex; justify-content: space-between; gap: 1rem; padding: clamp(1.2rem, 2.6vw, 1.6rem) clamp(1.2rem, 2.6vw, 1.6rem) 0; font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.07em; text-transform: uppercase; }
.pcard-kind { color: var(--co-ink); }
.pcard-year { color: var(--text-low); }
.pcard-body { padding: 0.8rem clamp(1.2rem, 2.6vw, 1.6rem) 0; }
.pcard-name { display: block; font-family: var(--f-display); font-weight: 700; font-size: clamp(1.2rem, 1.1rem + 0.4vw, 1.45rem); letter-spacing: -0.025em; }
.pcard-blurb { display: block; margin-top: 0.55rem; color: var(--text-mid); font-size: 0.92rem; line-height: 1.5; }
.pcard-more {
  margin-top: auto; padding: 1.2rem clamp(1.2rem, 2.6vw, 1.6rem) clamp(1.2rem, 2.6vw, 1.5rem);
  font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-low); display: flex; align-items: center; gap: 0.45rem; transition: color 0.2s var(--ease);
}
.pcard-more::after { content: '\2192'; transition: transform 0.25s var(--ease); }
.pcard:hover .pcard-more { color: var(--co-ink); }
.pcard:hover .pcard-more::after { transform: translateX(4px); }

/* --- the modal ---------------------------------------------------------- */
.modal { position: fixed; inset: 0; z-index: 200; display: grid; place-items: center; padding: clamp(0.75rem, 3vw, 2rem); }
.modal[hidden] { display: none; }
.modal-scrim { position: absolute; inset: 0; background: color-mix(in srgb, var(--ground) 86%, transparent); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); border: 0; padding: 0; cursor: pointer; }
.modal-box {
  --co: var(--signal);
  --co-ink: var(--signal-ink);
  position: relative; width: min(1040px, 100%); max-height: min(92svh, 900px);
  background: var(--ground); border: 1px solid var(--hair-2); border-top: 2px solid var(--co);
  overflow: auto; display: grid;
}
@media (min-width: 900px) { .modal-box { grid-template-columns: 1.25fr 1fr; } }
.modal-close {
  position: absolute; right: 0.6rem; top: 0.6rem; z-index: 3;
  width: 40px; height: 40px; display: grid; place-items: center; cursor: pointer;
  background: color-mix(in srgb, var(--ground) 80%, transparent); color: var(--text); border: 1px solid var(--hair-2);
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease);
}
.modal-close:hover { background: var(--signal); color: var(--on-signal); border-color: var(--signal); }
.modal-close svg { width: 16px; height: 16px; }

.gal { position: relative; background: var(--raised); display: flex; flex-direction: column; }
/* On a phone the gallery is stacked above the copy, so it needs a ratio of its
   own. Side by side it should instead fill whatever height the copy column
   ends up being, or the picture floats in a pool of empty ground. */
.gal-stage { position: relative; aspect-ratio: 16 / 10; overflow: hidden; }
.modal-box[data-tall="true"] .gal-stage { aspect-ratio: 629 / 1400; }
@media (min-width: 900px) {
  .gal-stage, .modal-box[data-tall="true"] .gal-stage { aspect-ratio: auto; flex: 1; min-height: 22rem; }
}
.slide { position: absolute; inset: 0; margin: 0; opacity: 0; transition: opacity 0.65s var(--ease); }
.slide.is-on { opacity: 1; }
.slide img { width: 100%; height: 100%; object-fit: contain; object-position: center; background: var(--ground); }

.gal-bar { flex: none; display: flex; align-items: center; gap: 0.7rem; padding: 0.7rem 0.9rem; border-top: 1px solid var(--hair); }
.gdots { display: flex; gap: 0.3rem; flex: 1; }
.gdot { width: 26px; height: 24px; padding: 0; background: transparent; border: 0; cursor: pointer; position: relative; }
.gdot::after { content: ''; position: absolute; left: 0; right: 0; top: 50%; height: 2px; background: var(--hair-2); transition: background 0.25s var(--ease); }
.gdot.is-on::after { background: var(--signal); }
.gcount { font-family: var(--f-mono); font-size: 0.70rem; color: var(--text-low); letter-spacing: 0.06em; }
.gnav { display: flex; gap: 0.3rem; }
.gnav button { width: 36px; height: 36px; display: grid; place-items: center; cursor: pointer; background: transparent; border: 1px solid var(--hair-2); color: var(--text-mid); transition: color 0.2s var(--ease), border-color 0.2s var(--ease); }
.gnav button:hover { color: var(--text); border-color: var(--edge); }
.gnav svg { width: 14px; height: 14px; }

.modal-copy { padding: clamp(1.4rem, 3.4vw, 2.4rem); display: flex; flex-direction: column; gap: 1rem; }
.modal-kind { font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--co-ink); }
.modal-copy h3 { font-size: clamp(1.6rem, 1.3rem + 1.2vw, 2.3rem); }
.modal-copy p { color: var(--text-mid); font-size: 0.98rem; }
.modal-status { font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-low); border: 1px solid var(--hair-2); padding: 0.35rem 0.6rem; align-self: flex-start; }
.modal-facts { display: grid; gap: 0.7rem; margin-top: auto; padding-top: 1rem; border-top: 1px solid var(--hair); }
.modal-fact { display: flex; justify-content: space-between; gap: 1rem; font-size: 0.88rem; }
.modal-fact dt { font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-low); margin: 0; }
.modal-fact dd { margin: 0; text-align: right; }
.modal-stack { display: flex; flex-wrap: wrap; gap: 0.35rem; justify-content: flex-end; }
.modal-stack span { font-family: var(--f-mono); font-size: 0.70rem; color: var(--text-mid); border: 1px solid var(--hair-2); padding: 0.24rem 0.5rem; }

/* --- 05 the trade: bars + chips + counters ------------------------------- */
.domains { display: grid; gap: clamp(1rem, 2.4vw, 1.5rem); margin-bottom: clamp(2.4rem, 5vw, 3.4rem); }
.domain { display: grid; grid-template-columns: minmax(0, 13rem) 1fr auto; gap: clamp(0.7rem, 2vw, 1.4rem); align-items: center; }
@media (max-width: 700px) { .domain { grid-template-columns: 1fr auto; } .domain .bar { grid-column: 1 / -1; } }
.domain-name { font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text); display: flex; align-items: center; gap: 0.55rem; }
.domain-name svg { width: 16px; height: 16px; flex: none; color: var(--signal-ink); }
/* Correct at --p:1, so the chart is finished with no JS. */
.bar { --p: 1; height: 8px; background: var(--hair); position: relative; overflow: hidden; }
.bar::after { content: ''; position: absolute; inset: 0 auto 0 0; width: calc(var(--w) * var(--p)); background: linear-gradient(90deg, var(--signal), var(--signal-hi)); }
.domain-n { font-family: var(--f-mono); font-size: 0.72rem; color: var(--text-low); min-width: 2.4ch; text-align: right; }

.stack { display: grid; gap: clamp(1.6rem, 3.5vw, 2.6rem); }
@media (min-width: 780px)  { .stack { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1120px) { .stack { grid-template-columns: repeat(3, 1fr); } }
.stack-group { border-top: 1px solid var(--hair-2); padding-top: 1.15rem; }
.stack-group h3 { font-family: var(--f-mono); font-weight: 400; font-size: 0.7rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--text); margin-bottom: 1rem; display: flex; align-items: center; gap: 0.55rem; }
.stack-group h3 svg { width: 16px; height: 16px; color: var(--signal-ink); flex: none; }
.chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.chips span { font-size: 0.8rem; color: var(--text-mid); border: 1px solid var(--hair-2); padding: 0.3rem 0.6rem; transition: border-color 0.2s var(--ease), color 0.2s var(--ease); }
.chips span:hover { border-color: var(--edge); color: var(--text); }

.counts { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1px; background: var(--hair); border: 1px solid var(--hair); margin-top: clamp(2.4rem, 5vw, 3.4rem); }
@media (min-width: 720px) { .counts { grid-template-columns: repeat(4, 1fr); } }
.count { background: var(--ground); padding: clamp(1.3rem, 3vw, 1.9rem); }
.count b { display: block; font-family: var(--f-display); font-weight: 700; font-size: clamp(2rem, 1.6rem + 2vw, 3rem); letter-spacing: -0.04em; line-height: 1; }
.count span { display: block; margin-top: 0.6rem; font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-low); }

/* --- 06 companies -------------------------------------------------------- */
.cos { display: grid; gap: 1px; background: var(--hair); border: 1px solid var(--hair); }
@media (min-width: 700px)  { .cos { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1060px) { .cos { grid-template-columns: repeat(4, 1fr); } }
.co { background: var(--ground); padding: clamp(1.5rem, 3.2vw, 2.1rem); text-decoration: none; display: flex; flex-direction: column; gap: 0.6rem; border-top: 2px solid var(--co); position: relative; overflow: hidden; transition: background 0.3s var(--ease); }
.co::before { content: ''; position: absolute; inset: 0 0 auto; height: 58%; background: linear-gradient(180deg, color-mix(in srgb, var(--co) 12%, transparent), transparent); pointer-events: none; }
.co:hover { background: var(--raised); }
.co-ico { width: 26px; height: 26px; color: var(--co); position: relative; margin-bottom: 0.3rem; }
.co-n { font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.08em; color: var(--co); position: relative; }
.co h3 { font-size: clamp(1.2rem, 1.1rem + 0.4vw, 1.45rem); position: relative; }
.co p { color: var(--text-mid); font-size: 0.9rem; position: relative; }
.co-go { margin-top: auto; padding-top: 1.1rem; font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-low); position: relative; transition: color 0.2s var(--ease); }
.co:hover .co-go { color: var(--co); }

/* --- 07 the bains mode: cinematic --------------------------------------- */
.mode { display: grid; gap: clamp(1.8rem, 4vw, 3.2rem); align-items: center; }
@media (min-width: 900px) { .mode { grid-template-columns: 1.05fr 1fr; } }
.mode-quote { font-family: var(--f-display); font-weight: 400; font-size: clamp(1.5rem, 1.2rem + 1.8vw, 2.5rem); line-height: 1.18; letter-spacing: -0.03em; }
.mode-quote em { font-style: normal; color: var(--signal-ink); }
.mode-side { display: grid; gap: 1.2rem; }
.mode-shot { position: relative; overflow: hidden; aspect-ratio: 4 / 3; border: 1px solid var(--shot-frame); }
.mode-shot img { width: 100%; height: 118%; object-fit: cover; filter: grayscale(0.35) contrast(1.06); }
.mode-shot::after { content: ''; position: absolute; inset: 0; background: linear-gradient(200deg, color-mix(in srgb, var(--signal) 26%, transparent), transparent 62%); mix-blend-mode: var(--duotone); }
.mode-play { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); z-index: 2; width: 66px; height: 66px; border-radius: 50%; display: grid; place-items: center; background: color-mix(in srgb, var(--ground) 62%, transparent); border: 1px solid var(--edge); color: var(--text); backdrop-filter: blur(4px); }
.mode-play svg { width: 20px; height: 20px; margin-left: 3px; }

/* --- 08 off the clock: image tiles -------------------------------------- */
.picks { display: grid; gap: 1px; background: var(--hair); border: 1px solid var(--hair); }
@media (min-width: 640px)  { .picks { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .picks { grid-template-columns: repeat(3, 1fr); } }
.pick { background: var(--ground); position: relative; overflow: hidden; display: flex; flex-direction: column; }
.pick-img { position: relative; aspect-ratio: 16 / 10; overflow: hidden; }
.pick-img img { width: 100%; height: 112%; object-fit: cover; filter: grayscale(0.55) contrast(1.05); transition: filter 0.45s var(--ease), transform 0.6s var(--ease); }
.pick-img::after { content: ''; position: absolute; inset: 0; background: linear-gradient(180deg, var(--img-scrim-0) 28%, var(--img-scrim)); }
.pick:hover .pick-img img { filter: grayscale(0.05) contrast(1.02); transform: scale(1.03); }
@media (prefers-reduced-motion: reduce) { .pick:hover .pick-img img { transform: none; } }
.pick-txt { padding: clamp(1.1rem, 2.6vw, 1.6rem); }
.pick h3 { font-size: 1.08rem; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.5rem; }
.pick h3 svg { width: 16px; height: 16px; color: var(--signal-ink); flex: none; }
.pick p { color: var(--text-mid); font-size: 0.92rem; }

/* --- 09 principles ------------------------------------------------------ */
/*
 * The six rules, as cards in the same system the project cards use: the card
 * ground, a hairline, square corners, and one accent on the top edge. They
 * were bare text under a rule, which read as a list of footnotes rather than
 * as six things worth stopping on.
 *
 * The hover is built from transform and opacity only, per the house rule, so
 * it composites and never triggers layout: the card lifts, the accent rules
 * across the top edge, and the number slides up to meet the heading.
 */
.rules { display: grid; gap: clamp(1rem, 2vw, 1.4rem); counter-reset: rule; }
@media (min-width: 720px)  { .rules { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1080px) { .rules { grid-template-columns: repeat(3, 1fr); } }

.rule {
  counter-increment: rule;
  position: relative;
  isolation: isolate;
  padding: clamp(1.3rem, 2.4vw, 1.7rem);
  background: var(--card);
  border: 1px solid var(--hair);
  border-top: 2px solid color-mix(in srgb, var(--signal) 38%, transparent);
  transition: background 0.3s var(--ease), border-top-color 0.3s var(--ease),
              transform 0.35s var(--ease);
}

/* The accent, drawn on the top edge and scaled out from the left on hover. */
.rule::after {
  content: '';
  position: absolute; left: -1px; right: -1px; top: -2px; height: 2px;
  background: var(--signal);
  transform: scaleX(0.16);
  transform-origin: left center;
  transition: transform 0.42s var(--ease);
}

.rule::before {
  content: counter(rule, decimal-leading-zero);
  font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.06em;
  color: var(--signal-ink);
  display: block; margin-bottom: 0.9rem;
  transition: transform 0.35s var(--ease), opacity 0.35s var(--ease);
}

.rule h3 { font-size: 1.12rem; margin-bottom: 0.55rem; }
.rule p { color: var(--text-mid); font-size: 0.93rem; }

@media (hover: hover) {
  .rule:hover { background: var(--raised); transform: translateY(-4px); }
  .rule:hover::after { transform: scaleX(1); }
  .rule:hover::before { transform: translateY(-2px); opacity: 0.85; }
}

/* Keyboard parity: the same state, reachable without a pointer. */
.rule:focus-within { background: var(--raised); transform: translateY(-4px); }
.rule:focus-within::after { transform: scaleX(1); }

@media (prefers-reduced-motion: reduce) {
  .rule, .rule::after, .rule::before { transition: none; }
  .rule:hover, .rule:focus-within { transform: none; }
}

/* --- 10 contact --------------------------------------------------------- */
.talk { display: grid; gap: clamp(2rem, 5vw, 3.5rem); }
@media (min-width: 900px) { .talk { grid-template-columns: 1.3fr 1fr; align-items: end; } }
.talk h2 { font-size: clamp(2.4rem, 1.8rem + 4vw, 5.2rem); max-width: 12ch; }
.talk-lines { display: grid; gap: 0.9rem; }
.talk-line { display: flex; justify-content: space-between; gap: 1rem; align-items: center; min-height: 44px; border-bottom: 1px solid var(--hair); padding-bottom: 0.8rem; text-decoration: none; transition: border-color 0.2s var(--ease); }
.talk-line:hover { border-bottom-color: var(--signal); }
.talk-line dt { font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-low); margin: 0; }
.talk-line dd { margin: 0; font-size: 0.98rem; }
.talk-line:hover dd { color: var(--signal-ink); }

/* ============================================================ footer ==== */
footer {
  border-top: 1px solid var(--hair);
  padding-block: clamp(2.5rem, 6vw, 4rem) 2rem;
  /* Glass, so the atmosphere keeps drifting behind the last block of the page
     instead of the page ending on a flat slab. */
  background: color-mix(in srgb, var(--ground) 34%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
}
.foot-in { display: grid; gap: 2rem; }
@media (min-width: 820px) { .foot-in { grid-template-columns: 1.6fr 1fr 1fr; } }
.foot-brand { color: var(--text-mid); max-width: 34ch; margin-top: 1rem; font-size: 0.94rem; }
.foot-col p { font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-low); margin-bottom: 1rem; }
.foot-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }
.foot-col a { text-decoration: none; color: var(--text-mid); font-size: 0.92rem;
  /* 24px minimum target, WCAG 2.5.8 */
  display: inline-flex; align-items: center; min-height: 26px;
  transition: color 0.2s var(--ease); }
.foot-col a:hover { color: var(--text); }
.foot-col a[data-co] { display: flex; align-items: baseline; gap: 0.55rem; color: var(--co); min-height: 26px; }
.foot-col a[data-co]::before { content: ''; flex: none; width: 5px; height: 5px; background: var(--co); transform: translateY(-0.15em); transition: transform 0.22s var(--ease); }
.foot-col a[data-co]:hover::before { transform: translateY(-0.15em) scale(1.7); }
.foot-bar { margin-top: clamp(2.5rem, 5vw, 3.5rem); padding-top: 1.6rem; border-top: 1px solid var(--hair); display: flex; flex-wrap: wrap; gap: 0.8rem; justify-content: space-between; font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.05em; color: var(--text-low); }

/* ============================================================ reveal ==== */
.js [data-reveal] { opacity: 0; transform: translateY(14px); transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); }
.js [data-reveal].in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1; transform: none; transition: none; }
  .slide { transition: none; }
}

/* --- header scene picker (TEMPORARY) ------------------------------------ */
/* Built by JS, so with scripting off it never appears. Delete this block and
   the markup once a scene is chosen. */
/*
 * TEMPORARY, with the rest of the picker. It is revealed by site.js once the
 * shader reports which scenes exist, and because the hero is bottom-aligned
 * that reveal used to push the headline up the screen: a fixed ~0.09 layout
 * shift on every load. Reserving the row keeps the hero still, at the cost of
 * a blank strip for the fraction of a second before the buttons are built.
 *
 * This whole rule goes when the scene is chosen and the picker is deleted.
 */

/* --- projects: the bar, and the shelf view ------------------------------ */
.pbar { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; justify-content: space-between; margin-bottom: clamp(1.8rem, 4vw, 2.6rem); }
.pview { display: flex; gap: 0.4rem; flex: none; }
.pview button {
  font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.05em; text-transform: uppercase;
  background: transparent; color: var(--text-mid); border: 1px solid var(--hair-2);
  padding: 0.5rem 0.9rem; min-height: 38px; cursor: pointer;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease), background 0.2s var(--ease);
}
.pview button:hover { color: var(--text); border-color: var(--edge); }
.pview button[aria-pressed="true"] { color: var(--on-signal); background: var(--signal); border-color: var(--signal); }

/*
 * The shelf. This is ThreeUI's Complete Shelf, a real Three.js scene with lit
 * board geometry and rendered covers, so it lives in its own document under
 * assets/shelf/ and mounts here in an iframe rather than being inlined: it
 * carries its own r165 runtime and its own canvas sizing, and keeping it
 * sandboxed means a WebGL context loss in the shelf cannot take the hero's
 * raymarcher down with it.
 *
 * The frame needs a DEFINITE height. The scene sizes its drawing buffer from
 * its own viewport, and an auto-height iframe collapses to the 150px default,
 * which is what a canvas with nothing to measure against reports.
 */
.shelf { padding-top: clamp(1rem, 3vw, 2rem); }
.shelf[hidden] { display: none; }
.shelf-frame {
  display: block; width: 100%; border: 1px solid var(--hair-2); border-radius: 4px;
  /*
   * Tall enough to open a book in. The reader panel stacks a title, a deck,
   * four metadata fields, the page-flip row and two buttons: 590px for the
   * longest volume. At the old 46rem ceiling the flip controls fell off the
   * bottom of the frame and the visitor had to scroll the host page to turn a
   * page, which is the one thing the reader is for.
   */
  height: clamp(34rem, 86vh, 54rem);
  background: #17130F;   /* the shelf's own paper, so the frame never flashes light */
  color-scheme: dark;
}
.shelf-note {
  font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.05em;
  color: var(--text-low); margin-top: 0.8rem; text-transform: uppercase;
}
@media (max-width: 700px) { .shelf-frame { height: clamp(30rem, 78vh, 40rem); } }

/* an art-only project card: the diagram fills the frame instead of a fan */
.fan--art { padding: 0; }
.fan--art .fan-art { width: 100%; height: 100%; object-fit: cover; object-position: center; border: 0; }

/* --- nav: the Companies menu ------------------------------------------- */
/*
 * Opens on hover for a pointer and on :focus-within for a keyboard, so it needs
 * no script at all. Visibility is animated rather than display, because display
 * cannot transition and a menu that snaps reads as a glitch.
 */
/*
 * The trigger fills the bar's full height and is NOT the positioning context.
 * With `position: relative` here, the menu's `top: 100%` resolved against the
 * inline link - so the panel opened level with the other nav items and sat
 * across Projects and Journey instead of below the bar. The menu is positioned
 * against `.nav` itself now, and stretching the trigger keeps the pointer
 * inside it on the way down, so the hover does not drop in the gap.
 */
/*
 * The padding/negative-margin pair extends the trigger's HIT AREA down to the
 * bar's bottom edge without moving anything. `align-self: stretch` only reaches
 * the bar's CONTENT box, so the trigger used to stop `--nav-pad` short of the
 * bottom while the menu opened at the bottom - leaving a 12.8px strip that
 * belonged to neither. Crossing it dropped `:hover`, the menu closed, and the
 * links could not be reached. The negative margin cancels the padding in
 * layout, so the box grows downward and the label does not move.
 */
.nav-co {
  position: relative; display: inline-flex; align-items: center; align-self: stretch;
  padding-bottom: var(--nav-pad); margin-bottom: calc(-1 * var(--nav-pad));
}
.nav-co > a::after {
  content: ''; margin-left: 0.4rem; width: 0; height: 0;
  border-left: 3px solid transparent; border-right: 3px solid transparent;
  border-top: 3px solid currentColor; opacity: 0.55;
  transition: transform 0.22s var(--ease);
  align-self: center;
}
.nav-co:hover > a::after, .nav-co:focus-within > a::after { transform: rotate(180deg); }

.nav-co-menu {
  /*
   * Anchored to the trigger's RIGHT edge, not centred on it. Centred, half the
   * panel hung out sideways across the neighbouring nav items instead of
   * dropping cleanly beneath Companies - and Companies sits near the right end
   * of the bar, so it was the toggle and the CTA it overlapped.
   */
  /*
   * ABSOLUTE inside the trigger, so the browser anchors it and no measurement
   * can go stale. It was briefly fixed to the viewport with a JS-measured
   * inset; that inset is read from a rect, and a resize handler runs BEFORE
   * layout reflows, so a resize could leave the panel anchored to where
   * Companies used to be - which is how it ended up in the far right corner.
   * Layout cannot race itself.
   *
   * `top: 100%` is the bottom of `.nav-co`'s padding box, and `.nav-co` now
   * pads itself down to the bar's bottom edge - so the panel still opens on
   * that edge, but with its top touching the trigger's box rather than sitting
   * `--nav-pad` below it. Contiguous is the whole point: any gap between the
   * two is dead space that drops `:hover` on the way down.
   */
  position: absolute; top: 100%; right: 0;
  left: auto; z-index: 40;
  /* Never wider than the viewport allows, however long a company name gets. */
  max-width: calc(100vw - 2 * var(--gutter, 1.5rem));
  min-width: 16rem; padding: 0.5rem;
  /* One item per row, always. */
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 1px;
  background: color-mix(in srgb, var(--ground) 92%, transparent);
  border: 1px solid var(--hair);
  backdrop-filter: blur(10px);
  /*
   * Opacity and pointer-events, not visibility. `visibility` is a discretely
   * animated property, and transitioning it alongside opacity left the panel
   * computing `hidden` even while the hover rule was matching - the selector
   * was right, the rule was there, and it still would not show. This pattern
   * has no such failure mode: nothing is interpolated that cannot be.
   */
  opacity: 0; pointer-events: none;
  transform: translateY(-8px);
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease);
}
.nav-co:hover .nav-co-menu,
.nav-co:focus-within .nav-co-menu {
  opacity: 1; pointer-events: auto; transform: translateY(0);
}
/* Keyboard users must not be able to tab into a panel they cannot see. */
.nav-co-menu a { }
.nav-co:not(:hover):not(:focus-within) .nav-co-menu a { visibility: hidden; }

.nav-co-menu a {
  /* Dot in its own column; the name and its descriptor stack in the second. */
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto; align-items: center;
  gap: 0 0.6rem; padding: 0.55rem 0.7rem; min-height: 44px;
  color: var(--co); text-decoration: none;
  font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.04em;
  transition: background 0.18s var(--ease);
}
.nav-co-menu a:hover, .nav-co-menu a:focus-visible { background: var(--raised); }
.nav-co-menu a em {
  grid-column: 2; font-style: normal; font-size: 0.6875rem;
  color: var(--text-low); letter-spacing: 0.06em; text-transform: uppercase;
}
.nav-co-dot {
  width: 6px; height: 6px; background: var(--co); flex: none;
  grid-row: span 2; align-self: center;
  transition: transform 0.22s var(--ease);
}
.nav-co-menu a:hover .nav-co-dot { transform: scale(1.6); }

/* The company links only exist in the drawer on touch, where hover cannot. */
.nav-panel-co a {
  display: flex; align-items: center; gap: 0.6rem; color: var(--co);
}
.nav-panel-co { margin-top: 0.15rem; }

/* --- footer: social marks ---------------------------------------------- */
.foot-social { display: flex; align-items: center; gap: 0.55rem; min-height: 26px; }
.foot-ico { width: 15px; height: 15px; flex: none; color: var(--text-low); transition: color 0.2s var(--ease); }
.foot-social:hover .foot-ico { color: var(--signal-ink); }

/* --- 06 the trade: certifications -------------------------------------- */
.certs {
  display: grid; gap: clamp(1.6rem, 4vw, 3rem);
  margin-top: clamp(2rem, 5vw, 3rem);
  padding-top: clamp(1.6rem, 4vw, 2.4rem);
  border-top: 1px solid var(--hair);
}
@media (min-width: 860px) { .certs { grid-template-columns: 1.1fr 1fr; } }
.certs-h {
  font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.10em;
  text-transform: uppercase; color: var(--text-low); margin-bottom: 1rem;
}
.certs-h--gap { margin-top: 2rem; }
.cert-list { list-style: none; margin: 0; padding: 0; counter-reset: cert; display: grid; gap: 0.1rem; }
.cert-list li {
  counter-increment: cert;
  display: grid; grid-template-columns: 2.2rem 1fr; align-items: baseline;
  gap: 0 0.5rem; padding: 0.62rem 0; border-bottom: 1px solid var(--hair);
}
.cert-list li::before {
  content: counter(cert, decimal-leading-zero);
  font-family: var(--f-mono); font-size: 0.6875rem; color: var(--signal-ink);
}
.cert-list b { font-weight: 600; font-size: 0.95rem; }
.cert-list span {
  grid-column: 2; font-family: var(--f-mono); font-size: 0.6875rem;
  letter-spacing: 0.03em; color: var(--text-low); margin-top: 0.2rem;
}
/* In-progress reads as intent, not achievement, so it is quieter than the rest. */
.chips--quiet span { color: var(--text-low); border-style: dashed; }

/* --- 01 about: the acknowledgement bar --------------------------------- */
.land {
  padding: clamp(1.3rem, 3vw, 1.9rem);
  display: grid; gap: 1rem;
}
@media (min-width: 860px) {
  .land {
    grid-template-columns: minmax(11rem, auto) 1fr;
    column-gap: clamp(2rem, 5vw, 3.5rem);
    align-items: start;
  }
  .land-text { grid-column: 2; grid-row: 1 / span 3; }
}
.land-eyebrow {
  font-family: var(--f-mono); font-size: 0.6875rem; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--signal-ink);
}
/* The Nations' own names, set as the heading they deserve rather than buried
   mid-paragraph. */
.land-city {
  font-family: var(--f-display); font-size: clamp(1.05rem, 1.6vw, 1.3rem);
  line-height: 1.45; margin-top: 0.5rem; letter-spacing: -0.01em;
}
.land-sub {
  font-family: var(--f-mono); font-size: 0.6875rem; letter-spacing: 0.05em;
  color: var(--text-low); margin-top: 0.5rem;
}
.land-text p { color: var(--text-mid); font-size: 0.92rem; }
.land-text p + p { margin-top: 0.8rem; }
.land-text strong { color: var(--text); font-weight: 600; }
.land-quote { font-style: italic; color: var(--text-low) !important; }

/* --- 11 worked with: the logo bar ---------------------------------------- */
/*
 * One infinite row moving right to left. The track holds the list twice and
 * slides exactly -50% (one list, trailing gap included), so the second copy
 * lands where the first began and the loop has no seam. Only the track's
 * transform animates, so the whole row is one composited layer: no per-logo
 * filter or shadow is repainted as it moves.
 *
 * Colour logos do not survive this site: on the dark ground Fraserway's
 * wordmark, ISCBC's text, Prospera's navy and three black wordmarks all
 * vanish. So every logo is an alpha MASK (assets/img/logos/) with the
 * theme's own ink painted through it - one family, and it re-lights with the
 * theme instead of needing a second set for paper.
 */
.mq {
  --mq-s: 1;
  --mq-gap: clamp(2.6rem, 6vw, 4.8rem);
  --mq-dur: 44s;
  border-block: 1px solid var(--hair);
  color: var(--text-mid);
}
.mq-viewport {
  overflow: hidden;
  padding-block: clamp(1.6rem, 3.4vw, 2.4rem);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}
.mq-track { display: flex; width: max-content; animation: mq-slide var(--mq-dur) linear infinite; }
.mq-list {
  display: flex; align-items: center; flex: none;
  gap: var(--mq-gap); padding: 0 var(--mq-gap) 0 0; margin: 0; list-style: none;
}
.mq-item { display: flex; align-items: center; transition: color 0.25s var(--ease); }
@media (hover: hover) { .mq-item:hover { color: var(--text); } }
@keyframes mq-slide { to { transform: translate3d(-50%, 0, 0); } }
/* Hover pauses, so a logo can be read. There is no pause button (removed at
   the owner's request); prefers-reduced-motion gets a static row instead. */
.mq:hover .mq-track { animation-play-state: paused; }

/*
 * Sized by AREA, not by height. A shared height makes a 7.6:1 wordmark
 * (Fraserway) nearly four times the ink of a 2.9:1 one (Shaw); equal area
 * makes them read as the same size. --r is the mask's measured aspect
 * ratio, --h the height that gives ~5,600px of area, nudged by eye where a
 * mark is unusually heavy (Shaw, RE/MAX, down) or its lockup carries tiny
 * type (BMC, up).
 */
.mq-logo {
  display: block; flex: none;
  height: calc(var(--h) * var(--mq-s)); aspect-ratio: var(--r);
  background-color: currentColor;
  -webkit-mask-position: center; -webkit-mask-size: contain; -webkit-mask-repeat: no-repeat;
          mask-position: center;         mask-size: contain;         mask-repeat: no-repeat;
}
.mq-logo--fraserway  { --r: 7.615; --h: 27px; }
.mq-logo--iscbc      { --r: 3.833; --h: 38px; }
.mq-logo--prospera   { --r: 3.686; --h: 37px; }
.mq-logo--concentrix { --r: 6.453; --h: 29.5px; }
.mq-logo--bmc        { --r: 5.524; --h: 35px; }
.mq-logo--telus      { --r: 5.237; --h: 33px; }
.mq-logo--shaw       { --r: 2.92;  --h: 38.5px; }
.mq-logo--remax      { --r: 3.735; --h: 35.5px; }
/*
 * The masks are fetched only once the bar is near (site.js adds .is-near).
 * CSS images are requested for every rendered element whether or not it is
 * on screen, so without this ~50KB of logos would join the first load for a
 * section most of a page away. Without JavaScript they apply at once.
 */
.mq.is-near .mq-logo--fraserway,  html:not(.js) .mq-logo--fraserway  { -webkit-mask-image: url(../img/logos/fraserway.png);    mask-image: url(../img/logos/fraserway.png); }
.mq.is-near .mq-logo--iscbc,      html:not(.js) .mq-logo--iscbc      { -webkit-mask-image: url(../img/logos/iscbc.png);        mask-image: url(../img/logos/iscbc.png); }
.mq.is-near .mq-logo--prospera,   html:not(.js) .mq-logo--prospera   { -webkit-mask-image: url(../img/logos/prospera.svg);     mask-image: url(../img/logos/prospera.svg); }
.mq.is-near .mq-logo--concentrix, html:not(.js) .mq-logo--concentrix { -webkit-mask-image: url(../img/logos/concentrix.svg);   mask-image: url(../img/logos/concentrix.svg); }
.mq.is-near .mq-logo--bmc,        html:not(.js) .mq-logo--bmc        { -webkit-mask-image: url(../img/logos/bmc-networks.svg); mask-image: url(../img/logos/bmc-networks.svg); }
.mq.is-near .mq-logo--telus,      html:not(.js) .mq-logo--telus      { -webkit-mask-image: url(../img/logos/telus.svg);        mask-image: url(../img/logos/telus.svg); }
.mq.is-near .mq-logo--shaw,       html:not(.js) .mq-logo--shaw       { -webkit-mask-image: url(../img/logos/shaw.svg);         mask-image: url(../img/logos/shaw.svg); }
.mq.is-near .mq-logo--remax,      html:not(.js) .mq-logo--remax      { -webkit-mask-image: url(../img/logos/remax.svg);        mask-image: url(../img/logos/remax.svg); }

@media (max-width: 640px) {
  .mq { --mq-s: 0.78; --mq-gap: 2.2rem; --mq-dur: 34s; }
}
/* Nothing moves: one centred, wrapped row of the eight. */
@media (prefers-reduced-motion: reduce) {
  .mq-track { animation: none; width: auto; }
  .mq-viewport { -webkit-mask-image: none; mask-image: none; }
  .mq-list { flex: 1 1 auto; flex-wrap: wrap; justify-content: center; row-gap: 1.8rem; padding-right: 0; }
  .mq-list[aria-hidden="true"] { display: none; }
}


/* --- the ribbon --------------------------------------------------------- */
/*
 * Absolutely positioned over the document, not fixed: the geometry is in
 * document coordinates so it scrolls with the page, and only the reveal clip
 * changes with scroll. Behind everything, never interactive.
 *
 * `color` is set here and the gradient stops use `currentColor`, so one
 * declaration re-themes the whole ribbon.
 */
#thread {
  position: absolute; inset: 0 auto auto 0; z-index: -8;
  pointer-events: none; overflow: visible;
  color: var(--signal);
}
/* The filled ribbon, shaded along its length by the generated gradient. */
.thread-body { fill: url(#thread-shade); opacity: 0.26; }
/*
 * A blurred copy underneath for depth. The radius is trimmed from what this
 * looked like at first (16px / 10px narrow) - a CSS blur's cost scales with
 * roughly the square of its radius, and this path spans nearly the whole
 * document, so it is one of the larger single costs behind the ribbon's
 * scroll-time GPU load (see thread.js's tick() for the rest of that fix).
 * At 0.05-0.07 opacity the softness reads the same; only the radius shrank.
 */
.thread-glow { fill: currentColor; opacity: 0.07; filter: blur(10px); }
/* One lit edge. Thin, and brighter than the body, so the twist has a highlight. */
.thread-edge { stroke: currentColor; stroke-width: 1.2; opacity: 0.30; fill: none; }

:root[data-theme="light"] #thread { color: var(--signal-ink); }
:root[data-theme="light"] .thread-body { opacity: 0.17; }
:root[data-theme="light"] .thread-glow { opacity: 0.05; }
:root[data-theme="light"] .thread-edge { opacity: 0.20; }

@media (max-width: 720px) {
  .thread-glow { filter: blur(6px); }
  .thread-edge { stroke-width: 0.9; }
}
@media (prefers-reduced-motion: reduce) {
  .thread-body { opacity: 0.14; }
  .thread-glow { opacity: 0.04; }
  .thread-edge { opacity: 0.16; }
}

/* --- 10 worked with: the testimonial mosaic ----------------------------- */
/*
 * A four-column mosaic where two cards span two columns and one spans two rows,
 * so the longer accounts get the room they need instead of every quote being
 * trimmed to the shortest. It collapses to a single column on a phone, where a
 * mosaic is just a stack with extra rules.
 */
.tgrid-h {
  margin-top: clamp(2.4rem, 5vw, 3.4rem);
  margin-bottom: clamp(1.2rem, 2.5vw, 1.6rem);
  font-family: var(--f-mono); font-size: 0.70rem; letter-spacing: 0.10em;
  text-transform: uppercase; color: var(--text-low); font-weight: 400;
}
.tgrid { display: grid; gap: clamp(0.9rem, 2vw, 1.4rem); }
@media (min-width: 760px) and (max-width: 1099px) {
  .tgrid { grid-template-columns: repeat(2, 1fr); }
}
/*
 * THREE COLUMNS, AUTO ROWS. Six cards of real, named testimonials divide into
 * this evenly (two rows of three), so unlike the five-card mosaic this once
 * was, no card needs an explicit span to tessellate - plain auto-flow already
 * fills the rectangle with no gaps. A uniform grid also suits genuine reviews
 * better than an asymmetric bento: real testimonials of similar length read as
 * more credible presented plainly than dressed up in a "designed" layout.
 *
 * Rows stay AUTO, not a fixed fraction: each row takes its tallest card and
 * every card in it stretches to match, so a longer quote is never cut instead
 * of the row simply being as tall as it needs to be.
 */
@media (min-width: 1100px) {
  .tgrid {
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: auto;
  }
}

.tcard {
  margin: 0; padding: clamp(1.2rem, 2.6vw, 1.7rem);
  /* No overflow rule and no line clamp anywhere: a testimonial that is cut off
     is worse than one that makes its card taller. */
  border: 1px solid var(--hair);
  display: grid; gap: 1rem; align-content: start;
  transition: transform 0.35s var(--ease), border-color 0.3s var(--ease);
}
@media (hover: hover) { .tcard:hover { transform: translateY(-3px); border-color: var(--edge); } }
@media (prefers-reduced-motion: reduce) { .tcard { transition: none; } .tcard:hover { transform: none; } }

/*
 * FIVE GROUNDS, one per card, drawn from the entity palette rather than five
 * shades of the same orange. Each is that entity's hue mixed heavily into the
 * card token, so they stay a family: distinct enough to tell apart, quiet
 * enough that the mosaic still reads as one block.
 */
.tcard--gold  { background: color-mix(in srgb, var(--holdco)  16%, var(--card)); border-color: color-mix(in srgb, var(--holdco)  34%, transparent); }
.tcard--teal  { background: color-mix(in srgb, var(--systems) 15%, var(--card)); border-color: color-mix(in srgb, var(--systems) 32%, transparent); }
.tcard--moss  { background: color-mix(in srgb, var(--labs)    15%, var(--card)); border-color: color-mix(in srgb, var(--labs)    32%, transparent); }
.tcard--slate { background: color-mix(in srgb, var(--digital) 14%, var(--card)); border-color: color-mix(in srgb, var(--digital) 30%, transparent); }
.tcard--plum  { background: color-mix(in srgb, var(--studios) 14%, var(--card)); border-color: color-mix(in srgb, var(--studios) 30%, transparent); }

/* The avatar mark picks up its own card's hue. */
.tcard--gold  .tcard-mark { --mk: var(--holdco); }
.tcard--teal  .tcard-mark { --mk: var(--systems); }
.tcard--moss  .tcard-mark { --mk: var(--labs); }
.tcard--slate .tcard-mark { --mk: var(--digital); }
.tcard--plum  .tcard-mark { --mk: var(--studios); }

.tcard-who { display: flex; align-items: center; gap: 0.7rem; }
.tcard-mark {
  width: 30px; height: 30px; flex: none; border-radius: 50%;
  --mk: var(--signal);
  background: linear-gradient(140deg, color-mix(in srgb, var(--mk) 70%, transparent), transparent 70%);
  border: 1px solid color-mix(in srgb, var(--mk) 45%, transparent);
}
.tcard-who b { display: block; font-size: 0.88rem; font-weight: 600; }
.tcard-who em {
  display: block; font-style: normal; font-family: var(--f-mono);
  font-size: 0.6875rem; letter-spacing: 0.05em; color: var(--text-low);
  text-transform: uppercase; margin-top: 0.15rem;
}
.tcard blockquote { margin: 0; }
.tcard-head { font-size: clamp(1.02rem, 0.95rem + 0.35vw, 1.22rem); line-height: 1.4; font-weight: 500; }
.tcard-body { margin-top: 0.8rem; font-size: 0.9rem; line-height: 1.6; color: var(--text-mid); }
/* A solid token, not a transparent mix. Alpha text over a mixed ground is how
   a card ends up measuring its own background against itself. */
.tcard-body { opacity: 0.9; }
/* Every tinted ground is lighter than the plain card token, so the quietest ink
   drops below AA on them at 11px. One step up clears it on all five. */
.tcard-who em { color: var(--text-mid); }

/* --- the trade: folded domains and the certification drawer ------------- */
/*
 * On a phone each domain becomes its own disclosure: the bar you tapped opens
 * its chips directly underneath, instead of the same eight names appearing
 * again as a separate list further down.
 */
.domain-toggle {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; cursor: pointer;
}
.domain-toggle .domain-name { pointer-events: none; }
/* A caret that turns, so the row reads as openable before it is opened. */
.domain-toggle .domain-name::after {
  content: ''; float: right; margin-top: 0.45em;
  width: 0; height: 0;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid currentColor; opacity: 0.5;
  transition: transform 0.22s var(--ease);
}
.domain-toggle[aria-expanded="true"] .domain-name::after { transform: rotate(180deg); }
/* One column once folded: the panel is the domain's second row, so it opens
   under the bar that was tapped, never beside it. */
.is-folded .domain { grid-template-columns: minmax(0, 1fr); padding-block: 0.35rem; }
.domain-panel { padding: 0.9rem 0 0.3rem; }
.domain-panel[hidden] { display: none; }

/* The certification drawer, on every screen size. */
.cert-more {
  margin-top: 1rem; min-height: 40px; padding: 0.5rem 1rem;
  font-family: var(--f-mono); font-size: 0.6875rem; letter-spacing: 0.08em;
  text-transform: uppercase; cursor: pointer;
  background: transparent; color: var(--text-mid);
  border: 1px solid var(--hair-2); border-radius: 3px;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease), background 0.2s var(--ease);
}
.cert-more:hover, .cert-more:focus-visible {
  color: var(--text); border-color: var(--edge); background: var(--raised);
}
.cert-more::after {
  content: ''; display: inline-block; margin-left: 0.5rem; vertical-align: middle;
  width: 0; height: 0;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid currentColor; opacity: 0.6;
}
.cert-more[aria-expanded="true"]::after { transform: rotate(180deg); }
.cert-list li[hidden] { display: none; }

/* --- testimonial star ratings ------------------------------------------ */
/*
 * The stars take their colour from the card, and the card's colour is a theme
 * token, so they re-light with the page rather than being a fixed gold that
 * looks minted on paper and dull on the dark ground. Empty stars are the same
 * hue at low opacity, not grey, so a four-star row still reads as one object.
 */
.tcard-stars {
  display: flex; align-items: center; gap: 3px;
  color: var(--signal-ink);
  margin-bottom: 0.15rem;
}
.star { width: 15px; height: 15px; flex: none; }
.star.is-empty { opacity: 0.28; }
.star.is-half  { opacity: 0.95; }
.tcard-score {
  margin-left: 0.45rem; font-family: var(--f-mono); font-size: 0.6875rem;
  letter-spacing: 0.04em; color: var(--text-mid);
}

/* Each card lends the stars its own hue, so the mosaic stays one family. */
.tcard--gold  .tcard-stars { color: var(--holdco); }
.tcard--teal  .tcard-stars { color: var(--systems); }
.tcard--moss  .tcard-stars { color: var(--labs); }
.tcard--slate .tcard-stars { color: var(--digital); }
.tcard--plum  .tcard-stars { color: var(--studios); }
/* On paper those hues are the contrast-tuned variants already, so nothing
   further is needed: the tokens themselves swap with the theme. */

.tgrid-avg {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.55rem;
  margin-top: clamp(1.1rem, 2.4vw, 1.5rem);
  font-family: var(--f-mono); font-size: 0.6875rem; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--text-low);
}
.tgrid-avg .tcard-stars { margin-bottom: 0; color: var(--signal-ink); }
.tgrid-avg b {
  font-family: var(--f-display); font-size: 1.45rem; letter-spacing: -0.02em;
  color: var(--signal-ink); text-transform: none;
}
