/* ============================================================================
   Aden — page-specific composition. Everything here builds on the tokens and
   the components; nothing here invents a colour.
   ========================================================================= */

/* ---------------------------------------------------- chrome: server status */
.server-status { display: flex; align-items: center; }
.server-status__link { text-decoration: none; }
.server-status__link:hover { border-color: currentColor; }
.server-status__label { color: var(--text-dim); }
@media (max-width: 560px) { .server-status__label { display: none; } }

/* ------------------------------------------------ chrome: download dialog -- */
.dl-size { margin: var(--s-4) 0 var(--s-3); font-size: var(--t-md); }
.dl-size__label { color: var(--text-dim); }

.mirror-grid {
  display: grid;
  gap: var(--s-3);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  margin-top: var(--s-3);
}
.mirror {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .06);
  color: var(--text);
  text-decoration: none;
  transition: background .16s var(--ease), border-color .16s var(--ease);
}
.mirror:hover { background: rgba(10, 15, 19, .7); border-color: var(--cream-edge); color: var(--paper); }
/* Placeholder mirror: present so the layout reads as "two sources", but
   visibly inert until a real URL exists. Not an <a>, so it cannot be clicked. */
.mirror--soon { opacity: .45; cursor: default; border-style: dashed; }
.mirror--soon:hover { background: rgba(255, 255, 255, .06); border-color: var(--line); color: var(--text); }
.mirror__plate {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--cream-edge);
  border-radius: var(--r-sm);
  color: var(--gold-text);
  background: rgba(255, 240, 181, .08);
}
.mirror__plate svg { width: 20px; height: 20px; }
.mirror__text { display: grid; gap: .1rem; min-width: 0; }
.mirror__kicker {
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--text-faint);
}
.mirror__name { font-weight: 600; font-size: var(--t-md); color: var(--paper); }
.mirror__size { font-size: var(--t-sm); color: var(--text-dim); }

/* ============================================================ home: hero == */
/* Six plates shipped with the build and two of them — bg-page.jpg and
   bg-files.jpg — are photographs of real mountains, not Lineage 2 art. They
   read as stock the moment they sit next to the painted ones. Nothing new was
   sourced: the three painted plates are daisy-chained across the five panels
   instead, which is also why no panel repeats the one beside it. */
.panel--hero    { background-image: url("../img/bg-hero.jpg"); }
.panel--about   { background-image: url("../img/bg-about.jpg"); }
.panel--stats   { background-image: url("../img/bg-hero.jpg"); }
.panel--vote    { background-image: url("../img/bg-support.jpg"); }
.panel--rules   { background-image: url("../img/bg-about.jpg"); }

.panel--hero .panel__body { position: relative; align-items: center; text-align: center; }

/* 02-hero.jpg puts the wordmark across a full-length figure's body with her face
   above it. The plate is full-length, so she is sized to the panel rather than
   scaled past it — nothing is cropped, and 92 vh stays under her native 1379 px
   on every ordinary display, so she is never upscaled either. */
.hero__figure {
  position: absolute;
  bottom: 0; left: 50%;
  transform: translateX(-50%);
  margin: 0;
  height: 92%;
  opacity: 1;
  pointer-events: none;
  /* She stands in mist: the foot of the plate dissolves instead of meeting the
     panel edge on a line. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 76%, rgba(0, 0, 0, .55) 92%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 0%, #000 76%, rgba(0, 0, 0, .55) 92%, transparent 100%);
}
.hero__figure img { width: auto; height: 100%; max-width: none; }
/* Narrow: she drops below the wordmark rather than standing behind it — there
   is no room for the mockup's "face above the mark" composition at 390 px. */
@media (max-width: 899px) { .hero__figure { height: 62%; } }

/* The mark, the one thing to do, the state of the server, two ways in — four
   blocks with nothing between them but a 1 rem gap read as one stack rather
   than as four decisions. The gap is on viewport HEIGHT, not width: it is
   vertical air, and it is a short screen, not a narrow one, that cannot spare
   it. */
.hero__content {
  position: relative;
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(var(--s-5), 3.6vh, var(--s-8));
  max-width: 44rem;
  padding: clamp(var(--s-5), 4vw, var(--s-7)) clamp(var(--s-4), 4vw, var(--s-7));
}
/* The download control is the hero's single purpose; it gets the widest berth
   on either side of it. */
.hero__content .btn--download { margin-block: clamp(0px, 1.2vh, var(--s-4)); }
/* A 768 px laptop is 8 px short of holding the opened-up hero, and the hero is
   the one panel that is allowed to scroll — so it must not need to. The mark
   gives first: it is the largest thing on the panel and the least harmed by
   losing a few per cent. */
@media (min-width: 900px) and (max-height: 820px) {
  .hero__content { padding-block: var(--s-4); }
  .hero__mark img { width: min(420px, 74vw); }
}
/* The text bed: a veil sized to the content, not to the panel. Same rule as
   .page-head — `closest-side` ends the gradient on the box's own edges, and the
   box is drawn well outside the copy so the falloff happens in empty space. */
.hero__content::before {
  content: "";
  position: absolute; inset: -22% -20%;
  background: radial-gradient(ellipse closest-side at 50% 50%,
    rgba(12, 17, 22, .80) 0%,
    rgba(12, 17, 22, .74) 38%,
    rgba(12, 17, 22, .52) 62%,
    rgba(12, 17, 22, .22) 82%,
    rgba(12, 17, 22, 0) 100%);
  pointer-events: none;
}
.hero__content > * { position: relative; }

.hero__mark { margin: 0; }
/* 900 px is the source width; never scaled past it. Wider than the Aden mark
   ran because the name is longer, and the plate it sits on has the room. */
.hero__mark img { width: min(520px, 82vw); height: auto; }

/* One world, so one plate — and it takes the width the pair of cards used to
   span rather than shrinking to its own contents, which would have left the
   hero looking like something had been removed from it.

   It carries the two halves of the server instead of a player count. The
   header pill already prints the count and updates from the same data; what it
   cannot say from up there is WHICH half is up, and "login is up, the world is
   in maintenance" is the state a player needs before they try to log in. */
.hero__servers { display: flex; flex-wrap: wrap; gap: var(--s-4); justify-content: center; width: 100%; }
.hero__server {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: var(--s-3) var(--s-6);
  width: min(34rem, 100%);
  padding: var(--s-3) var(--s-5);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: rgba(10, 15, 19, .5);
}
.hero__server-name {
  font-family: var(--font-display);
  font-size: var(--t-sm);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--paper);
}
.hero__server-states {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: var(--s-2) var(--s-4);
}
.hero__server-line { display: inline-flex; align-items: center; gap: var(--s-2); }
.hero__server-label {
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--text-faint);
}

.hero__ctas { display: flex; flex-wrap: wrap; gap: var(--s-3); justify-content: center; }

/* ================================================= the panels that fit ==== */
/* Home aside, every spine panel now holds one viewport and does not scroll the
   page. That is the shape of this pass: the site pages sideways, so a panel
   that also pages downwards asks the reader to work out which axis they are on,
   and above 900 px the answer was "both".

   The rule is the same everywhere. The panel body is a flex column that hides
   its own overflow, the head is fixed at the top of it, and exactly ONE region
   inside is allowed to scroll — the table on Statistics, the column on Vote,
   the document body on Rules. Whatever will not fit scrolls inside a bordered
   box that looks like it is meant to, rather than growing the document. */
.panel--about .panel__body,
.panel--stats .panel__body,
.panel--vote .panel__body,
.panel--rules .panel__body { max-width: 1280px; margin: 0 auto; }

@media (min-width: 900px) {
  .panel--fit { overflow: hidden; }
  .panel--fit .panel__body {
    justify-content: flex-start;
    height: 100%;
    /* A flex item's default min-height is its content. Without this the body
       refuses to shrink and hands the overflow straight back to the panel. */
    min-height: 0;
    overflow: hidden;
    /* the dot strip is fixed over the panel, so the last row clears it */
    padding-top: calc(var(--header-h) + var(--s-3));
    padding-bottom: calc(var(--s-6) + var(--s-4));
  }
  /* Two kinds of region take the room that is left, and they are not the
     same thing. `--fit` IS the scroller: Vote's column, which simply runs long
     on a short screen. `--fill` HOLDS one: the plate stays exactly the height
     of the panel and the box inside it does the scrolling, which is what makes
     the Statistics table the same size on all eight tabs. Give a `--fill`
     plate `overflow-y: auto` by mistake and it grows to its contents again. */
  /* `0 1 auto`, not `1 1 auto`: the plate is the height of what is on it and
     shrinks only when the panel cannot hold that. Stretched to fill, Vote's
     column ended in 300 px of empty plate — a box that looks like it lost
     something. */
  .column--fit {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .column--fill {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  /* A `--fill` plate is a flex column, so everything in it is a flex ITEM and
     shrinks by default. That is how the Statistics tab strip ended up 9 px
     tall on the two widest tables — squeezed by the box below it — and why the
     "fixed" box measured 495 to 525 depending on which tab was up. Nothing
     gives except the one region that is meant to. */
  .column--fill > * { flex: 0 0 auto; }
  .column--fill > .stats-box,
  .column--fill > .doc-box__scroll { flex: 1 1 auto; min-height: 0; }

  /* The plate's own padding is 48 px a side at this width — nearly a fifth of
     the room a panel that has to fit can spare. */
  .panel--fit .column { padding: clamp(var(--s-4), 1.6vw, var(--s-5)); }
}

/* The head of a panel that has to fit is the first thing to give: its veil
   needs generous padding to feather properly, but on an 800 px panel that
   padding is a fifth of the reading area. It keeps the falloff and loses the
   air. */
.page-head--tight { margin-bottom: var(--s-4); }
@media (min-width: 900px) {
  .panel--fit .page-head--tight { padding-block: clamp(var(--s-3), 2.4vh, var(--s-5)); }
  .panel--fit .page-head--tight .lockup { margin-bottom: 0; }
  .panel--fit .page-head--tight .lockup__title { font-size: clamp(1.6rem, 3.4vh, 2.6rem); }
  .panel--fit .page-head--tight .lockup__rule { margin-top: var(--s-3); }
  .panel--fit .page-head--tight .lockup__lede { margin-top: var(--s-3); font-size: var(--t-base); }
}

/* =========================================================== home: about == */
/* The server's rate card and rule sheet. The rules come from
   data/server-info.json, so the panel grows and shrinks with that file and the
   CSS assumes a count of nothing; the rates come from `config.rates`.

   It is the mockup's composition again — cards, character, cards — with three
   a side instead of two. She was never meant to be off this panel.

   The cards are small on purpose. Six sets of house rules will not fit a
   viewport in any layout, so each card shows its title and one line and hands
   the reading to a dialog. The panel is for knowing what there is; the dialog
   is for reading it. */

.about__rates {
  position: relative;
  z-index: 2;
  margin-bottom: clamp(var(--s-4), 2.4vh, var(--s-6));
  text-align: center;
}
/* The label sits between the head's veil and the plates' own, which is exactly
   where the panel art is at its palest — as a plain caption it measured about
   1.3:1 and read as a smudge. It carries its own chip, so it is gold on a dark
   plate wherever the mist happens to be bright. */
.about__rates-label {
  display: inline-block;
  margin-bottom: var(--s-3);
  padding: .3rem .9rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: var(--veil-strong);
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--gold-text);
}
/* Eight rates and .stat-row's grid do not get on: a count that is not a
   multiple of the track count leaves a hole in the last row, and because the
   row's own background is what draws the hairlines between plates, that hole
   shows as a gap in the strip rather than as nothing. Flex instead — the last
   row's plates stretch to close it, for any number of rates. */
.about__rates .stat-row { display: flex; flex-wrap: wrap; gap: 1px; }
.about__rates .stat-plate { flex: 1 1 96px; padding: clamp(var(--s-2), 1.4vh, var(--s-4)) var(--s-3); }
.about__rates .stat-plate__value { font-size: clamp(1.15rem, 2.6vh, var(--t-xl)); }

/* cards | character | cards */
.about__grid {
  position: relative;
  z-index: 1;
  display: grid;
  gap: var(--s-4);
  grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  /* The grid is where the panel gives. It takes the room the head, the rates
     and the controls leave, and on a screen too short even for the compact
     cards it scrolls — inside itself, with the controls still on screen under
     it. A panel that hides its own contents is worse than one that scrolls. */
  .about__grid {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* The implicit row defaults to `auto`, which means max-content — the grid
       would take the height of the tallest column's full list first and only
       then stretch, so the columns never got a definite height to divide and
       the cards sized to their lists. `minmax(0, 1fr)` pins the row to the
       grid, which is what makes the cards' own `1fr` rows resolvable. */
    grid-template-rows: minmax(0, 1fr);
  }
  .about__col { min-height: 0; }
}
/* `minmax(0, 1fr)`, not `minmax(min-content, 1fr)`: the card's height is the
   region's to hand out, and the list inside clips to whatever it gets. With a
   `min-content` floor the rows would size to the longest list — eight rules —
   and the panel would be three viewports tall. `.info-card`'s own min-height
   is the floor instead, and it is a floor for the HEAD LINE, not the list. */
.about__col {
  display: grid;
  gap: var(--s-3);
  grid-auto-rows: minmax(0, 1fr);
}
.about__centre { position: relative; display: none; }

@media (min-width: 1000px) {
  .panel--about .panel__body { position: relative; }
  .about__grid { grid-template-columns: minmax(0, 1fr) minmax(150px, .52fr) minmax(0, 1fr); }
  .about__centre { display: block; position: relative; }
  /* Anchored to the FOOT of the panel, so she stands in it rather than
     floating in the middle of it, and the plate dissolves behind the four
     controls instead of ending on a line. Behind the cards — z-index 0 against
     their 1 — because at 1000 px the middle track is narrower than she is. */
  /* The full height of the middle track and centred across it — the opening
     between the two columns, whatever size that turns out to be. Behind the
     cards (z-index 0 against their 1), because a plate this tall is wider than
     the track at most widths. */
  .about__figure {
    position: absolute;
    inset: 0 auto 0 50%;
    transform: translateX(-50%);
    height: 100%;
    width: auto;
    max-width: none;
    z-index: 0;
    pointer-events: none;
    filter: drop-shadow(0 20px 40px rgba(10, 15, 19, .5));
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 78%, transparent 100%);
            mask-image: linear-gradient(180deg, #000 0%, #000 78%, transparent 100%);
  }
  .about__col { position: relative; z-index: 1; }
}
@media (max-width: 999px) { .about__figure { display: none; } }
@media (min-width: 760px) and (max-width: 999px) {
  .about__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* One card: a glyph, a title and the way in on one line, then one line of what
   is inside it. Six of these have to sit in a viewport alongside a rate card
   and four controls, so every row it does not need is a row it does not get. */
.info-card {
  gap: 0;
  width: 100%;
  /* the head line sits at the top and the list runs down from it; there is no
     centring to do, because the list fills whatever is under the head */
  justify-content: flex-start;
  min-height: 4.75rem;
  padding: clamp(var(--s-2), 1.3vh, var(--s-4)) var(--s-4);
  overflow: hidden;
  /* it is a <button>: none of this comes for free the way it does on an <a> */
  appearance: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.info-card__head { display: flex; align-items: center; gap: var(--s-3); }
.info-card .tile__icon { width: 18px; height: 18px; margin: 0; flex: 0 0 auto; }
.info-card .tile__title { font-size: var(--t-micro); flex: 1 1 auto; min-width: 0; }
/* The card shows as many rules as its height allows and clips the rest. The
   clip is a fade over the last 40 px, not a hard edge: a half-line that fades
   reads as "there is more", where a half-line cut across the middle reads as a
   rendering fault. Content that ends above the fade is untouched, so a card
   with two rules in it is not dimmed for nothing. */
.info-card__body {
  flex: 1 1 auto;
  min-height: 0;
  margin-top: var(--s-3);
  overflow: hidden;
}
/* The fade belongs to the panel that has to fit. Below 900 px the page scrolls
   and the cards are the height of their own lists, so nothing is clipped —
   and a fade there would dim the last two lines of every card for nothing. */
@media (min-width: 900px) {
  .info-card__body {
    -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - 40px), transparent 100%);
            mask-image: linear-gradient(180deg, #000 calc(100% - 40px), transparent 100%);
  }
}
.info-card .info-list { gap: var(--s-2); }
.info-card .info-list li {
  font-size: var(--t-sm);
  line-height: 1.42;
  padding-left: var(--s-4);
}
.info-card .info-list li::before { top: .48em; width: 5px; height: 5px; }
/* one column in a card, however wide the dialog's version runs */
.info-card .fact-list { grid-template-columns: 1fr; gap: 0; }
.info-card .fact-row { padding: .22rem 0; font-size: var(--t-sm); }
.info-card .fact-row__value { font-size: var(--t-micro); }
.info-card__more {
  display: inline-flex; align-items: center; gap: .4rem;
  flex: 0 0 auto;
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--gold-text);
}
.info-card__more svg { width: 6px; height: 11px; transition: transform .2s var(--ease); }
.info-card:hover .info-card__more { color: var(--cream); }
.info-card:hover .info-card__more svg { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .info-card__more svg { transition: none; }
}

/* the lists, in the dialog */
.info-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-3);
}
.info-list li {
  position: relative;
  padding-left: var(--s-5);
  font-size: var(--t-base);
  line-height: 1.55;
  color: var(--text-dim);
}
/* the diamond that breaks every section rule, at list scale — the marker is
   drawn rather than a bullet glyph, for the same reason the chevron is */
.info-list li::before {
  content: "";
  position: absolute;
  left: 2px; top: .55em;
  width: 6px; height: 6px;
  border: 1px solid var(--cream-edge);
  transform: rotate(45deg);
}
.info-list strong { color: var(--paper); font-weight: 600; }
.info-list code {
  font-family: var(--font-mono);
  font-size: .85em;
  padding: .08em .34em;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: rgba(10, 15, 19, .55);
  color: var(--gold-text);
}

/* label left, value right — the raid-boss respawns and the Olympiad's facts */
.fact-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0 var(--s-6);
  grid-template-columns: 1fr;
}
@media (min-width: 700px) { .fact-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.fact-list--single { grid-template-columns: 1fr; }
.fact-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s-4);
  padding: .42rem 0;
  border-bottom: 1px solid var(--line-soft);
}
.fact-row__name { color: var(--text); }
.fact-row__value { color: var(--gold-text); font-size: var(--t-sm); white-space: nowrap; }

/* four controls where four figures were: the panel ends with somewhere to go */
.about__actions {
  position: relative;
  z-index: 2;
  display: grid;
  gap: var(--s-4);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: clamp(var(--s-4), 2.4vh, var(--s-6));
}
@media (min-width: 900px) {
  .about__actions { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-3); }
  .btn--action { min-height: clamp(4.2rem, 9vh, 6.4rem); padding: var(--s-4) var(--s-3); gap: var(--s-2); }
  .btn--action .btn__action-glyph { width: clamp(18px, 2.6vh, 26px); height: clamp(18px, 2.6vh, 26px); }
}

/* ======================================================= home: statistics = */
/* The panel is the rankings and nothing else — no chart, no figures above it.
   One box, the same size on every tab, scrolling in both axes inside itself so
   sixty rows of Top PvP and nine castles are the same component. */
.stats-box {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.stats-box > * { flex: 1 1 auto; min-height: 0; }
/* Above the breakpoint the box takes the room that is left. Below it there is
   no fixed viewport to fit, so it takes a readable slice and the page scrolls
   the way a phone expects. */
@media (max-width: 899px) { .stats-box { min-height: 24rem; } }

.stats-box .table-wrap {
  height: 100%;
  overflow: auto;
  overscroll-behavior: contain;
}
.stats-box .olympiad-grid { overflow: auto; align-content: start; }

/* ============================================================ home: rules == */
/* One document at a time, chosen from the strip. Those are `.tab`s — the same
   control the Statistics panel uses, because it is the same gesture. */
.rules-body { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
.doc-tabs { flex: 0 0 auto; }
/* The plate is the panel's — the full width of the body, in line with the tab
   strip above it, and the height of what the head and the strip leave. Sized to
   its own document instead, it read as a small box adrift under a full-width
   strip on anything wider than a laptop.

   The MEASURE moved inside it: the prose keeps its 68 ch and is centred in the
   plate, horizontally and vertically. So the box is large and the text is in
   the middle of it, which is the arrangement, rather than a column of type
   stranded in the top-left corner of a large box. */
.doc-box {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
}
/* `.section-head` is a flex row (title left, controls right), so `text-align`
   alone does nothing to the heading's own box — the title block is centred the
   way every other section title on this site is, and only the prose under it
   stays flush left, because that is what prose is read at. */
.doc-box .section-head { justify-content: center; text-align: center; }
.doc-box .legal-meta { margin-bottom: var(--s-5); text-align: center; }
/* Centred in the plate on both axes. `margin: auto` in a flex column is what
   does the vertical half — `justify-content: center` centres it too, but then
   a document taller than the plate is cut off at the TOP and cannot be
   scrolled back to. Auto margins collapse when there is no room, so the same
   rule that centres a short document scrolls a long one. */
.doc-box__scroll { display: flex; flex-direction: column; }
.doc-box__inner { margin: auto; width: 100%; max-width: 68ch; }
.doc-box .legal-body.prose { max-width: none; margin-inline: 0; }
/* The scroller is inside the plate, so the plate holds still and the prose
   moves. Focusable, because a region reachable only by wheel is not reachable. */
.doc-box__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: var(--s-3);
}
.doc-box__scroll:focus-visible { outline: 1px solid var(--cream-edge); outline-offset: 4px; }
@media (max-width: 899px) { .doc-box__scroll { max-height: 60vh; } }

/* ============================================================= home: vote = */
/* Nothing is dropped; everything is tightened, so five toplists, a balance and
   four exchange rows sit in one viewport instead of one and a half. */
@media (min-width: 900px) {
  .panel--vote .balance { padding: var(--s-3) var(--s-5); }
  .panel--vote .balance__value { font-size: var(--t-xl); }
  /* Two columns rather than five stacked rows, for both lists: these are short
     items and a full-width row was mostly empty. It is also what buys the room
     to keep the type at a readable size. */
  .panel--vote .vote-list {
    gap: var(--s-3);
    margin-top: var(--s-4);
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .panel--vote .vote-row { padding: var(--s-3) var(--s-4); }
  .panel--vote .exchange-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3); }
  .panel--vote .exchange-row { padding: var(--s-3) var(--s-4); }
  .panel--vote .section-head--rule { margin-top: var(--s-4); padding-top: var(--s-4); }
}

/* A 768 px laptop cannot hold the generous set. It gets the tight one rather
   than a scrollbar. */
@media (min-width: 900px) and (max-height: 860px) {
  .panel--vote .balance { padding: var(--s-2) var(--s-4); }
  .panel--vote .balance__value { font-size: var(--t-lg); }
  .panel--vote .vote-list { gap: var(--s-2); margin-top: var(--s-4); }
  .panel--vote .vote-row { padding: .5rem var(--s-4); }
  .panel--vote .vote-row__name { font-size: var(--t-sm); }
  .panel--vote .vote-row__reward { margin-top: 1px; font-size: var(--t-micro); }
  .panel--vote .exchange-list { gap: var(--s-2); }
  .panel--vote .exchange-row { padding: var(--s-2) var(--s-3); }
  .panel--vote .section-head--rule { margin-top: var(--s-3); padding-top: var(--s-3); }
  .panel--vote .section-head { margin-bottom: var(--s-2); }
}

/* A heading with a rule over it, in place of an <hr> plus a heading: two
   separators for one break was one too many on a panel this tight. */
.section-head--rule {
  border-top: 1px solid var(--line-soft);
  padding-top: var(--s-4);
  margin-top: var(--s-4);
}

/* ======================================================== vertical pages == */
.page--forum    { --page-plate: url("../img/bg-auth.jpg"); }
.page--shop     { --page-plate: url("../img/bg-about.jpg"); }
.page--support  { --page-plate: url("../img/bg-support.jpg"); }
.page--files    { --page-plate: url("../img/bg-support.jpg"); }
.page--auth     { --page-plate: url("../img/bg-auth.jpg"); }
.page--search   { --page-plate: url("../img/bg-about.jpg"); }
.page--notfound { --page-plate: url("../img/bg-auth.jpg"); }

/* The character plate from 07-download.jpg / 08-registration.jpg: bottom-right,
   behind the content column, gone before it can crowd anything. */
.page-figure { display: none; }
@media (min-width: 1280px) {
  .page { position: relative; overflow: hidden; }
  .page-figure {
    display: block;
    position: absolute;
    right: max(0px, calc((100vw - var(--page-max)) / 2 - 5rem));
    bottom: 0;
    width: auto;
    height: min(78vh, 720px);
    max-width: none;
    z-index: 0;
    opacity: .92;
    pointer-events: none;
    filter: drop-shadow(0 24px 50px rgba(10, 15, 19, .5));
    /* The plate is a whole figure, so only the foot needs dissolving — he stands
       in the mist rather than on the footer's edge. */
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 88%, transparent 100%);
            mask-image: linear-gradient(180deg, #000 0%, #000 88%, transparent 100%);
  }
  .page__body { position: relative; z-index: 1; }
}

/* ============================================================== the forum = */
/* A board, not a stack of cards: a column header strip, then rows on fixed
   tracks so THREADS / POSTS / LAST POST line up down the page. Below 900 px the
   header strip goes and each row folds into "title, then a meta line" — nothing
   is dropped.

   The previous rules never reached the wide layout: `.board-row > .last-post`
   (two classes) outranked the `.board-row > *` reset inside the media query
   (one class), so every row stayed in the narrow two-column form at every
   width. The reset now carries the same weight as the rule it overrides. */

.board {
  /* one track set, shared by the header strip and every row below it */
  --board-cols: minmax(0, 1fr) 92px 92px 13rem;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: rgba(12, 18, 23, .34);
  overflow: hidden;
}

/* Decorative — every row labels its own numbers, so this is aria-hidden in the
   markup and exists only to align the eye. */
.board__head {
  display: none;
  grid-template-columns: var(--board-cols);
  gap: var(--s-4);
  padding: var(--s-3) var(--s-4);
  background: rgba(10, 15, 19, .62);
  font-family: var(--font-display);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--text-faint);
}
.board__head-num { text-align: center; }

.board-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-3) var(--s-4);
  align-items: center;
  padding: var(--s-4);
  border-top: 1px solid var(--line-soft);
  text-decoration: none;
  color: var(--text);
  transition: background .18s var(--ease);
}
.board-row:hover { background: rgba(10, 15, 19, .52); color: var(--paper); }

.board-row__main { display: flex; align-items: flex-start; gap: var(--s-4); min-width: 0; }
.board-row__text { min-width: 0; }

.board-row__title {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-md);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--paper);
  overflow-wrap: anywhere;
}
.board-row:hover .board-row__title { color: var(--cream); }
.board-row__desc { margin: var(--s-2) 0 0; font-size: var(--t-base); color: var(--text-dim); }

/* labelled figures, not a comma-separated string */
.figure-pair { display: grid; gap: .1rem; text-align: center; }
.figure-pair__value {
  font-family: var(--font-display);
  font-size: var(--t-lg);
  font-weight: 600;
  line-height: 1.1;
  color: var(--gold-text);
  font-variant-numeric: lining-nums tabular-nums;
}
.figure-pair__label {
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--text-faint);
}

/* the last-post column — the part people scan */
.last-post { display: grid; gap: .2rem; min-width: 0; }
.last-post__label {
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--text-faint);
}
.byline { display: flex; align-items: center; gap: var(--s-2); min-width: 0; }
.byline__text { display: grid; min-width: 0; }
.byline__name { font-weight: 600; color: var(--paper); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.byline__when { font-size: var(--t-sm); color: var(--text-dim); white-space: nowrap; }

/* thread list ------------------------------------------------------------- */
.threads { display: grid; }
.threads__group-label {
  padding: var(--s-3) var(--s-4);
  background: rgba(10, 15, 19, .5);
  border-top: 1px solid var(--line-soft);
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--text-faint);
}

/* pinned threads grouped at the top and tinted, so a wall of stickies never
   hides the live discussion under it */
.thread-row--pinned { background: rgba(255, 240, 181, .06); }
.thread-row--pinned:hover { background: rgba(255, 240, 181, .10); }

.thread-row__title {
  display: block;
  font-family: var(--font-body);
  font-size: var(--t-md);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--paper);
  text-decoration: none;
  overflow-wrap: anywhere;
}
.thread-row__title:hover { color: var(--cream); text-decoration: underline; }
.thread-row__badges { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-bottom: var(--s-2); }
.thread-row__by { margin: var(--s-2) 0 0; font-size: var(--t-sm); color: var(--text-dim); }

@media (min-width: 900px) {
  .board__head { display: grid; }
  .board-row { grid-template-columns: var(--board-cols); gap: var(--s-5); }
  /* the wide layout wins: same weight as the narrow full-bleed rules above */
  .board-row > .last-post { padding-left: var(--s-4); border-left: 1px solid var(--line-soft); }
  .board-row > .last-post > .last-post__label { display: none; }
}

/* Narrow: counts sit on one meta line under the title and the last-post block
   is pushed to its end. Flex, not grid — in a grid the pairs stretch to their
   track and read as "28 .......... REPLIES". */
@media (max-width: 899px) {
  .board-row { display: flex; flex-wrap: wrap; }
  .board-row__main { flex: 1 1 100%; }
  .figure-pair { display: flex; align-items: baseline; gap: .4rem; }
  .figure-pair__value { font-size: var(--t-md); }
  .last-post { margin-left: auto; }
}
@media (max-width: 560px) {
  .board-row > .crest { display: none; }
  .last-post { margin-left: 0; flex: 1 1 100%; }
}

/* thread view ------------------------------------------------------------- */
.thread-head { margin-bottom: var(--s-5); }
.thread-head__badges { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-bottom: var(--s-3); }

.posts { display: grid; gap: var(--s-4); }

.post {
  display: grid;
  grid-template-columns: 1fr;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .04);
  overflow: hidden;
  scroll-margin-top: calc(var(--header-h) + var(--s-5));
}
.post:target { border-color: var(--cream); box-shadow: 0 0 0 1px rgba(255, 240, 181, .35); }

.post__author {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-4);
  background: rgba(10, 15, 19, .45);
  border-bottom: 1px solid var(--line-soft);
}
.post__author-meta { display: grid; gap: .25rem; min-width: 0; }
.post__author-name { font-family: var(--font-display); font-weight: 600; letter-spacing: .08em; color: var(--paper); }
.post__author-badges { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.post__author-facts { display: grid; gap: .1rem; font-size: var(--t-sm); color: var(--text-dim); }

.post__main { display: flex; flex-direction: column; min-width: 0; }
.post__head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--t-sm);
  color: var(--text-dim);
}
.post__permalink {
  font-family: var(--font-display);
  font-variant-numeric: lining-nums;
  letter-spacing: .08em;
  color: var(--gold-text);
  text-decoration: none;
}
.post__permalink:hover { color: var(--cream); text-decoration: underline; }
.post__body { padding: var(--s-4); flex: 1 1 auto; }
.post__body p { max-width: 74ch; }
.post__foot {
  display: flex; flex-wrap: wrap; gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--line-soft);
}

@media (min-width: 820px) {
  .post { grid-template-columns: 208px minmax(0, 1fr); }
  .post__author {
    flex-direction: column; align-items: flex-start; text-align: left;
    border-bottom: 0; border-right: 1px solid var(--line-soft);
  }
}

.reply-box { margin-top: var(--s-6); }
.gate {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5);
  border: 1px solid var(--cream-edge);
  border-radius: var(--r-sm);
  background: rgba(255, 240, 181, .07);
}
.gate__text { margin: 0; color: var(--text); }
.gate__actions { display: flex; gap: var(--s-3); flex-wrap: wrap; }

/* auto-fit leaves an empty fourth cell on a phone; the three forum figures
   stack instead. */
@media (max-width: 620px) { .stat-row--3 { grid-template-columns: minmax(0, 1fr); } }

.forum-search { display: flex; gap: var(--s-3); flex-wrap: wrap; align-items: stretch; margin-bottom: var(--s-5); }
.forum-search .input { flex: 1 1 16rem; }
.forum-search .btn { flex: 0 0 auto; }
@media (max-width: 560px) { .forum-search .btn { flex: 1 1 100%; } }

/* ============================================================== the stats = */
.countdown { font-variant-numeric: lining-nums tabular-nums; color: var(--gold-text); }
.countdown--ready { color: var(--ok); }

.olympiad-grid { display: grid; gap: var(--s-5); grid-template-columns: 1fr; }
@media (min-width: 860px) { .olympiad-grid { grid-template-columns: minmax(0, 1fr) 18rem; } }

/* =============================================================== the vote = */
.balance {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  border: 1px solid var(--cream-edge);
  border-radius: var(--r-sm);
  background: rgba(255, 240, 181, .07);
  margin-bottom: var(--s-5);
}
.balance__label {
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--text-dim);
}
.balance__value {
  font-family: var(--font-display);
  font-size: var(--t-xl);
  font-weight: 600;
  color: var(--cream);
  font-variant-numeric: lining-nums tabular-nums;
}

.vote-list { display: grid; gap: var(--s-3); }
.vote-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-3);
  align-items: center;
  padding: var(--s-4);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .04);
}
@media (min-width: 760px) { .vote-row { grid-template-columns: minmax(0, 1fr) auto auto; gap: var(--s-5); } }
.vote-row__name { font-family: var(--font-display); font-weight: 600; letter-spacing: .1em; color: var(--paper); }
.vote-row__reward { margin: var(--s-1) 0 0; font-size: var(--t-sm); color: var(--text-dim); }

.exchange-list { display: grid; gap: var(--s-2); }
.exchange-row {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .04);
}
.exchange-row__cost { font-family: var(--font-display); color: var(--gold-text); font-variant-numeric: lining-nums; }

/* =============================================================== the shop = */
.pack-grid { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.pack { position: relative; text-align: center; align-items: center; gap: var(--s-2); }
.pack__coins {
  font-family: var(--font-display);
  font-size: var(--t-2xl);
  font-weight: 600;
  color: var(--cream);
  font-variant-numeric: lining-nums;
  line-height: 1.05;
}
.pack--active .pack__coins { color: var(--ink); }
.pack__bonus { font-size: var(--t-sm); color: var(--ok); }
.pack--active .pack__bonus { color: #2f6b3f; }
.price { font-size: var(--t-lg); color: var(--paper); font-variant-numeric: lining-nums; }
.pack--active .price { color: var(--ink); }
.pack__flag {
  position: absolute; top: -.7rem; left: 50%; transform: translateX(-50%);
  white-space: nowrap;
  background: rgba(16, 22, 27, .96);   /* it straddles the cream tile edge */
}

.item-grid { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.item { gap: var(--s-3); }
.item__name { font-family: var(--font-body); font-size: var(--t-md); font-weight: 600; color: var(--paper); }
.item__note { font-size: var(--t-sm); color: var(--text-dim); }
.item__foot { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); padding-top: var(--s-3); }

/* ============================================================== the forms = */
/* 08-registration.jpg sets the language: a left column of wide, low-contrast
   translucent fields over character art. Real labels, positioned as drawn. */
/* The form keeps to a column on the left, the way the mockup lays it out; the
   character plate is the page figure behind it. */
.auth-layout { display: block; }
.auth-column { max-width: 36rem; }
.auth-column .lockup { text-align: left; }
/* the display face plus .18em tracking breaks "REGISTRATION" in a form column,
   so the auth heading steps down rather than the column growing */
.auth-column .lockup__title { font-size: var(--t-2xl); }
.auth-column { max-width: 38rem; }
.auth-column .lockup__rule { justify-content: flex-start; }
.auth-column .lockup__rule::after { display: none; }
.auth-column .lockup__lede { margin-left: 0; }

/* ------------------------------------------------------------- auth deck -- */
/* Log in and Sign up are two faces of one plate. The face that is up carries
   the title; the other is a smaller live control on the right of the same line,
   and the two forms sit side by side on a track that slides between them. */

/* The plate is the measuring stick, not the window. In Lithuanian the pair is
   PRISIJUNGIMAS / REGISTRUOTIS — a third longer than LOG IN / SIGN UP — and it
   used to run off the right edge of the column, in the dialog and on the page
   alike. What gives way first is secondary furniture — the gaps, the rule and
   the label of the face that is DOWN — so that at the widths this deck is
   actually drawn at (the 38rem column, and the dialog matched to it) the face
   that is up keeps the exact size and tracking it was drawn with.

   Below that the pair scales with the plate, and if even that is not enough it
   wraps. Both are worse than the drawing; neither loses a word off the edge. */
.auth-deck { container-type: inline-size; }

.auth-switch {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}

.auth-switch__tab {
  appearance: none;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  color: var(--text-faint);
  /* the size, the tracking and the colour all move, which is what makes the
     inactive label grow into the title as the forms slide underneath */
  font-size: clamp(.8125rem, 2.95cqi, var(--t-base));
  letter-spacing: .12em;
  text-shadow: var(--text-shadow);
  transition: font-size .34s var(--ease), letter-spacing .34s var(--ease),
              color .34s var(--ease), opacity .2s var(--ease);
}
.auth-switch__tab:hover { color: var(--cream-edge); }

.auth-switch__tab[aria-selected="true"] {
  font-size: clamp(1.25rem, 6.6cqi, var(--t-2xl));
  letter-spacing: var(--track-wide);
  color: var(--paper);
  cursor: default;
}

/* the lockup rule, here doing double duty as the gap between the two faces */
.auth-switch__rule {
  flex: 1 1 auto;
  min-width: var(--s-3);
  height: 1px;
  background: linear-gradient(90deg, var(--line-strong), transparent);
}

/* The stage clips the two faces and is exactly as tall as the one that is up;
   its height is set in JS from the panel and animates with the slide.

   Both panels sit in the SAME grid cell and each slides by a percentage of its
   own width — which is the stage's width — rather than a shared track sliding
   by a percentage of itself. A track's percentage transform is resolved when
   the transition starts, so the page scrollbar appearing as the stage grows
   (the register face is ~190 px taller) left the track translated by the old,
   wider measurement and clipped the fields by 12 px. Per-panel transforms
   cannot drift: the panel and the stage are the same box. */
.auth-stage {
  display: grid;
  overflow: hidden;
  transition: height .42s var(--ease);
}

/* Both panels are laid out at all times so there is something to slide to; the
   one that is down is inert, so Tab never walks off the edge of the stage. */
.auth-panel {
  grid-area: 1 / 1;
  align-self: start;
  min-width: 0;
  transition: transform .42s var(--ease), opacity .3s var(--ease);
}
.auth-panel[aria-hidden="true"] { opacity: 0; pointer-events: none; }

.auth-deck[data-mode="login"]    .auth-panel[data-face="register"] { transform: translateX(100%); }
.auth-deck[data-mode="register"] .auth-panel[data-face="login"]    { transform: translateX(-100%); }

.auth-pitch {
  margin: 0 0 var(--s-5);
  max-width: 40ch;
  font-size: var(--t-md);
  color: var(--text-dim);
  text-shadow: var(--text-shadow);
}

@media (prefers-reduced-motion: reduce) {
  .auth-stage, .auth-panel, .auth-switch__tab { transition: none; }
}

/* four-segment strength bar */
.strength { display: flex; gap: 4px; margin-top: var(--s-2); }
.strength__seg { flex: 1 1 auto; height: 3px; background: rgba(255, 255, 255, .16); border-radius: 2px; }
.strength__seg[data-on="1"] { background: var(--danger); }
.strength__seg[data-on="2"] { background: var(--gold-text); }
.strength__seg[data-on="3"] { background: var(--ok); }
.strength__label { margin-top: var(--s-2); font-size: var(--t-sm); color: var(--text-dim); }

/* six-digit code entry */
.code-row { display: flex; gap: var(--s-2); margin-bottom: var(--s-4); }
.code-cell {
  width: 3rem; height: 3.6rem;
  text-align: center;
  font-family: var(--font-display);
  font-size: var(--t-xl);
  font-variant-numeric: lining-nums;
  padding: 0;
}
@media (max-width: 400px) { .code-cell { width: 100%; height: 3rem; font-size: var(--t-lg); } }

.auth-alt { margin-top: var(--s-5); font-size: var(--t-base); color: var(--text-dim); }
.auth-alt a { margin-left: .35rem; }

/* ============================================================ the support = */
.support-grid-2 { display: grid; gap: var(--s-5); grid-template-columns: 1fr; }
@media (min-width: 900px) { .support-grid-2 { grid-template-columns: minmax(0, 1fr) 20rem; } }
.contact-list { display: grid; gap: var(--s-4); }
.contact-item { display: flex; gap: var(--s-3); align-items: flex-start; }
.contact-item svg { width: 18px; height: 18px; color: var(--gold-text); flex: 0 0 auto; margin-top: .2rem; }
.contact-item__label {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--text-faint);
}

/* ============================================================= the search = */
.search-form { display: flex; gap: var(--s-3); flex-wrap: wrap; margin-bottom: var(--s-6); }
.search-form .input { flex: 1 1 16rem; }
.search-group { margin-bottom: var(--s-6); }
.search-hit { display: block; padding: var(--s-3) 0; border-bottom: 1px solid var(--line-soft); text-decoration: none; }
.search-hit:last-child { border-bottom: 0; }
.search-hit__title { font-size: var(--t-md); font-weight: 600; color: var(--paper); }
.search-hit:hover .search-hit__title { color: var(--cream); text-decoration: underline; }
.search-hit__meta { margin-top: .15rem; font-size: var(--t-sm); color: var(--text-dim); }

/* ============================================================= downloads == */
.req-grid { display: grid; gap: var(--s-5); grid-template-columns: 1fr; }
@media (min-width: 820px) { .req-grid { grid-template-columns: 1fr 1fr; } }
.steps { counter-reset: step; list-style: none; padding: 0; margin: 0; display: grid; gap: var(--s-3); }
.steps li {
  counter-increment: step;
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s-3);
  align-items: flex-start;
  margin: 0;
}
.steps li::before {
  content: counter(step);
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border: 1px solid var(--cream-edge);
  border-radius: var(--r-sm);
  font-family: var(--font-display);
  font-variant-numeric: lining-nums;
  font-size: var(--t-sm);
  color: var(--gold-text);
}

/* ================================================================ the 404 = */
.notfound { text-align: center; }
.notfound__code {
  font-family: var(--font-display);
  font-variant-numeric: lining-nums;
  font-size: clamp(4rem, 14vw, 8rem);
  line-height: 1;
  color: var(--cream);
  letter-spacing: var(--track-wide);
  text-shadow: var(--text-shadow);
}
.notfound__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); justify-content: center; margin-top: var(--s-6); }

/* ============================================================ legal prose = */
.legal-body h2 {
  font-family: var(--font-display);
  font-size: var(--t-md);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--paper);
  margin: var(--s-6) 0 var(--s-3);
}
.legal-body ul, .legal-body ol { color: var(--text-dim); }
.legal-body p { color: var(--text-dim); }
.legal-body.prose { margin-inline: auto; }
.legal-body strong { color: var(--paper); }
.legal-meta { font-size: var(--t-sm); color: var(--text-faint); margin-bottom: var(--s-5); }
