/* ══════════════════════════════════════════════════════════════════════════
   THE WORLD BELOW — the world itself.
   One persistent cavern under every page: a light shaft fed by the sky above,
   rock strata drawn from seeded noise, crystals that grow and age, drifting
   dust, and the island's real weather falling in from overhead — rain, cloud
   and storm are READ from the server, never invented here.

   Architecture is the proven Fangs & Ferns atmosphere stack (sky / ridges /
   growth / particles / fog / grain / vignette + frame governor), re-cut for
   this brand: stone, depth, cold cyan light and an ember or two. No candy.

   Layering follows this site's own precedent (fi-atmos): fixed, z -3..-1,
   pointer-events none, UNDER the in-flow content. body backgrounds go
   transparent only when the world has actually mounted (body.twb-world-on),
   so a JS failure leaves every page exactly as it is today.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  --twbw-a: #0a141c;          /* cavern upper wash  (JS-driven) */
  --twbw-b: #05080b;          /* cavern floor wash  (JS-driven) */
  --twbw-sun: #dfebf2;        /* shaft light colour (JS-driven) */
  --twbw-so: .32;             /* shaft strength     (JS-driven) */
  --twbw-sx: 62%;             /* shaft x            (JS-driven) */
  --twbw-sy: 18%;             /* shaft y            (JS-driven) */
  --twbw-haze: .34;           /* fog strength       (JS-driven) */
  --twbw-px: 0;               /* pointer parallax   (JS-driven) */
  --twbw-py: 0;
  --twbw-rb-h: 34px;          /* ribbon height */
}

/* ── the stack ──────────────────────────────────────────────────────────── */
#twbWorld { position: fixed; inset: 0; z-index: -3; overflow: hidden;
  pointer-events: none; contain: strict; }
body.twb-world-on { background: transparent; }
/* the page keeps breathing room above the instrument ribbon */
body.twb-has-ribbon { padding-bottom: calc(var(--twbw-rb-h) + 10px); }

.tw-sky { position: absolute; inset: 0;
  background:
    radial-gradient(130% 100% at var(--twbw-sx) -12%, var(--twbw-a) 0%, transparent 58%),
    linear-gradient(180deg, var(--twbw-a) 0%, var(--twbw-b) 78%);
  transition: background 1.2s linear; }

/* the shaft: daylight (or worse) arriving from the world above */
.tw-shaft { position: absolute; inset: 0; opacity: var(--twbw-so);
  transition: opacity 1.1s linear;
  background: radial-gradient(46% 34% at var(--twbw-sx) var(--twbw-sy),
    var(--twbw-sun) 0%, transparent 70%);
  filter: blur(6px);
  transform: translate3d(calc(var(--twbw-px) * 6px), calc(var(--twbw-py) * 4px), 0); }

.tw-rays { position: absolute; inset: -14% -22%;
  opacity: calc(.42 + var(--twbw-so) * .5); transition: opacity 1.2s linear;
  transform: translate3d(calc(var(--twbw-px) * 10px), 0, 0); }
.tw-rays i { position: absolute; top: -12%; left: 50%; width: 12vw; height: 150%;
  background: linear-gradient(180deg, rgba(var(--sw-ink-rgb), 0) 0%,
    rgba(var(--sw-accent-rgb), .05) 22%, rgba(var(--sw-ink-rgb), .035) 55%,
    rgba(var(--sw-ink-rgb), 0) 88%);
  transform: skewX(-9deg);
  animation: twbwRay 24s ease-in-out infinite alternate; }
.tw-rays i:nth-child(1) { left: 40%; width: 8vw;  animation-duration: 23s; }
.tw-rays i:nth-child(2) { left: 54%; width: 15vw; animation-duration: 17s; animation-delay: -6s;  opacity: .8; }
.tw-rays i:nth-child(3) { left: 67%; width: 6vw;  animation-duration: 26s; animation-delay: -11s; opacity: .7; }
.tw-rays i:nth-child(4) { left: 79%; width: 11vw; animation-duration: 21s; animation-delay: -3s;  opacity: .5; }
@keyframes twbwRay { from { transform: skewX(-9deg) translateX(-1.2vw); }
                     to   { transform: skewX(-7deg) translateX(1.2vw); } }

/* canvases: strata behind, growth in front of them */
.tw-ridge, .tw-grow, .tw-fx { position: absolute; inset: 0; width: 100%; height: 100%; }
.tw-grow { opacity: .95; }

/* floor mist */
.tw-fog { position: absolute; inset: 0; opacity: var(--twbw-haze);
  transition: opacity 1.4s linear; }
.tw-fog i { position: absolute; left: -60%; width: 220%; height: 32vh; border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(var(--sw-ink-rgb), .05), transparent 70%);
  filter: blur(2px);
  animation: twbwFog 96s linear infinite; }
.tw-fog i:nth-child(1) { bottom: 4vh; animation-duration: 88s; }
.tw-fog i:nth-child(2) { bottom: 20vh; height: 24vh; opacity: .7; animation-duration: 112s; animation-delay: -40s; }
.tw-fog i:nth-child(3) { bottom: -6vh; height: 42vh; opacity: .85; animation-duration: 140s; animation-delay: -70s; }
@keyframes twbwFog { from { transform: translateX(-6%); } to { transform: translateX(6%); } }

/* readability scrim: the world stays a background even at its busiest */
.tw-scrim { position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgba(var(--twb-scrim-rgb), .30) 0%,
    rgba(var(--twb-scrim-rgb), .12) 30%,
    rgba(var(--twb-scrim-rgb), .34) 78%,
    rgba(var(--twb-scrim-rgb), .55) 100%); }

.tw-grain { position: absolute; inset: 0; opacity: .05; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.92' numOctaves='2'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E"); }
.tw-vig { position: absolute; inset: 0;
  background: radial-gradient(115% 95% at 50% 42%, transparent 40%,
    rgba(var(--twb-scrim-rgb), .5) 100%); }

/* ── weather states (body attribute, written by the engine) ─────────────── */
body[data-twbw="cloud"] .tw-rays { opacity: .18; }
body[data-twbw="rain"]  .tw-rays { opacity: .14; }
body[data-twbw="storm"] .tw-rays { opacity: .05; }
body[data-twbw="storm"] .tw-vig {
  background: radial-gradient(110% 90% at 50% 40%, transparent 30%,
    rgba(var(--twb-scrim-rgb), .78) 100%); }

/* ── precipitation overlay ──────────────────────────────────────────────────
   Rain has to fall IN FRONT of the page. The world sits under the content,
   and pages carry opaque art (the landing hero plate) that would swallow a
   behind-the-page shower whole — the reference map draws its weather over
   the map for the same reason. Dust stays in the world; only falling water
   rides this layer. Under the topbar (z1000), the ribbon (58) and the
   drawer (70); above everything the page itself draws. */
#twbWx { position: fixed; inset: 0; z-index: 40; pointer-events: none;
  width: 100%; height: 100%; }
body[data-twbw="cloud"] #twbWx {
  background: linear-gradient(180deg, rgba(124, 141, 154, .10) 0%,
    rgba(124, 141, 154, .03) 42%, transparent 68%); }
:root[data-theme="light"] body[data-twbw="cloud"] #twbWx {
  background: linear-gradient(180deg, rgba(86, 107, 123, .16) 0%,
    rgba(86, 107, 123, .05) 42%, transparent 68%); }
body[data-twbw="storm"] #twbWx {
  background: radial-gradient(120% 100% at 50% 30%, rgba(4, 10, 14, .10) 40%,
    rgba(4, 10, 14, .34) 100%); }
:root[data-theme="light"] body[data-twbw="storm"] #twbWx {
  background: radial-gradient(120% 100% at 50% 30%, rgba(40, 54, 66, .10) 40%,
    rgba(40, 54, 66, .30) 100%); }
#twbFlash { position: fixed; inset: 0; z-index: 45; pointer-events: none;
  background: #dfeef5; opacity: 0; }

/* ── chat, seated in the instruments instead of floating over the page ──────
   The floating launcher hovered over page headings and crowded the strip,
   and the drawer ran top-to-bottom over the topbar and the ribbon (his
   2026-08-03 screenshots). CSS-only re-seat: the shared chat script keeps
   its ids, handlers and badge untouched.
   - the launcher becomes a compact control ON the ribbon, right side,
     beside the atmosphere button;
   - the panel opens BETWEEN the bars: below the topbar (and its sub-row
     when one covers), above the ribbon, so the chrome never disappears. */
body.twb-has-ribbon #twbRibbon .twrb-right { margin-right: 108px; }
body.twb-has-ribbon .sw-chat-toggle {
  left: auto; right: clamp(12px, 2.4vw, 26px); bottom: 4px; z-index: 59;
  padding: 5px 13px; gap: 7px;
  font-size: 10.5px; letter-spacing: .14em;
  border-radius: 5px;
  /* the one lit control on the strip: chat is a door, not a readout */
  background: rgba(var(--sw-accent-rgb), .14);
  border-color: rgba(var(--sw-accent-rgb), .5);
  color: var(--twb-cyan);
  box-shadow: 0 0 12px rgba(var(--sw-accent-rgb), .22);
}
body.twb-has-ribbon .sw-chat-toggle:hover {
  transform: none; color: var(--sw-accent-ink);
  background: var(--sw-accent); border-color: var(--sw-accent);
  box-shadow: 0 0 16px rgba(var(--sw-accent-rgb), .45);
}
body.twb-has-ribbon .sw-chat-toggle svg { width: 13px; height: 13px; }
body.twb-has-ribbon .sw-chat-panel {
  /* --twbw-chrome-top is MEASURED off the real topbar (nav + sub-row where
     one exists) by twb-live.js — a guessed constant covers the sub-row on
     half the pages */
  top: var(--twbw-chrome-top, var(--twb-navrow, 72px));
  bottom: var(--twbw-rb-h);
  border-top: 1px solid var(--sw-hair);
  border-bottom: 1px solid var(--sw-hair);
  border-top-right-radius: 10px;
  border-bottom-right-radius: 10px;
}

/* ── motion tiers ───────────────────────────────────────────────────────── */
body[data-twbw-mo="off"]  .tw-rays i,
body[data-twbw-mo="off"]  .tw-fog i { animation: none !important; }
body[data-twbw-mo="calm"] .tw-rays i,
body[data-twbw-mo="calm"] .tw-fog i { animation-play-state: paused; }
body.shot .tw-rays i, body.shot .tw-fog i { animation: none !important; }

html[data-twbw-perf="low"] .tw-fog,
html[data-twbw-perf="low"] .tw-grain { display: none; }
html[data-twbw-perf="low"] .tw-rays i { animation: none !important; }
html[data-twbw-perf="low"] .tw-shaft { filter: none; }
html.twbw-android .tw-rays,
html.twbw-android .tw-grain,
html.twbw-android .tw-fog { display: none; }
html.twbw-android .tw-shaft { filter: none; }

/* ── landing hero: let the weather through ──────────────────────────────────
   The hero's wash was an OPAQUE gradient down to --sw-bg, which would wall the
   world off behind the most important band of the most important page. With
   the world mounted it becomes a scrim: the key-art plate and the copy stay,
   the rain shows behind them. Loaded after twb-theme.css, same specificity →
   this layer wins (that ordering is load-bearing; see the voice-page law). */
body.twb-world-on .hero.sw-hero-wash {
  background:
    radial-gradient(120% 90% at 50% 116%, rgba(var(--sw-accent-rgb), .10), transparent 58%),
    linear-gradient(180deg, rgba(var(--twb-scrim-rgb), .38) 0%, rgba(var(--twb-scrim-rgb), .18) 74%);
}
body.twb-world-on .hero.sw-hero-wash::after {
  background: linear-gradient(180deg, transparent, rgba(var(--twb-scrim-rgb), .42) 90%);
}
/* under 900px the key-art plate lives in the hero BACKGROUND (not ::before),
   and this override outranks that media rule (a media query adds no
   specificity) — so the plate is re-stated here rather than lost */
@media (max-width: 900px) {
  body.twb-world-on .hero.sw-hero-wash {
    background:
      linear-gradient(180deg, rgba(var(--twb-scrim-rgb), .82) 0%, rgba(var(--twb-scrim-rgb), .88) 58%, rgba(var(--twb-scrim-rgb), .55) 100%),
      radial-gradient(110% 70% at 50% 112%, rgba(var(--sw-accent-rgb), .14), transparent 58%),
      url('/assets/brand/twb-plate-640.webp') center top / cover no-repeat;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE INSTRUMENT RIBBON — live state, never competing with the page.
   Same instrument as the reference strip: pulse + On the island n / max +
   bars, Next restart, Weather, and a source chip. (No "next storm" — his
   call, 2026-08-03.)
   ══════════════════════════════════════════════════════════════════════════ */
#twbRibbon { position: fixed; left: 0; right: 0; bottom: 0; height: var(--twbw-rb-h);
  z-index: 58; display: flex; align-items: center; justify-content: space-between;
  gap: 14px; padding: 0 clamp(12px, 2.4vw, 26px);
  background: linear-gradient(180deg, rgba(var(--twb-scrim-rgb), .72), rgba(var(--twb-scrim-rgb), .88));
  -webkit-backdrop-filter: blur(9px); backdrop-filter: blur(9px);
  border-top: 1px solid var(--sw-hair-2);
  font-family: var(--sw-mono, ui-monospace, monospace); }
.twrb-left, .twrb-mid, .twrb-right { display: flex; align-items: center; gap: 10px; min-width: 0; }
.twrb-k { font-size: 9.5px; letter-spacing: .19em; text-transform: uppercase;
  color: var(--sw-ink-3); white-space: nowrap; }
.twrb-v { font-size: 11px; color: var(--sw-ink-2); letter-spacing: .04em; white-space: nowrap; }
.twrb-v.soon { color: var(--twb-ember); }
.twrb-sep { width: 1px; height: 13px; background: var(--sw-hair); }
.twrb-pulse { position: relative; width: 7px; height: 7px; flex: 0 0 auto; }
.twrb-pulse i { position: absolute; inset: 0; border-radius: 50%;
  background: var(--twb-mint); box-shadow: 0 0 9px var(--twb-mint); }
.twrb-pulse::after { content: ""; position: absolute; inset: -4px; border-radius: 50%;
  border: 1px solid var(--twb-mint); opacity: 0; animation: twrbPing 3s ease-out infinite; }
@keyframes twrbPing { 0% { transform: scale(.5); opacity: .8; }
  70%, 100% { transform: scale(1.6); opacity: 0; } }
.twrb-bars { display: flex; align-items: flex-end; gap: 2px; height: 14px; margin-left: 4px; }
.twrb-bars i { width: 2px; height: 14px; background: var(--twb-cyan); opacity: .55;
  transform-origin: bottom; transform: scaleY(.3); transition: transform .55s ease; }
.twrb-src { font-size: 9px; letter-spacing: .2em; text-transform: uppercase;
  padding: 3px 8px; border: 1px solid var(--sw-hair); border-radius: 3px;
  color: var(--sw-ink-3); white-space: nowrap; }
.twrb-src.on   { color: var(--twb-cyan);  border-color: rgba(var(--sw-accent-rgb), .4); }
.twrb-src.part { color: var(--twb-mint);  border-color: rgba(99, 210, 168, .35); }
.twrb-src.off  { color: var(--twb-ember); border-color: rgba(var(--twb-ember-rgb, 215, 122, 95), .35); }
#twbAtmosBtn { pointer-events: auto; width: 24px; height: 22px; display: grid;
  place-items: center; color: var(--sw-ink-3); background: none; border: 0;
  cursor: pointer; padding: 0; }
#twbAtmosBtn:hover { color: var(--sw-ink); }
#twbAtmosBtn svg { width: 14px; height: 14px; }
body[data-twbw-mo="off"] .twrb-pulse::after,
html[data-twbw-perf="low"] .twrb-pulse::after { animation: none; }
html[data-twbw-perf="low"] #twbRibbon,
html.twbw-android #twbRibbon { -webkit-backdrop-filter: none; backdrop-filter: none;
  background: linear-gradient(180deg, rgba(var(--twb-scrim-rgb), .92), rgba(var(--twb-scrim-rgb), .98)); }
@media (max-width: 1080px) { .twrb-mid { display: none; } }
@media (max-width: 560px)  { .twrb-right .twrb-src { display: none; } }

/* ── atmosphere drawer ──────────────────────────────────────────────────── */
#twbAtmos { position: fixed; right: clamp(12px, 2.4vw, 26px);
  bottom: calc(var(--twbw-rb-h) + 12px); width: 306px; z-index: 70;
  background: linear-gradient(180deg, rgba(var(--twb-scrim-rgb), .94), rgba(var(--twb-scrim-rgb), .985));
  border: 1px solid var(--sw-hair); border-radius: 10px; padding: 16px 18px;
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  transition: opacity .22s ease, transform .22s ease; }
#twbAtmos.closed { opacity: 0; transform: translateY(10px) scale(.98); pointer-events: none; }
.twat-head { display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 12px; font-family: var(--sw-mono, monospace); font-size: 10px;
  letter-spacing: .22em; text-transform: uppercase; color: var(--sw-ink-3); }
.twat-head button { font-size: 17px; color: var(--sw-ink-3); background: none;
  border: 0; cursor: pointer; line-height: 1; }
.twat-head button:hover { color: var(--sw-ink); }
.twat-row { display: flex; align-items: center; gap: 12px; padding: 8px 0;
  font-size: 12px; color: var(--sw-ink-2); }
.twat-row > span { flex: 0 0 92px; }
.twat-row input[type=range] { flex: 1; height: 2px; background: var(--sw-hair);
  -webkit-appearance: none; appearance: none; outline: 0; }
.twat-row input[type=range]::-webkit-slider-thumb { -webkit-appearance: none;
  width: 12px; height: 12px; border-radius: 50%; background: var(--twb-cyan); }
.twat-row b { font-family: var(--sw-mono, monospace); font-size: 11px;
  color: var(--twb-cyan); flex: 0 0 44px; text-align: right; font-weight: 400; }
.twat-seg { display: flex; gap: 5px; flex-wrap: wrap; }
.twat-seg button { font-family: var(--sw-mono, monospace); font-size: 10px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--sw-ink-3);
  background: none; border: 1px solid var(--sw-hair); border-radius: 5px;
  padding: 4px 9px; cursor: pointer; }
.twat-seg button:hover { color: var(--sw-ink); }
.twat-seg button.on { color: var(--twb-cyan); border-color: rgba(var(--sw-accent-rgb), .45); }
.twat-note { margin: 10px 0 0; font-size: 10.5px; color: var(--sw-ink-3); line-height: 1.5; }
@media (max-width: 480px) { #twbAtmos { right: 12px; left: 12px; width: auto; } }
