/* DiscordPad.

   ART DIRECTION: discord.com's PUBLIC MARKETING SITE.

   Every number in this file that has a reference equivalent was MEASURED off
   the live page over CDP at a 1440x900 viewport, not estimated. The ones that
   drive the composition:

     content column   1240 inside 1440, i.e. a 100px gutter
     nav              80px tall, transparent, over the hero
     hero             910 tall (144 top padding), h1 56/48 in a 491px column,
                      CTA pair centred at y=720, buttons 65px tall
     section module   outer 1240x610, radius 120, overflow VISIBLE
                      inner  727x554, radius 104, overflow HIDDEN, inset 28
                      inner alternates x=128 / x=576, text column 384px
     section rhythm   95-190px padding, 190-300px between containers
     type             h1 56/48 (0.86), h2 36/33 (0.92) w800 tracking -0.36,
                      body 20/26 with POSITIVE tracking 0.32
     ground           FLAT colour; the gradients live on the modules, not the page
     mobile (390)     nav 64, h1 27, module radius 56, 24px gutter

   The two devices that make the reference read as designed rather than
   assembled, and which the previous pass missed entirely:

     1. BREAKOUT. The outer container is overflow:visible and objects straddle
        its edges. That overlap is what creates depth.
     2. FRAGMENTS. The inner block holds UI pieces floating at different
        depths with their own shadows, not one flat screenshot.

   Both are rebuilt here with DiscordPad's own product pieces. No Discord
   illustration, logo, copy or asset is reproduced.

   Claim-state colour (mint/amber) is confined to functional UI. It is
   deliberately NOT used to theme whole marketing sections, because that broke
   the page's coherence with the reference. */

@import url('https://fonts.googleapis.com/css2?family=Archivo:wght@600;700;800;900&family=Figtree:wght@400;500;600;700;800&family=IBM+Plex+Mono:wght@400;500&display=swap');

:root {
  /* The ground is FLAT, like the reference's rgb(26,32,129). Gradients belong
     to the modules. A long gradient on the page was what made the last pass
     drift from navy to washed-out blue. */
  --ground: #191e6e;
  --ground-deep: #12143f;

  --blurple: #5865f2;
  --blurple-hi: #7c85f7;
  --violet: #a879ff;
  --violet-deep: #6c33d6;

  --veil: rgba(255, 255, 255, .07);
  --veil-line: rgba(255, 255, 255, .11);
  --slab: #1a1e56;
  --slab-hi: #232873;

  --ink: #ffffff;
  --ink-dim: rgba(255, 255, 255, .8);
  --ink-mute: rgba(255, 255, 255, .58);
  --on-light: #14163a;

  /* Claim states. Functional UI only. */
  --mint: #3ddc84;
  --amber: #ffc84a;
  --danger: #ff6b6b;

  /* Measured radii. The module's 120/104 pair is the signature. */
  --r-module: 120px;
  --r-art: 104px;
  --r-card: 32px;
  --r-lg: 22px;
  --r-md: 14px;
  --r-sm: 10px;
  --r-pill: 999px;

  --shell: 1240px;
  --gutter: clamp(20px, 6.95vw, 100px);
  --nav-h: 80px;

  --shadow-frag: 0 18px 40px rgba(6, 8, 40, .45);
  --shadow-float: 0 30px 80px rgba(6, 8, 40, .5);
  --shadow-lift: 0 10px 26px rgba(6, 8, 40, .4);

  --ease: cubic-bezier(.22, 1, .36, 1);
  --bounce: cubic-bezier(.34, 1.56, .64, 1);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body { overflow-x: clip; }

body {
  margin: 0;
  color: var(--ink);
  font-family: Figtree, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 20px;                    /* reference body copy is 20px, not 17 */
  line-height: 1.3;                   /* reference leading ratio on body copy */
  letter-spacing: .32px;              /* reference tracks body copy POSITIVE */
  -webkit-font-smoothing: antialiased;
  /* A plain ground. The starfield that used to tile here was six repeating
     radial gradients of white dots; it is gone. */
  background-color: var(--ground);
}

/* -------------------------------------------------------------- typography */

h1, h2, h3, .display {
  font-family: Archivo, Figtree, system-ui, sans-serif;
  font-variation-settings: 'wdth' 108;
  text-transform: uppercase;
  margin: 0;
  text-wrap: balance;
}
/* 56/48 at 1440. The leading is TIGHTER than the size, which is most of what
   makes the reference's headlines read the way they do. */
h1 {
  font-size: clamp(30px, 3.9vw, 56px);
  line-height: .86;
  font-weight: 800;
  letter-spacing: -.01em;
}
/* 36/33 at 1440, sitting in a 384px column. Much smaller than a normal
   marketing h2, which is why the reference feels dense rather than shouty. */
h2 {
  font-size: clamp(26px, 2.5vw, 36px);
  line-height: .92;
  font-weight: 800;
  letter-spacing: -.01em;
}
h3 { font-size: clamp(17px, 1.35vw, 19px); line-height: 1.15; font-weight: 800; letter-spacing: 0; }
p { margin: 0; }
a { color: inherit; }

/* A merkle root or an address is 66 unbroken characters, which on a phone is
   about 130px wider than the screen. `anywhere` rather than `break-all` so it
   only breaks when it genuinely has to. */
.mono {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: .8em;
  letter-spacing: 0;
  overflow-wrap: anywhere;
}
.dim { color: var(--ink-dim); }
.mute { color: var(--ink-mute); }
.tnum { font-variant-numeric: tabular-nums; }
.small { font-size: 16px; }

.eyebrow {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

/* ------------------------------------------------------------------ shell */

.app { position: relative; min-height: 100%; display: flex; flex-direction: column; }

/* 80px, transparent, sitting over the hero rather than pushing it down. On
   scroll it collapses to the compact pill pair the reference shows further
   down the page. */
.nav {
  position: sticky;
  top: 0;
  z-index: 60;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  gap: 10px;
  /* 40px, deliberately WIDER than the 100px content column: the reference
     hangs its nav outside the column rather than aligning to it. */
  padding-inline: clamp(20px, 2.8vw, 40px);
  padding-top: env(safe-area-inset-top, 0px);
  transition: height .3s var(--ease);
}
.nav .spacer { flex: 1; }

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  height: 44px;
  padding: 0 20px 0 15px;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink);
  text-decoration: none;
  font-family: Archivo, sans-serif;
  font-variation-settings: 'wdth' 106;
  font-weight: 800;
  font-size: 22px;
  letter-spacing: -.01em;
  text-transform: uppercase;
  transition: background .28s var(--ease), color .28s var(--ease), box-shadow .28s var(--ease);
}
.brand svg { display: block; }

.navlinks { display: flex; align-items: center; gap: 2px; }
.navlink {
  padding: 8px 14px;
  border-radius: var(--r-pill);
  text-decoration: none;
  font-weight: 500;
  font-size: 16px;
  letter-spacing: .25px;
  color: var(--ink);
  transition: background .18s var(--ease), opacity .18s var(--ease);
}
.navlink:hover, .navlink.is-active { background: rgba(255, 255, 255, .14); }

/* Scrolled: the wordmark becomes a white pill and the links step aside, which
   is what the reference shows at every scroll position past the hero. */
.nav.is-compact .brand { background: #fff; color: var(--on-light); box-shadow: var(--shadow-lift); }
.nav.is-compact .navlinks,
.nav.is-compact .notice-tag,
.nav.is-compact [data-slot="discord"] { display: none; }

.content { flex: 1; }
.wrap { width: min(860px, 100%); margin: 0 auto; padding-inline: var(--gutter); }
.wrap-wide { width: min(var(--shell), 100%); margin: 0 auto; }
.shell { width: min(var(--shell), 100%); margin: 0 auto; padding-inline: 0; }

/* App pages sit below the nav rather than under it. */
.content > .wrap:first-child,
.content > .stack:first-child { padding-top: 36px; }

.notice-tag {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--amber);
  background: rgba(255, 200, 74, .14);
  border: 1px solid rgba(255, 200, 74, .34);
  padding: 4px 10px;
  border-radius: var(--r-pill);
}

/* ---------------------------------------------------------------- buttons */

/* Reference CTAs are 65px tall with 20px labels. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  height: 48px;
  padding: 0 24px;
  border-radius: var(--r-pill);
  border: 0;
  background: rgba(255, 255, 255, .14);
  color: var(--ink);
  font: inherit;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: .25px;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition: transform .2s var(--bounce), background .18s var(--ease), box-shadow .22s var(--ease), color .18s var(--ease);
}
.btn:hover { background: rgba(255, 255, 255, .22); transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn[disabled], .btn[aria-disabled='true'] { opacity: .4; cursor: not-allowed; transform: none; }

.btn-primary { background: #fff; color: var(--on-light); box-shadow: var(--shadow-lift); }
.btn-primary:hover { background: #fff; box-shadow: 0 16px 34px rgba(6, 8, 40, .45); }
.btn-ghost { background: rgba(255, 255, 255, .12); }
.btn-ghost:hover { background: rgba(255, 255, 255, .2); }
.btn-lg { height: 65px; padding: 0 34px; font-size: 20px; }
.btn-block { width: 100%; }

/* ------------------------------------------------------------------- hero */

/* 910 tall, content starting 144 down, headline in a 491px column. */
.hero {
  position: relative;
  min-height: 910px;
  margin-top: calc(var(--nav-h) * -1);
  padding-top: 144px;
  padding-bottom: 100px;
  display: flex;
  flex-direction: column;
}
.hero .lede {
  margin-top: 22px;
  max-width: 491px;
  color: var(--ink-dim);
}
/* The hero graphic is a composition, not a boxed panel: product fragments and
   discs floating at different depths, free to run past the column's right
   edge the way the reference's art does. */
.hero-art {
  position: relative;
  height: 580px;
  min-width: 0;
  /* Pieces are free to hang past this box on the right. */
  overflow: visible;
}

/* --------------------------------------------------------- section module */

/* The repeated object. Outer is a soft veil at radius 120 and overflow
   VISIBLE so breakout pieces can straddle its edge. */
/* The reference leaves 189-301px between consecutive containers (measured
   gaps: 206, 189, 271, 301, plus one 519 outlier where an extra element
   sits between). 120px of padding each side lands a 240px gap, in the middle
   of that range. An earlier value collapsed adjacent padding to 95px, which
   read as a stack of bands rather than objects floating with air around them. */
.section { padding-block: clamp(48px, 8.3vw, 120px); }

/* z-index 0 makes the module a stacking context, so a .behind child paints
   behind the module's own veil and the container partially obscures it. That
   half-hidden layer is the third depth the reference has. */
.module {
  position: relative;
  z-index: 0;
  width: 100%;
  min-height: 610px;
  border-radius: var(--r-module);
  /* The veil alone. A lilac radial used to bloom out of the top-right
     corner of every module; it is gone. */
  background: var(--veil);
  border: 1px solid var(--veil-line);
  overflow: visible;
  display: grid;
  grid-template-columns: 727px 1fr;
  align-items: center;
  gap: 49px;
  padding: 28px;
}
/* Alternate: art right, copy left. Measured at x=576 vs x=128. */
.module.is-flipped { grid-template-columns: 1fr 727px; }

/* The saturated inner block: 727x554, radius 104, clipped. */
.module-art {
  position: relative;
  height: 554px;
  border-radius: var(--r-art);
  overflow: hidden;
  isolation: isolate;
}

/* The copy column is 384px, which is what keeps the reference dense. */
.module-copy { max-width: 384px; }
.module-copy .eyebrow { display: block; margin-bottom: 14px; }

/* ------------------------------------------------------ art: fragments */

/* Pieces of the real product, floating at different depths inside the inner
   block. Each carries its own shadow; together they read as a composition
   rather than a screenshot pasted into a box. */
.frag {
  position: absolute;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-frag);
  background: var(--slab);
  border: 1px solid rgba(255, 255, 255, .12);
  padding: 12px 14px;
  backdrop-filter: blur(6px);
}
.frag-light .member-handle { color: #6b6f8a; }

/* A token disc. DiscordPad's own object, used both inside the art and as a
   breakout piece. */
.disc {
  position: absolute;
  border-radius: 50%;
  background: linear-gradient(150deg, #fff 0%, #cdd2ff 42%, #8d97f5 100%);
  box-shadow: var(--shadow-frag), inset 0 -6px 14px rgba(70, 60, 160, .35);
  display: grid;
  place-items: center;
  color: var(--on-light);
  font-family: Archivo, sans-serif;
  font-weight: 800;
}

/* Scene objects from art.js. */
.obj { display: block; pointer-events: none; }

/* Background layer: partially hidden behind the container. */

/* BREAKOUT. Sits outside the inner block, straddling the outer container's
   edge. The container is overflow:visible precisely so these can hang over. */

/* --------------------------------------------------------- app surfaces */

/* Used by the launch, claim and coin pages. Deliberately NOT the marketing
   module: those pages are read and filled in, so this stays a calm panel. */
.card {
  background: var(--veil);
  border: 1px solid var(--veil-line);
  border-radius: var(--r-card);
  padding: clamp(22px, 3vw, 34px);
}

.stack { display: flex; flex-direction: column; gap: 18px; }
.row { display: flex; align-items: center; gap: 13px; }
.between { display: flex; align-items: center; justify-content: space-between; gap: 18px; }

/* ----------------------------------------------------------------- inputs */

.field { display: flex; flex-direction: column; gap: 8px; }
.label {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px; font-weight: 500; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-mute);
}
.hint { font-size: 15px; color: var(--ink-mute); line-height: 1.45; letter-spacing: 0; }

.input, .textarea, .select {
  width: 100%;
  background: rgba(8, 10, 40, .5);
  border: 2px solid rgba(255, 255, 255, .12);
  border-radius: var(--r-md);
  color: var(--ink);
  font: inherit;
  font-size: 17px;
  letter-spacing: 0;
  padding: 13px 16px;
  outline: none;
  transition: border-color .18s var(--ease), background .18s var(--ease);
}
.input:focus, .textarea:focus, .select:focus { border-color: #fff; background: rgba(8, 10, 40, .72); }
.input::placeholder, .textarea::placeholder { color: rgba(255, 255, 255, .4); }
.textarea { resize: vertical; min-height: 100px; }
input[type='range'].input { padding: 0; background: transparent; border: 0; accent-color: #fff; }

/* ------------------------------------------------------------ member rows */

.member {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  transition: background .15s var(--ease);
}
.member:hover { background: rgba(255, 255, 255, .08); }
.member.is-picked { background: rgba(255, 255, 255, .1); }

.avatar {
  position: relative;
  width: 44px; height: 44px; flex: 0 0 44px;
  border-radius: var(--r-pill);
  background: linear-gradient(140deg, var(--blurple-hi), var(--violet-deep));
  /* Never overflow:hidden. The dot rides the edge; clipping turns it into a
     sliver. The image is rounded by its own inherited radius. */
  display: grid; place-items: center;
  font-family: Archivo, sans-serif; font-weight: 800; color: #fff;
  font-size: 16px; letter-spacing: 0;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: inherit; grid-area: 1 / 1; }
.avatar-sm { width: 30px; height: 30px; flex-basis: 30px; font-size: 12px; }

.dot {
  position: absolute; right: -1px; bottom: -1px;
  width: 15px; height: 15px;
  border-radius: var(--r-pill);
  background: #7f86b8;
  box-shadow: 0 0 0 3.5px var(--slab);
}
.dot-online { background: var(--mint); }
.dot-idle { background: var(--amber); }
.dot-offline { background: #7f86b8; }
.frag-light .dot { box-shadow: 0 0 0 3.5px #fff; }
/* The dot has to scale with the avatar. At a fixed 15px it covered half a
   30px avatar and clipped the initial underneath it. */
.avatar-sm .dot { width: 10px; height: 10px; box-shadow: 0 0 0 2.5px var(--slab); }
.frag-light .avatar-sm .dot { box-shadow: 0 0 0 2.5px #fff; }
.avatar-lg .dot { width: 20px; height: 20px; box-shadow: 0 0 0 4px var(--slab); }

.member-name { font-weight: 700; line-height: 1.25; font-size: 17px; letter-spacing: 0; }
.member-handle { font-size: 15px; color: var(--ink-mute); letter-spacing: 0; }
.member-body { min-width: 0; flex: 1; }
.member-body > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.share {
  display: flex; align-items: center; gap: 5px;
  background: rgba(8, 10, 40, .55);
  border: 2px solid rgba(255, 255, 255, .14);
  border-radius: var(--r-sm);
  padding: 5px 10px;
}
.share input {
  width: 52px; background: transparent; border: 0; color: var(--ink);
  font: inherit; font-size: 17px; font-weight: 800; text-align: right;
  font-variant-numeric: tabular-nums; outline: none; -moz-appearance: textfield;
}
.share input::-webkit-outer-spin-button, .share input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.share span { color: var(--ink-mute); font-weight: 700; }
.frag-light .share { background: rgba(20, 22, 58, .07); border-color: rgba(20, 22, 58, .12); }
.frag-light .share input { color: var(--on-light); }
.frag-light .share span { color: #6b6f8a; }

.total {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 20px;
  border-radius: var(--r-md);
  background: rgba(8, 10, 40, .45);
  border: 2px solid rgba(255, 255, 255, .12);
  font-family: Archivo, sans-serif;
  font-weight: 800; text-transform: uppercase; font-size: 17px; letter-spacing: 0;
}
.total.is-exact { border-color: var(--mint); color: var(--mint); background: rgba(61, 220, 132, .12); }
.total.is-over { border-color: var(--danger); color: var(--danger); background: rgba(255, 107, 107, .12); }

.meter { height: 9px; border-radius: var(--r-pill); background: rgba(8, 10, 40, .5); overflow: hidden; }
.meter > i { display: block; height: 100%; background: #fff; border-radius: inherit; transition: width .45s var(--ease), background .3s var(--ease); }
.meter.is-exact > i { background: var(--mint); }
.meter.is-over > i { background: var(--danger); }

/* ------------------------------------------------------------------ misc */

.pill {
  display: inline-flex; align-items: center; gap: 7px;
  height: 32px; padding: 0 14px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .13);
  border: 1px solid rgba(255, 255, 255, .16);
  font-size: 15px; font-weight: 600; color: var(--ink); letter-spacing: 0;
}
.pill-blurple { background: rgba(168, 121, 255, .24); border-color: rgba(168, 121, 255, .46); }
.pill-green { background: rgba(61, 220, 132, .18); border-color: rgba(61, 220, 132, .42); color: #9df3c2; }
.pill-amber { background: rgba(255, 200, 74, .16); border-color: rgba(255, 200, 74, .4); color: #ffdd95; }

.note {
  border-radius: var(--r-md);
  background: rgba(8, 10, 40, .4);
  border-left: 4px solid rgba(255, 255, 255, .3);
  padding: 15px 19px;
  color: var(--ink-dim);
  font-size: 17px;
}
.note-warn { border-left-color: var(--amber); background: rgba(255, 200, 74, .09); }

.empty { text-align: center; padding: clamp(48px, 9vw, 96px) 24px; color: var(--ink-dim); }

.grid { display: grid; gap: 20px; }
@media (min-width: 760px) {
  .grid-2 { grid-template-columns: 1fr 1fr; }
  .grid-3 { grid-template-columns: repeat(3, 1fr); }
}

.stat {
  background: var(--veil);
  border: 1px solid var(--veil-line);
  border-radius: var(--r-lg);
  padding: 22px 24px;
}
.stat b {
  display: block;
  font-family: Archivo, sans-serif;
  font-size: 30px; font-weight: 800; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; line-height: 1.05;
}

.steps { display: flex; flex-direction: column; gap: 12px; }
.step { display: flex; gap: 12px; align-items: flex-start; color: var(--ink-mute); font-size: 17px; }
.step.is-now { color: var(--ink); }
.step.is-done { color: var(--ink-dim); }
.step i { width: 19px; height: 19px; flex: 0 0 19px; border-radius: var(--r-pill); border: 3px solid currentColor; margin-top: 4px; }
.step.is-done i { background: var(--mint); border-color: var(--mint); }
.step.is-now i { border-color: #fff; animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

.toast {
  position: fixed; left: 50%; bottom: 28px;
  transform: translateX(-50%) translateY(160%);
  background: var(--slab-hi);
  border: 1px solid rgba(255, 255, 255, .18);
  box-shadow: var(--shadow-float);
  border-radius: var(--r-pill);
  padding: 15px 26px; z-index: 100; font-weight: 600; font-size: 17px;
  max-width: min(560px, calc(100vw - 32px));
  transition: transform .4s var(--bounce);
}
.toast.is-up { transform: translateX(-50%) translateY(0); }
.toast.is-bad { border-color: var(--danger); }

/* ---------------------------------------------------------------- closing */

/* A full-bleed scene with a horizon, the way the reference closes: objects
   standing on a line, a centred CTA above, the footer below. */
.closer {
  position: relative;
  /* The facts section above already carries a clamp(48px, 8.3vw, 120px) of
     bottom padding, so the old 120 + 90 on top of it opened a dead gap of
     roughly 450px between the last line of text and the closing line. */
  margin-top: 0;
  padding-top: 40px;
  padding-bottom: 0;
  text-align: center;
  overflow: hidden;
}
.closer-scene {
  position: relative;
  height: 340px;
  margin-top: 20px;
}

/* The ground the closing cast stands on.

   This was a flat 210px band with a nominal 60px corner radius. At 200vw wide
   that radius is invisible, so it rendered as a hard-edged rectangle whose top
   edge sliced straight through the figures standing on it, and whose bottom
   edge left a second seam where the section ended.

   It is now a gradient with no edge at the top, ending at EXACTLY the footer's
   #2b3196 at the bottom, so the ground runs into the footer with nothing to
   see at the join. */
.horizon {
  position: absolute;
  left: 50%; bottom: 0;
  width: 100vw; height: 100%;
  transform: translateX(-50%);
  background: linear-gradient(180deg, transparent 0%, rgba(43, 49, 150, .32) 40%, #2b3196 100%);
}

/* ----------------------------------------------------------------- footer */

/* The reference closes with four link columns, not one line. */
.foot {
  position: relative;
  background: #2b3196;
  padding: 92px var(--gutter) 72px;
  margin-top: 0;
}
.foot-grid {
  width: min(var(--shell), 100%);
  margin: 0 auto;
  display: grid;
  gap: 32px;
  grid-template-columns: 1fr;
}
@media (min-width: 760px) { .foot-grid { grid-template-columns: 1.5fr 1fr 1fr; } }
.foot h3 { font-family: Figtree, sans-serif; text-transform: none; font-size: 16px; font-weight: 700; color: var(--blurple-hi); margin-bottom: 14px; letter-spacing: 0; }
.foot ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.foot a { text-decoration: none; font-size: 16px; color: var(--ink-dim); }
.foot a:hover { text-decoration: underline; color: #fff; }
.foot-base {
  width: min(var(--shell), 100%);
  margin: 40px auto 0;
  padding-top: 24px;
  border-top: 1px solid rgba(255, 255, 255, .22);
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  font-size: 15px; color: var(--ink-mute);
}

/* ------------------------------------------------------------- responsive */

/* Measured off the reference at 390: nav 64, h1 27, module radius 56, and the
   module stacks with the art on top. */
@media (max-width: 900px) {
  :root { --nav-h: 64px; --r-module: 56px; --r-art: 44px; --gutter: 24px; }
  body { font-size: 17px; }
  .navlinks { display: none; }
  .brand { font-size: 19px; }

  .hero { min-height: 0; padding-top: 104px; padding-bottom: 64px; }
  .hero-row { grid-template-columns: 1fr; gap: 36px; }
  .hero-copy { padding-top: 0; }
  .hero .lede { max-width: none; }
  .hero-cta { justify-content: flex-start; }

  .wrap-wide { padding-inline: var(--gutter); }
  .section { padding-block: 44px; }
  .module {
    grid-template-columns: 1fr;
    min-height: 0;
    gap: 28px;
    padding: 16px;
    border-radius: var(--r-module);
  }
  .module.is-flipped { grid-template-columns: 1fr; }
  .module.is-flipped .module-art { order: 1; }
  .module.is-flipped .module-copy { order: 2; padding-left: 0; }
  .module-art { height: 360px; }
  .module-copy { max-width: none; padding: 0 12px 16px; }

  /* Breakout pieces are a desktop device; at phone width they crowd the
     composition and get clipped by the viewport instead of the container. */
  .closer-scene { height: 200px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition-duration: .001ms !important; }
}

/* Atmosphere kept from the scene system: a soft out-of-focus mass behind a
   composition. Everything else it came with (mascot placement, ground pools,
   depth classes) went with the mascots. */

/* The implied floor. A soft pool rather than a drawn horizon, so objects read
   as standing in light instead of sitting on a line. */

/* .haze, a 70px-blurred disc used to sit behind the subject. Removed, and
   nothing referenced it by the time it went. */

/* Atmospheric perspective: a distant object loses contrast AGAINST THE
   BACKGROUND, which means it moves toward the background's value, not toward
   black. The first attempt darkened distant figures, and since these figures
   are already dark-bodied they went blacker than the ground behind them and
   read as holes in the picture. Dropping opacity blends them toward whatever
   they stand on, which is correct on the deep blue page and on the magenta and
   violet blocks alike. */

/* A full-bleed section. Used to break the container rhythm so the page is not
   six identical boxes down the page. */
.band {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  padding-block: clamp(80px, 10vw, 150px);
  overflow: hidden;
}

/* A quieter surface for the app pages. They belong to the same world but are
   read and filled in, so they get less light and no scene behind them. */
.panel {
  background: rgba(255, 255, 255, .055);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: var(--r-card);
  padding: clamp(22px, 3vw, 32px);
}

/* --------------------------------------------------------------- the band

   The one full-bleed section. Everything else on the page is a contained
   module, so this is where the page breathes out: the floor and the wash run
   edge to edge while the scene itself stays on the 1240 grid, which keeps the
   flow coordinates predictable. */
.band { margin-top: 120px; }

/* -------------------------------------------------------------- the facts

   The quiet beat. After a full-bleed scene, plain text with no surface behind
   it is what lets the closer land. These were four bright cards before, which
   put yet another row of panels on a page that already had too many. */
.facts {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 40px 56px;
}
.fact h3 { font-size: 20px; line-height: 1.3; }
.fact p { margin-top: 10px; color: var(--ink-dim); font-size: 15px; line-height: 1.55; }

.closer h2 {
  font-size: clamp(30px, 3.3vw, 48px);
  line-height: 1.21;
  max-width: 18ch;
  margin-inline: auto;
}
.closer-cta { margin-top: 28px; display: flex; justify-content: center; }

@media (max-width: 900px) {
  .facts { grid-template-columns: 1fr; gap: 28px; }
}

/* ---------------------------------------------------------- app page head

   The app pages had no title at all and opened straight onto a column of
   cards, which made them read as a different product from the homepage. They
   now get the same eyebrow / heading / lede, with a little of the homepage's
   world sunk far behind it. */
.pagehead {
  position: relative;
  padding-block: clamp(40px, 5vw, 76px) clamp(24px, 3vw, 40px);
  overflow: hidden;
}
.pagehead-art {
  position: absolute;
  right: 0;
  top: 0;
  width: 520px;
  height: 100%;
  pointer-events: none;
}
.pagehead h1 { font-size: clamp(32px, 3.6vw, 46px); line-height: 1.06; margin-top: 14px; max-width: 17ch; }
.pagehead .lede { margin-top: 16px; max-width: 54ch; color: var(--ink-dim); }
@media (max-width: 900px) { .pagehead-art { display: none; } }

/* The app pages' own cards were barely separable from the ground, so a page
   of them looked like text floating on a field. Raising the surface and the
   border is enough; the shapes themselves were already right. */
.content .card {
  background: rgba(255, 255, 255, .075);
  border-color: rgba(255, 255, 255, .13);
  box-shadow: 0 18px 40px rgba(6, 8, 40, .28);
}

/* The range track read as olive against the page, because a default track
   plus the accent colour lands somewhere unintended. */
input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .3);
  outline: none;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: #fff;
  border: none;
  box-shadow: 0 2px 8px rgba(6, 8, 40, .5);
  cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
  width: 20px; height: 20px;
  border-radius: 50%;
  background: #fff;
  border: none;
  cursor: pointer;
}
input[type="range"]::-moz-range-track { height: 6px; border-radius: 999px; background: rgba(255, 255, 255, .3); }

/* ------------------------------------------------------ recipient entry

   Adding somebody is: paste an id, see who it is, confirm. The id is the only
   thing the entitlement is keyed to, but it is unreadable, so it appears once
   at confirmation time, small and monospaced so it can be compared against
   what was copied, and never again afterwards. */
.addrow { display: flex; gap: 10px; align-items: stretch; }
.addrow .input { flex: 1; min-width: 0; font-family: 'IBM Plex Mono', ui-monospace, monospace; letter-spacing: .02em; }
.addrow .btn { flex: none; white-space: nowrap; }
@media (max-width: 560px) { .addrow { flex-direction: column; } }

.confirm {
  display: grid;
  gap: 12px;
  padding: 16px;
  margin-top: 4px;
  background: rgba(124, 133, 247, .12);
  border: 1px solid rgba(124, 133, 247, .34);
  border-radius: var(--r-sm);
}
.confirm .member { padding-left: 0; }
.confirm-id {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 12px;
  color: var(--ink-dim);
  letter-spacing: .02em;
}

/* The "how do I find this" helper. Closed by default: it is needed once, and
   after that it is noise above the thing people came to use. */
.helper {
  border: 1px solid var(--veil-line);
  border-radius: var(--r-sm);
  padding: 12px 16px;
  background: rgba(255, 255, 255, .035);
}
.helper > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 14px;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
}
.helper > summary::-webkit-details-marker { display: none; }
.helper > summary::before {
  content: "?";
  display: grid;
  place-items: center;
  width: 18px; height: 18px;
  flex: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, .14);
  font-size: 11px;
  font-weight: 700;
}
.helper[open] > summary { margin-bottom: 10px; }
.helper ol { margin: 0; padding-left: 20px; display: grid; gap: 6px; color: var(--ink-dim); font-size: 14px; }
.helper ol b { color: var(--ink); }
.helper code {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 12px;
  background: rgba(255, 255, 255, .1);
  padding: 1px 5px;
  border-radius: 4px;
}
.helper .hint { margin-top: 10px; display: block; }

/* ═══════════════════════════════════════════════════════════════════════
   PHONE WIDTH

   Found by measurement, not by eye: tools/qa.mjs walks every page at 390px
   and fails if the document scrolls sideways, naming the widest offender.
   Two things were pushing it over.

   1. THE NAV. "Sign in with Discord" plus "Connect" plus the wordmark is
      about 430px of content in a 390px bar. The wordmark's text goes first
      (the mark still identifies the site), then the sign-in label shortens.

   2. THE SCENES. Their objects are positioned at fixed pixel offsets inside
      a 709px hero and a 1240px band, because a composition with a ground
      line cannot be reflowed into a column without becoming a list of
      unrelated objects. So the whole scene is scaled instead, and negative
      margins reclaim the layout box the transform no longer occupies. */

@media (max-width: 640px) {
  .notice-tag { display: none; }
  .wide-only { display: none; }
  .nav { gap: 8px; padding-inline: 16px; }
  .nav .btn { padding-inline: 14px; }
  .nav .pill { padding-inline: 8px; }

  /* The scene keeps its own geometry and is scaled to fit. */
  .hero-art {
    width: 709px;
    height: 580px;
    transform: scale(var(--s));
    transform-origin: 0 0;
    margin-right: calc(709px * (var(--s) - 1));
    margin-bottom: calc(580px * (var(--s) - 1));
  }
  .band-scene {
    width: 1240px;
    height: 400px;
    transform: scale(var(--s));
    transform-origin: 0 0;
    margin-right: calc(1240px * (var(--s) - 1));
    margin-bottom: calc(400px * (var(--s) - 1));
  }
  .closer-scene {
    width: 100%;
    height: 340px;
    transform: scale(var(--s));
    transform-origin: 0 100%;
    margin-bottom: calc(340px * (var(--s) - 1));
  }
}

@media (max-width: 420px) {
  /* The wordmark's text is the last thing to go. The mark stays. */
  .brand { font-size: 0; gap: 0; }
  .brand svg { width: 26px; height: 26px; }
}

@media (max-width: 640px) {
  /* The module's glow is an absolutely positioned pseudo-element hanging 80px
     past each side of a container that is deliberately overflow:visible, so on
     a phone it pushed the document 55px wider than the viewport. A pseudo
     element is invisible to querySelectorAll, which is why this took a
     scrollWidth measurement rather than an element scan to find. */
  .module::before { inset: -70px -10px -50px; }

  /* A directory row is an avatar, a name and some figures. Given less width
     than they need, the figures pushed the name off the screen instead of
     moving under it. */
  .card.row { flex-wrap: wrap; }
  .card.row .member-body { flex: 1 1 100%; min-width: 0; }
}

@media (max-width: 640px) {
  /* A space-between pair whose left side is an avatar and a name cannot keep
     both items on one line at phone width. The left side is allowed to shrink
     and its text to ellipsize, and the pair wraps if that is still not enough.
     Without the min-width:0 the flex item refuses to go below its content
     width, which is what pushed the pill past the right edge. */
  .between { flex-wrap: wrap; gap: 10px 14px; }
  .between > .row { min-width: 0; flex: 1 1 auto; }
  .between > .row .member-body { min-width: 0; }
}

/* ═══════════════════════════════════════════════════════════════════════
   THE ACT

   One pinned, scroll-scrubbed sequence. act.js reads a single number, the
   track's progress, and writes the custom properties below; everything here
   consumes them. Nothing animates on a timer and nothing transitions, because
   the scroll position IS the time: a transition would fight it and make
   scrubbing feel laggy.

   Every property that moves is transform or opacity, so the whole act runs on
   the compositor without a layout pass.

   STICKY WARNING: position:sticky dies silently under an ancestor with
   overflow-x other than `visible` or `clip`. If the pin ever stops pinning,
   look for an ancestor that grew an `overflow: hidden`, not at this rule. */

.act {
  position: relative;
  /* The height IS the running time. Seven beats need room to be read. */
  height: 620vh;
  /* clip, never hidden: hidden would kill the sticky pin inside. */
  overflow-x: clip;
}
.act-pin {
  position: sticky;
  top: 0;
  height: 100vh;
  min-height: 600px;
  overflow: clip;
  isolation: isolate;
}

/* ------------------------------------------------------------- the world */

/* Below the script on purpose: the coin and the streams pass BEHIND the
   typography, which is what stops the type feeling like a separate layer
   pasted on top of a picture. */
.act-world { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
/* .act-glow was a 60px-blurred lilac disc behind the composition, up to
   660px across. It is removed. The element is still emitted by act.js so
   the slot numbering there is untouched, but it draws nothing. */
.act-glow { display: none; }

/* THE FRAME. Every object is placed as a percentage of this box, in a named
   slot, and the slots do not overlap.

   An earlier version positioned each object against the viewport separately
   and three of them landed on the same spot: the wallet card on top of the
   accrue card on top of the candles, with the identity rows hidden behind
   both. One frame with explicit slots makes that impossible rather than
   something to keep re-checking by eye. */
.world-frame {
  position: absolute;
  left: 38%;
  right: 2.5%;
  top: 11%;
  bottom: 7%;
}

/* SLOT: the coin. Top left of the frame, and the only object that travels
   across slots, which is how the same coin stays the subject throughout. */
.world-centre {
  position: absolute;
  left: 24%; top: 21%;
  width: 0; height: 0;
  /* --travel scales the journey to the frame. The timeline writes pixels sized
     for the desktop frame; a phone's frame is less than half as tall, and the
     unscaled travel carried the coin up behind the nav. */
  transform: translate(calc(var(--coin-x, 0px) * var(--travel, 1)), calc(var(--coin-y, 0px) * var(--travel, 1)));
}
.act-ring {
  position: absolute;
  left: 50%; top: 50%;
  width: 272px; height: 272px;
  margin: -136px 0 0 -136px;
  transform: scale(var(--ring-s, 1));
  opacity: var(--ring-o, 1);
}
.act-ring-arc { stroke-dasharray: var(--ring-dash, 70 654); }
.act-coin {
  position: absolute;
  left: 50%; top: 50%;
  margin: -100px 0 0 -100px;
  transform: scale(var(--coin-s, 1));
  opacity: var(--coin-o, 1);
}

/* SLOT: the market, bottom left. The streams start here, so it has to be the
   thing they visibly come out of. */
.act-market {
  position: absolute;
  left: 0; top: 64%;
  width: 34%;
  opacity: var(--market-o, 0);
  transform: translateY(var(--market-y, 0px));
}
.act-market .candles { width: 100%; height: auto; }
.act-market-base {
  display: block; height: 1px; margin-top: 6px;
  background: linear-gradient(90deg, rgba(255, 255, 255, .34), transparent);
}
.act-market-label {
  display: block; margin-top: 7px;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: rgba(255, 255, 255, .58);
}
/* Candles reveal left to right, so the market reads as opening. */
.act-market .candle {
  transform-origin: 50% 100%;
  opacity: clamp(0, calc((var(--market-reveal, 0) * 11) - var(--i)), 1);
  transform: scaleY(clamp(.15, calc((var(--market-reveal, 0) * 11) - var(--i)), 1));
}

/* Fee units lifting out of the trades and into the split. The one looping
   animation in the act, because value arriving is genuinely continuous
   rather than something the scroll should scrub. */
.act-units {
  position: absolute;
  left: 22%; top: 56%;
  width: 120px; height: 90px;
  opacity: var(--units-o, 0);
}
.act-unit {
  position: absolute;
  width: 6px; height: 6px;
  border-radius: 2px;
  background: #e6e9ff;
  left: calc(var(--i) * 20px);
  animation: act-unit 2.8s cubic-bezier(.4, 0, .5, 1) infinite;
  animation-delay: calc(var(--i) * -560ms);
}
@keyframes act-unit {
  0% { opacity: 0; transform: translate(0, 26px) scale(.5); }
  20% { opacity: 1; }
  78% { opacity: 1; transform: translate(74px, -34px) scale(1); }
  100% { opacity: 0; transform: translate(110px, -48px) scale(.5); }
}

/* SLOT: the split. Spans the frame, drawn in the frame's own percentage
   space, so the endpoints land exactly on the identity rows at any size.
   The stroke widths ARE the percentages, which is why the paths carry
   `vector-effect="non-scaling-stroke"`: without it the non-uniform scale
   would squash them and the drawing would stop agreeing with the numbers. */
.act-streams {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  opacity: var(--streams-o, 0);
}
.act-stream {
  stroke-dasharray: 700;
  stroke-dashoffset: var(--streams-dash, 700);
}

/* SLOT: the accounts the streams arrive at. Top right. The three rows sit at
   the vertical fractions the stream paths terminate on (14/30/46). */
.act-ids {
  position: absolute;
  left: 64%; top: 4%;
  width: 36%;
  display: grid; gap: 10px;
  list-style: none; margin: 0; padding: 0;
  transform: translateX(var(--ids-x, 0px));
  opacity: var(--ids-o, 0);
}
/* The identity row: an avatar, a handle, a state and a share. Shared by the
   accounts, the accrued card and the wallet, so it lives here rather than
   inside any one of them.

   min-width:0 on the body is load-bearing. Without it the flex item refuses
   to shrink below its text width and the row overflows its own container,
   which is exactly what happened when these rules were briefly lost. */
.id-body { display: grid; gap: 2px; min-width: 0; flex: 1; }
.id-body > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.id-handle { font-weight: 700; font-size: 14px; letter-spacing: 0; }
.id-state { font-size: 11px; color: rgba(255, 255, 255, .55); }
.id-share { font-weight: 800; font-size: 15px; flex: none; }

.act-ids .id-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px;
  border-radius: var(--r-md);
  background: rgba(10, 12, 44, .66);
  border: 1px solid rgba(255, 255, 255, .13);
}
.act-ids .id-handle { font-size: 14px; }
.act-ids .id-state { font-size: 11px; }
.act-ids .id-share { font-size: 15px; }

/* SLOT: what one account has accrued. Middle right, under the rows. */
.act-accrue {
  position: absolute;
  left: 64%; top: 50%;
  width: 36%;
  display: grid; gap: 6px;
  padding: 14px;
  border-radius: var(--r-md);
  background: rgba(10, 12, 44, .72);
  border: 1px solid rgba(255, 255, 255, .13);
  opacity: var(--accrue-o, 0);
}
.accrue-label {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
  color: rgba(255, 255, 255, .62);
}
.accrue-value { font-size: clamp(24px, 2.6vw, 34px); line-height: 1; letter-spacing: -.02em; }
.accrue-chart { width: 100%; height: 52px; display: block; }
.accrue-line { stroke-dasharray: 420; stroke-dashoffset: var(--accrue-dash, 420); }
.accrue-fill { opacity: calc(1 - (var(--accrue-dash, 420) / 420)); }

/* SLOT: the wallet, bottom right. */
.act-wallet {
  position: absolute;
  left: 64%; top: 86%;
  width: 36%;
  display: flex; align-items: center; gap: 10px;
  padding: 11px 13px;
  border-radius: var(--r-md);
  background: rgba(10, 12, 44, .82);
  border: 1px solid rgba(124, 133, 247, .55);
  opacity: var(--wallet-o, 0);
  transform: translateX(var(--wallet-x, 0px));
}
.act-wallet .wallet-got { margin-left: auto; font-size: 16px; }
.act-wallet .id-handle { font-size: 13px; }
.act-wallet .id-state { font-size: 11px; }
.wallet-mark {
  width: 30px; height: 30px; flex: none;
  border-radius: 9px;
  background: linear-gradient(135deg, rgba(255, 255, 255, .3), rgba(255, 255, 255, .07));
  border: 1px solid rgba(255, 255, 255, .22);
}

/* THE ONE OBJECT THAT CROSSES A SLOT BOUNDARY. It leaves the accrued balance
   and lands in the wallet, because the value actually moving is the last
   thing the product promises and the only thing worth animating across the
   frame. The travel is the gap between the two slots: 50% to 86%. */
.act-parcel {
  position: absolute;
  left: 66%; top: 58%;
  padding: 6px 12px;
  border-radius: 999px;
  background: linear-gradient(90deg, #e6e9ff, #8b94ff);
  color: #10123a;
  font-weight: 800; font-size: 13px;
  white-space: nowrap;
  opacity: var(--parcel-o, 0);
  transform: translateY(calc(var(--parcel-t, 0) * 28%)) scale(calc(.92 + var(--parcel-t, 0) * .08));
}

/* ------------------------------------------------------------ the script */

.act-script {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: min(var(--shell), 100%);
  margin: 0 auto;
  padding-inline: var(--gutter);
  pointer-events: none;
}
.beat {
  position: absolute;
  top: 50%;
  width: min(430px, 40vw);
  transform: translateY(calc(-50% + var(--d, 0) * 58px));
  opacity: 0;
  pointer-events: none;
  /* The only transition in the act, and it is short: the copy is read, so it
     should settle rather than track the scroll pixel for pixel. */
  transition: opacity .32s ease, transform .42s cubic-bezier(.22, 1, .36, 1);
}
.beat.is-current { opacity: 1; pointer-events: auto; }
.beat .eyebrow { display: block; }
.beat h2 {
  margin-top: 12px;
  font-size: clamp(28px, 3.5vw, 46px);
  line-height: 1.05;
  letter-spacing: -.02em;
  text-wrap: balance;
}
.beat p { margin-top: 16px; color: var(--ink-dim); max-width: 40ch; }

/* The hero beat. Wider and larger than the story beats, because it carries
   the page's title and its two calls to action. */
.beat-hero { width: min(560px, 46vw); }
.beat-hero h1 {
  margin-top: 14px;
  font-size: clamp(34px, 4.6vw, 60px);
  line-height: 1.02;
  letter-spacing: -.03em;
  text-wrap: balance;
}
.beat-hero p { margin-top: 20px; max-width: 46ch; }
.beat-cta { margin-top: 28px; display: flex; gap: 14px; flex-wrap: wrap; }

/* -------------------------------------------------------------- the rail */

.act-rail {
  position: absolute;
  left: var(--gutter); bottom: 6vh;
  z-index: 3;
  display: flex; align-items: center; gap: 10px;
}
.rail-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .22);
  transition: background .3s ease, transform .3s ease;
}
.act[style*="--act-beat:0"] .rail-dot[data-i="0"],
.act[style*="--act-beat:1"] .rail-dot[data-i="1"],
.act[style*="--act-beat:2"] .rail-dot[data-i="2"],
.act[style*="--act-beat:3"] .rail-dot[data-i="3"],
.act[style*="--act-beat:4"] .rail-dot[data-i="4"],
.act[style*="--act-beat:5"] .rail-dot[data-i="5"],
.act[style*="--act-beat:6"] .rail-dot[data-i="6"] {
  background: #aeb6ff;
  transform: scale(1.5);
}
.rail-fill { display: none; }

/* ------------------------------------------------------- narrow screens

   The act keeps its beats but stops sharing one frame with them: the world
   takes the top, the copy the bottom, because 40vw of each is unreadable on
   a phone. */

@media (max-width: 900px) {
  .act { height: 520vh; }
  .act-pin { min-height: 540px; }

  .act-pin { --travel: .5; }
  .world-frame { left: 4%; right: 4%; top: 12%; bottom: 43%; }
  .world-centre { left: 50%; top: 22%; }
  .act-coin { margin: -70px 0 0 -70px; }
  .act-coin .token { width: 140px; height: 140px; }
  .act-ring { width: 196px; height: 196px; margin: -98px 0 0 -98px; }

  .act-market { left: 0; top: 58%; width: 44%; }
  .act-units { left: 34%; top: 52%; width: 80px; }
  .act-ids { left: 52%; top: 4%; width: 48%; gap: 7px; }
  .act-ids .id-row { padding: 7px 9px; gap: 8px; }
  .act-ids .id-handle { font-size: 12px; }
  .act-ids .id-state { font-size: 11px; }
  .act-ids .id-share { font-size: 12px; }
  .act-accrue { left: 52%; top: 56%; width: 48%; padding: 11px; }
  .act-accrue .accrue-chart { height: 38px; }
  .act-wallet { left: 52%; top: 88%; width: 48%; padding: 8px 10px; }
  .act-parcel { left: 54%; top: 76%; font-size: 11px; padding: 5px 9px; }

  .beat { width: calc(100% - var(--gutter) * 2); top: auto; bottom: 7vh; transform: translateY(calc(var(--d, 0) * 40px)); }
  .beat-hero { width: calc(100% - var(--gutter) * 2); }
  .beat h2 { font-size: clamp(24px, 6.6vw, 32px); }
  .beat-hero h1 { font-size: clamp(28px, 8vw, 38px); }
  .beat p { font-size: 15px; max-width: none; }
  .beat-hero p { margin-top: 14px; }
  .beat-cta { margin-top: 16px; gap: 10px; }
  .beat-cta .btn { flex: 1 1 auto; justify-content: center; }
  .act-rail { bottom: 3.5vh; }
}

/* ------------------------------------------------ reduced motion: no act

   A pinned scrubbed sequence without motion is several screens of empty
   scrolling, so the whole mechanism is dismantled: no track height, no pin,
   no overlap. The beats become an ordinary list, and the world is parked on
   its final frame above them with every object visible at once. The narrative
   survives because it is carried by the words and the final composition, not
   by the movement. */

.act.is-still { height: auto; overflow: visible; }
.act.is-still .act-pin {
  position: static;
  height: auto;
  min-height: 0;
  overflow: visible;
  padding-block: 24px 40px;
}
.act.is-still .act-world {
  position: relative;
  inset: auto;
  height: 460px;
  margin: 0 auto 36px;
  width: min(var(--shell), 100%);
  padding-inline: var(--gutter);
}
.act.is-still .world-frame { left: var(--gutter); right: var(--gutter); top: 4%; bottom: 4%; }
.act.is-still .act-script {
  position: relative;
  inset: auto;
  pointer-events: auto;
}
.act.is-still .beat {
  position: relative;
  top: auto; bottom: auto;
  width: 100%;
  max-width: 62ch;
  transform: none;
  opacity: 1;
  pointer-events: auto;
  padding-block: 22px;
  border-top: 1px solid rgba(255, 255, 255, .1);
}
.act.is-still .beat-hero { border-top: 0; max-width: 62ch; }
.act.is-still .beat h2 { font-size: clamp(22px, 2.6vw, 30px); }
.act.is-still .beat-hero h1 { font-size: clamp(30px, 4vw, 46px); }
.act.is-still .act-rail { display: none; }
.act.is-still .act-unit { animation: none; opacity: .75; }

/* The nav's sign-in label has a long and a short form. Splitting it as
   "Sign in" plus a hidden " with Discord" tail rendered a double space, so
   each form is now its own element and exactly one of them is shown. */
.narrow-only { display: none; }
@media (max-width: 640px) {
  .wide-only { display: none; }
  .narrow-only { display: inline; }
}

/* The signed-in account and its way out. */
.account { display: inline-flex; align-items: center; gap: 8px; }
.btn-sm { height: 32px; padding-inline: 12px; font-size: 13px; }
@media (max-width: 640px) {
  /* The nav is already tight on a phone, so this shrinks rather than pushing
     Connect off the edge. */
  .account { gap: 6px; }
  .btn-sm { padding-inline: 9px; font-size: 12px; }
}

/* ─────────────────────────────────────────────────────────── the last check

   The one screen between a launcher and a coin that exists forever. It is a
   sheet rather than a page so the form behind it is not destroyed: pressing
   Back returns to exactly the state they were editing, which matters when the
   thing they went back to fix is one digit of a Discord ID.

   On a phone it fills the screen and scrolls, because the roster is the part
   that must be read and a sheet that clips it defeats the point. */
.review {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: clamp(12px, 4vh, 48px) 16px;
  background: rgba(8, 10, 34, .74);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  overflow-y: auto;
  overscroll-behavior: contain;
  animation: review-in .18s ease-out;
}
@keyframes review-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .review { animation: none; } }

.review-sheet {
  width: min(560px, 100%);
  margin: auto;
  background: var(--slab);
  border: 1px solid var(--veil-line);
  box-shadow: 0 28px 80px rgba(0, 0, 0, .5);
}
.review-sheet h2 { text-wrap: balance; }
.review-sheet .mono { word-break: break-all; }

/* The acknowledgement is a real target, not a decorative tick: it is the last
   deliberate act before money moves. */
.confirm-check {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--veil-line);
  border-radius: var(--r-md);
  background: var(--veil);
  cursor: pointer;
  font-size: 15px;
  line-height: 1.45;
}
.confirm-check input {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 1px 0 0;
  accent-color: var(--blurple-hi);
  cursor: pointer;
}
.confirm-check:has(input:checked) { border-color: var(--blurple-hi); }
.confirm-check:focus-within { outline: 2px solid var(--blurple-hi); outline-offset: 2px; }

/* A discard that asks twice says so in the button rather than in a dialog the
   viewer may never be shown. */
.btn.is-armed { border-color: var(--danger); color: var(--danger); }

@media (max-width: 520px) {
  .review { padding: 0; }
  .review-sheet {
    width: 100%;
    min-height: 100%;
    border-radius: 0;
    border-left: 0;
    border-right: 0;
    padding-top: calc(20px + env(safe-area-inset-top, 0px));
    padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  }
}

/* The brand mark. Sized explicitly so the header does not reflow between the
   first paint and the image arriving. */
.brand img { width: 26px; height: 26px; object-fit: contain; flex: none; }
