/* ============================================================================
   The Gold Jacket — styles for the sales page (/gold-jacket), the in-app modal,
   and the wall.

   A separate file rather than another few hundred lines in styles.css, which is
   already 280KB and where a `.gj-` block would be unfindable. Both surfaces
   load this one file, so the jacket cannot look like two different products.

   Everything here inherits the site's neo-brutalist frame — 2px ink borders,
   hard offset shadows, Playfair display over Space Mono utility type — and then
   departs in exactly one way: the jacket surfaces run DARK where the rest of
   the site runs cream. That is the whole visual argument. A tier sold on being
   a different class of object cannot be sold on a page that looks like the
   page below it, and inverting the ground does more work than any amount of
   gold trim on cream would.

   Theme: the dark ground is the design, in both themes, on purpose — a gold
   jacket on a cream card is a beige card. So the jacket blocks paint their own
   ink background explicitly and do not flip; only the surrounding page does.
   Every colour is still declared, never inherited, so neither theme leaks in.
   ========================================================================== */

/* ── One guard for the whole file: anchors on a jacket dark ground ────────
   styles.css:1490 sets `.legal a { color: var(--ink); text-decoration:
   underline }` at specificity (0,1,1). Every jacket surface lives inside
   `article.legal`, so a bare `.gj-*`/`.jp-*` class at (0,1,0) LOSES to it —
   and `--ink` is a foreground token that is near-black in light mode and cream
   in dark, i.e. the exact colour of the dark card in one theme and unreadable
   in the other.

   That has now produced five separate bugs: the hero's ghost button, the
   jacket page's ghost button, the Hall promo card, the Hall's row links, and
   the "See the Hall" label. Patching them one at a time clearly does not hold,
   so this is the rule that closes the class. Two classes deep beats (0,1,1) by
   class count, so anything matched here is safe by construction; individual
   blocks still set their own colour where it is not simply "the cream on this
   card".

   Nothing inside these blocks wants a text underline — .gj-mine-link draws its
   own with border-bottom precisely so it can be animated. */
.legal .gj-hero a,
.legal .jp-card a,
.legal .jp-actions a,
.legal .gjh-top a,
.legal .gj-mine a,
.legal .gj-wall a,
.legal a.gjh-promo {
  text-decoration: none;
}
/* The promo card and the Hall rows carry no colour of their own — their inner
   spans do — so without this they inherit `.legal a`'s ink and any text added
   to them later lands invisible on the dark ground. */
.legal a.gjh-promo,
.legal .gj-taken .gj-wall-link {
  color: var(--gj-on-ink);
}

:root {
  /* Named here rather than reusing --gold alone, because the jacket needs a
     two-stop gold to read as metal rather than as a flat yellow fill. */
  --gj-gold: #e6b843;
  --gj-gold-hi: #f7dd94;
  --gj-gold-lo: #b58a24;
  --gj-ink: #14110a;
  --gj-ink-2: #241e12;
  --gj-ink-3: #33291a;
  --gj-on-ink: #f6eedb;
  --gj-on-ink-dim: rgba(246, 238, 219, 0.66);
}

/* ── The hero ─────────────────────────────────────────────────────────────
   Built to fit ONE screen. The old hero stacked art, title, price, a long
   paragraph, the counter, the CTA and the fine print in a single column, which
   on a laptop put the perk list and everything after it below the fold — so the
   page opened on a wall of prose and the offer needed scrolling to reach.

   Two columns now: the jacket on the left, the entire offer on the right. The
   art is capped with `max-height: 44vh` rather than a fixed pixel height, so it
   shrinks with a short laptop window instead of pushing the offer off screen.
   Single column below 760px, where there is no horizontal room to trade and
   vertical scrolling is expected anyway. */
.gj-hero {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.6fr);
  align-items: center;
  gap: clamp(18px, 4vw, 40px);
  background: linear-gradient(165deg, var(--gj-ink-3) 0%, var(--gj-ink) 62%);
  border: 3px solid var(--gj-gold);
  box-shadow: 6px 6px 0 var(--ink, #151413);
  padding: clamp(20px, 3.4vw, 34px);
  margin: 0 0 26px;
  position: relative;
  overflow: hidden;
}
@media (max-width: 760px) {
  .gj-hero { grid-template-columns: 1fr; text-align: center; gap: 14px; }
}
/* A single slow sheen. One moving thing, once — the page is selling a metal
   object and metal catches light. More than this reads as a template. */
.gj-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(110deg, transparent 40%, rgba(255, 255, 255, 0.06) 50%, transparent 60%);
  transform: translateX(-120%);
  animation: gjSheen 7s ease-in-out 1.2s infinite;
  pointer-events: none;
}
@keyframes gjSheen { 0% { transform: translateX(-120%); } 55%, 100% { transform: translateX(120%); } }
@media (prefers-reduced-motion: reduce) { .gj-hero::after { animation: none; } }

.gj-hero-art { display: grid; place-items: center; }
.gj-hero-art .gj-art {
  width: 100%;
  height: auto;
  /* Sized to the height of the offer beside it. At 190px the art stopped a
     third of the way up its own column and the card carried a block of dead
     gradient under it; the cap still yields to `max-height` on a short window,
     so a laptop shrinks the jacket rather than pushing the price off screen. */
  max-width: min(250px, 40vw);
  max-height: 44vh;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.5));
}
@media (max-width: 760px) {
  .gj-hero-art .gj-art { max-width: 108px; max-height: none; }
}
.gj-hero-body { min-width: 0; }

.gj-hero-kicker {
  font: 700 0.6rem/1.4 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--gj-gold);
  margin: 0 0 6px;
}
.gj-hero-title {
  font: 900 clamp(1.7rem, 5vw, 2.7rem)/1.02 "Playfair Display", Georgia, serif;
  color: var(--gj-on-ink);
  margin: 0;
  text-wrap: balance;
}
.gj-hero-title em {
  font-style: normal;
  background: linear-gradient(135deg, var(--gj-gold-hi), var(--gj-gold) 45%, var(--gj-gold-lo));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* Without this an engine that ignores background-clip:text renders the
     headline transparent on dark, i.e. invisible. */
  -webkit-text-fill-color: transparent;
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .gj-hero-title em { color: var(--gj-gold); -webkit-text-fill-color: var(--gj-gold); }
}

/* The price. These rules were lost when the hero was rebuilt, and the failure
   is worth recording: with them gone the figure inherited a dark body colour
   and rendered nearly invisible on the dark card — the single most important
   number on the page, unreadable, with nothing erroring to notice it by. */
.gj-price {
  font: 900 clamp(1.9rem, 6vw, 2.7rem)/1 "Space Mono", monospace;
  color: var(--gj-gold);
  margin: 10px 0 0;
  font-variant-numeric: tabular-nums;
}
.gj-price small {
  display: block;
  font: 700 0.6rem/1.5 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--gj-on-ink-dim);
  margin-top: 7px;
}
/* The struck-through full price, shown only to a Pass holder taking the credit.
   Hidden by default so a non-holder never sees a discount they cannot have. */
.gj-price-was {
  font: 700 0.9rem/1 "Space Mono", monospace;
  color: var(--gj-on-ink-dim);
  text-decoration: line-through;
  margin-right: 9px;
  font-variant-numeric: tabular-nums;
}

.gj-hero-line {
  font: 400 clamp(0.86rem, 2.2vw, 0.97rem)/1.5 "Playfair Display", Georgia, serif;
  color: var(--gj-on-ink);
  max-width: 52ch;
  margin: 10px 0 0;
  text-wrap: pretty;
}
@media (max-width: 760px) { .gj-hero-line { margin-inline: auto; } }

/* .gj-cta carries `margin: 20px 0 0` for use on its own under a paragraph.
   In a row of buttons that margin applies to the filled button and not to the
   outlined one beside it, which pushed the two 7px out of line and made the
   row look accidental. Zeroed for every actions row, not just the hero's —
   scoping it to one row is what let the jacket page inherit the bug. */
.gj-hero-actions > .gj-cta, .gj-hero-actions > .gj-cta-ghost,
.jp-actions > .gj-cta, .jp-actions > .gj-cta-ghost,
.jp-actions > button { margin-top: 0; }
.gj-hero-actions {
  display: flex;
  flex-wrap: wrap;
  /* stretch for the same reason as .jp-actions: the two buttons are 53px and
     42px tall, so centring them left a visible 6px step at the one width where
     they share a line (~760px — wider than that and the narrow text column
     wraps them, which hid it). Stretch costs nothing when they are stacked,
     since each then fills its own line. */
  align-items: stretch;
  gap: 10px;
  margin: 16px 0 0;
}
.gj-hero-actions > .gj-cta-ghost { display: flex; align-items: center; justify-content: center; }
@media (max-width: 760px) { .gj-hero-actions { justify-content: center; } }

/* The secondary route. Outlined, not filled, and smaller — the Hall is worth
   offering next to the buy button but must not compete with it. */
/* Scoped to the actions row on purpose. Both jacket pages sit inside
   `article.legal`, and styles.css:1490 sets `.legal a { color: var(--ink);
   text-decoration: underline }` at specificity (0,1,1) — which beats a lone
   class and silently painted this button's gold label near-black on a near-
   black card, i.e. an invisible button. Anything gold-on-dark inside .legal
   needs to clear that rule, not just declare a colour. */
.gj-hero-actions .gj-cta-ghost,
.gj-cta-ghost {
  display: inline-block;
  padding: 13px 18px;
  border: 2px solid var(--gj-gold);
  background: transparent;
  color: var(--gj-gold);
  font: 700 0.72rem/1 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-decoration: none;
  transition: background 0.12s ease, color 0.12s ease;
}
.gj-hero-actions .gj-cta-ghost { color: var(--gj-gold); text-decoration: none; }
.gj-cta-ghost:hover { background: rgba(230, 184, 67, 0.16); }
.gj-cta-ghost:focus-visible { outline: 3px solid var(--gj-on-ink); outline-offset: 3px; }

/* ── Remaining counter ────────────────────────────────────────────────────
   Renders only when the live read succeeds — see GoldJacket.remaining(). There
   is deliberately no fallback copy: an invented "only 12 left!" is the one lie
   on this page that would be worth catching, and a missing line costs nothing. */
.gj-left {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 18px 0 0;
  padding: 7px 14px;
  border: 2px solid var(--gj-gold);
  border-radius: 999px;
  background: rgba(230, 184, 67, 0.12);
  font: 800 0.66rem/1 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gj-gold);
}
.gj-left-num { font-size: 1.15em; font-variant-numeric: tabular-nums; }
.gj-left-bar {
  display: block;
  width: min(300px, 76vw);
  height: 9px;
  margin: 12px auto 0;
  border: 2px solid var(--gj-gold);
  border-radius: 999px;
  background: rgba(20, 17, 10, 0.7);
  overflow: hidden;
}
.gj-left-fill {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--gj-gold-lo), var(--gj-gold-hi));
  transition: width 0.7s ease;
}
.gj-soldout {
  margin: 18px 0 0;
  padding: 9px 16px;
  border: 2px solid var(--gj-gold);
  background: var(--gj-gold);
  color: var(--gj-ink);
  font: 900 0.86rem/1 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  display: inline-block;
}

/* ── The CTA ──────────────────────────────────────────────────────────────── */
.gj-cta {
  display: inline-block;
  margin: 20px 0 0;
  padding: 15px 30px;
  border: 3px solid var(--gj-ink);
  background: linear-gradient(135deg, var(--gj-gold-hi), var(--gj-gold) 52%, var(--gj-gold-lo));
  color: var(--gj-ink);
  box-shadow: 5px 5px 0 var(--gj-ink);
  font: 900 clamp(0.92rem, 3vw, 1.08rem)/1 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
/* Correct only by coincidence until now: .legal a forced #151413, which merely
   happened to match this button's own near-black. Stated explicitly so it does
   not depend on two unrelated values staying equal. */
.gj-hero-actions .gj-cta, .legal .gj-cta { color: var(--gj-ink); text-decoration: none; }
.gj-cta:hover { transform: translate(-2px, -2px); box-shadow: 7px 7px 0 var(--gj-ink); }
.gj-cta:active { transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--gj-ink); }
.gj-cta:focus-visible { outline: 3px solid var(--gj-on-ink); outline-offset: 3px; }
.gj-cta[disabled] { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: 3px 3px 0 var(--gj-ink); }
.gj-fine {
  font: 400 0.68rem/1.55 "Space Mono", monospace;
  color: var(--gj-on-ink-dim);
  margin: 12px auto 0;
  max-width: 44ch;
}
/* The competitor-price comparison. Deliberately a step up from .gj-fine — a
   gold rule and the gold accent color, because this is the actual argument
   for the price, not fine print to skim past. */
.gj-vs {
  font: 700 0.82rem/1.5 "Playfair Display", Georgia, serif;
  color: var(--gj-gold);
  border-top: 1.5px solid rgba(230, 184, 67, 0.35);
  margin: 16px auto 0;
  padding-top: 14px;
  max-width: 40ch;
}
.gj-vs b { color: var(--gj-gold-hi); }

/* ── Perk groups ──────────────────────────────────────────────────────────
   No icon column. Eleven emoji stacked down a list read as decoration, render
   differently on every platform, and none of them is the thing being sold — so
   each perk is a bold name over its description, marked by a gold rule that
   comes from CSS rather than from a glyph in the markup. */
.gj-group {
  border: 2px solid var(--ink, #151413);
  background: var(--surface, #fff7df);
  box-shadow: 4px 4px 0 var(--ink, #151413);
  padding: clamp(16px, 3.5vw, 26px);
  margin: 0 0 20px;
}
/* The Pass summary group is the "and everything below it" row, so it is dialled
   down against the two that carry the actual argument. */
.gj-group-pass { background: transparent; box-shadow: none; border-style: dashed; }
.gj-group-title {
  font: 900 clamp(1.15rem, 4vw, 1.5rem)/1.15 "Playfair Display", Georgia, serif;
  color: var(--ink, #151413);
  margin: 0 0 4px;
}
.gj-group-note {
  font: 700 0.66rem/1.5 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted, rgba(21, 20, 19, 0.66));
  margin: 0 0 16px;
}
.gj-perks { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
@media (min-width: 760px) { .gj-perks { grid-template-columns: 1fr 1fr; gap: 18px 26px; } }
.gj-perks li {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  /* The rule replaces the icon: it marks each entry without competing with the
     name, and it is the one place gold appears on the cream card. */
  border-left: 3px solid var(--gj-gold);
  padding-left: 13px;
}
.gj-perks strong {
  font: 800 0.98rem/1.25 "Playfair Display", Georgia, serif;
  color: var(--ink, #151413);
}
.gj-perks span {
  font: 400 0.85rem/1.55 "Playfair Display", Georgia, serif;
  color: var(--muted, rgba(21, 20, 19, 0.66));
  text-wrap: pretty;
}

/* ── The Hall promo, on the about page ────────────────────────────────────
   The Hall used to render inline here as one row per number, on a page whose job is
   to explain the tier. This is the door to it instead. */
.gjh-promo {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2.5vw, 20px);
  border: 3px solid var(--gj-gold);
  background: linear-gradient(135deg, var(--gj-ink-3), var(--gj-ink));
  box-shadow: 5px 5px 0 var(--ink, #151413);
  padding: clamp(14px, 2.6vw, 20px);
  margin: 0 0 18px;
  text-decoration: none;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.gjh-promo:hover { transform: translate(-2px, -2px); box-shadow: 7px 7px 0 var(--ink, #151413); }
.gjh-promo:focus-visible { outline: 3px solid var(--gj-gold); outline-offset: 3px; }
.gjh-promo-art { flex: none; display: grid; place-items: center; }
.gjh-promo-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.gjh-promo-txt strong {
  font: 900 clamp(1rem, 3vw, 1.25rem)/1.2 "Playfair Display", Georgia, serif;
  color: var(--gj-gold);
}
.gjh-promo-txt span {
  font: 400 0.84rem/1.5 "Playfair Display", Georgia, serif;
  color: var(--gj-on-ink-dim);
  text-wrap: pretty;
}
.gjh-promo-go { flex: none; color: var(--gj-gold); font-size: 1.5rem; line-height: 1; }
@media (max-width: 560px) {
  .gjh-promo-go { display: none; }
}
/* The fine print under it sits on the cream page, not on a dark card. */
/* The same fine print on the cream page instead of the dark card. Centred
   rather than flush left: at 0.68rem mono, a left-aligned block under a
   full-width table reads as a leftover code comment, which is how it looked
   before — the content is a reassurance, so it sits under the page like one. */
.gj-fine-dark {
  color: var(--muted, rgba(21, 20, 19, 0.66));
  max-width: 72ch;
  margin: 0 auto 26px;
  text-align: center;
  /* At 58ch this wrapped to three centred lines of very uneven width, which
     is worse than flush left, not better. Wider plus `balance` gives two
     even lines — centring only helps fine print if the lines are balanced. */
  text-wrap: balance;
}

/* ── The three-column comparison ──────────────────────────────────────────
   The single most persuasive object on the page, because it is the only place a
   Pass holder can see what their $14.99 actually buys on top of what they own.
   Its own scroll container so the page body never scrolls sideways. */
.gj-table-wrap { overflow-x: auto; margin: 0 0 26px; border: 2px solid var(--ink, #151413); box-shadow: 4px 4px 0 var(--ink, #151413); }
.gj-table { width: 100%; border-collapse: collapse; min-width: 560px; background: var(--surface, #fff7df); }
.gj-table th, .gj-table td {
  padding: 11px 13px;
  text-align: left;
  /* A mid tone rather than `rgba(21,20,19,.16)`: a near-black 16% rule is
     invisible on the dark `--surface` panel, so in dark mode the table lost
     its row separators and read as one block of text. This reads on both. */
  border-bottom: 1.5px solid rgba(140, 130, 110, 0.32);
  font: 400 0.82rem/1.4 "Playfair Display", Georgia, serif;
  color: var(--ink, #151413);
}
.gj-table thead th {
  font: 800 0.66rem/1.3 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  /* --gj-ink, NOT --ink. This is a fixed-dark ground carrying fixed-cream
     text, and --ink is a FOREGROUND token that flips to cream in dark mode —
     so this header rendered cream-on-cream at a 1.02 contrast ratio and the
     column titles were invisible for every dark-theme visitor. The rule this
     file opens with ("jacket blocks paint their own ink background and do not
     flip") exists precisely to prevent this, and this line broke it. */
  background: var(--gj-ink);
  color: var(--gj-on-ink);
  border-bottom: 2px solid var(--gj-gold-lo);
  position: sticky; top: 0;
}
/* The jacket column is the one being sold — it gets the gold header and a tint
   down the whole column so the eye tracks it without reading every row. */
.gj-table thead th.gj-col {
  background: linear-gradient(135deg, var(--gj-gold-hi), var(--gj-gold) 55%, var(--gj-gold-lo));
  color: var(--gj-ink);
}
.gj-table tbody td.gj-col { background: rgba(230, 184, 67, 0.15); font-weight: 700; }
.gj-table tbody tr:last-child td { border-bottom: none; }
.gj-table td:first-child { font-weight: 700; }
.gj-dash { color: var(--muted, rgba(21, 20, 19, 0.66)); }

/* ── The wall ─────────────────────────────────────────────────────────────
   Numbered rows, tabular digits, and an explicit slot for every unclaimed
   number — an empty wall that just stops at #3 looks broken, whereas one that
   renders every open slot is the scarcity argument as a list. The count comes
   from the catalog's CAP, so no number is written down here either. */
.gj-wall { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
/* minmax(0, 1fr), not 1fr. A bare `1fr` is `minmax(auto, 1fr)`, so the track
   refuses to go narrower than its widest item's content — which is the same
   trap as the rows below, one level up. */
/* 700, not 620. At 620 the two columns were ~290px each and still carrying the
   date, which left ~120px for the handle — narrower than the single-column
   layout it had just replaced, so going two-up actually cut MORE names. */
@media (min-width: 700px) { .gj-wall { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 7px 14px; } }
.gj-wall li {
  display: flex; align-items: center; gap: 11px;
  padding: 8px 12px;
  border: 2px solid var(--ink, #151413);
  background: var(--surface, #fff7df);
  /* THE fix for the phone overflow, and the level that actually mattered. A
     grid item defaults to `min-width: auto` and will not shrink below its own
     content, so one holder with a 40-character handle made this row 474px wide
     in a 360px viewport — and because every row shares the column, it stretched
     EVERY row and gave the page a horizontal scrollbar. Setting it here is what
     lets the ellipsis on .gj-wall-who finally fire. */
  min-width: 0;
}
.gj-wall-num {
  flex: none;
  /* 40px fits "#126" at this size with room to spare; 46 was guesswork from
     when the cap was 100, and every pixel here comes off the handle. */
  min-width: 40px;
  font: 800 0.76rem/1 "Space Mono", monospace;
  font-variant-numeric: tabular-nums;
  color: var(--ink, #151413);
}
.gj-wall-who {
  font: 700 0.9rem/1.25 "Playfair Display", Georgia, serif;
  color: var(--ink, #151413);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* `min-width: 0` is what actually makes the ellipsis work. A flex item's
     default `min-width: auto` refuses to shrink below its content, so a handle
     at the 40-character maximum the rules allow pushed the row wider than the
     phone and gave the WHOLE PAGE a horizontal scrollbar — measured 472px of
     content in a 360px viewport. The overflow/ellipsis pair above cannot fire
     until the item is allowed to shrink. */
  flex: 1 1 auto;
  min-width: 0;
}
.gj-wall-at {
  margin-left: auto; flex: none; white-space: nowrap;
  font: 400 0.62rem/1 "Space Mono", monospace;
  color: var(--muted, rgba(21, 20, 19, 0.66));
  font-variant-numeric: tabular-nums;
}
.gj-wall li.gj-taken {
  background: linear-gradient(135deg, var(--gj-ink-3), var(--gj-ink));
  border-color: var(--gj-gold);
}
.gj-taken .gj-wall-num { color: var(--gj-gold); }
.gj-taken .gj-wall-who { color: var(--gj-on-ink); }
.gj-taken .gj-wall-at { color: var(--gj-on-ink-dim); }
.gj-wall li.gj-open { border-style: dashed; opacity: 0.72; background: transparent; }
.gj-open .gj-wall-who {
  font: 700 0.66rem/1 "Space Mono", monospace;
  text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--muted, rgba(21, 20, 19, 0.66));
}
/* On a phone the date is the first thing to go. It was costing ~73px of the
   row, which pushed the ellipsis in far enough to cut a 16-character handle —
   and on a wall of names, the name is the entire point while the date is
   trivia. Nothing is lost: the claim date is still on the holder's own page,
   and still here on any screen wide enough to hold both. */
@media (max-width: 480px) {
  .gj-wall-at { display: none; }
}

.gj-wall-msg {
  font: 400 0.84rem/1.5 "Playfair Display", Georgia, serif;
  color: var(--muted, rgba(21, 20, 19, 0.66));
  margin: 0;
}

/* ── Name banners ─────────────────────────────────────────────────────────
   The pill a jacket holder's name sits in, on the wall and on every
   leaderboard. The gradient and the text colour both come from the catalog as
   an inline style, so this rule carries only shape — which is what lets the
   same class render correctly on a cream board row and on a near-black wall
   row without knowing which it is on.

   `display:inline-block` with a tight padding rather than a block background:
   the banner has to hug the name, not stripe the whole row, or it competes
   with the row highlight the boards already use for "this is you". */
.gj-banner {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1.5px solid rgba(0, 0, 0, 0.35);
  font-weight: 800;
  /* The name inherits the page's font; only colour and shape change here. */
  line-height: 1.35;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}
/* On the dark wall rows the black hairline disappears; a light one reads. */
.gj-taken .gj-banner { border-color: rgba(255, 255, 255, 0.28); }

/* ── The banner picker (holders only, in the Hall) ───────────────────────── */
.gj-pick { margin: 14px 0 0; }
.gj-pick-label {
  font: 700 0.62rem/1 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--gj-on-ink-dim);
  margin: 0 0 9px;
}
.gj-pick-row { display: flex; flex-wrap: wrap; gap: 8px; }
.gj-swatch {
  width: 46px;
  height: 30px;
  border-radius: 6px;
  border: 2px solid rgba(246, 238, 219, 0.3);
  cursor: pointer;
  padding: 0;
  position: relative;
  transition: transform 0.12s ease, border-color 0.12s ease;
}
.gj-swatch:hover { transform: translateY(-2px); border-color: var(--gj-gold); }
.gj-swatch:focus-visible { outline: 3px solid var(--gj-gold); outline-offset: 2px; }
.gj-swatch[aria-pressed="true"] {
  border-color: var(--gj-gold);
  box-shadow: 0 0 0 2px rgba(230, 184, 67, 0.55);
}
/* A tick on the active swatch, so the choice survives being looked at on a
   phone where the 2px border alone is easy to miss. */
.gj-swatch[aria-pressed="true"]::after {
  content: "✓";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font: 900 0.9rem/1 "Space Mono", monospace;
  color: var(--gj-ink);
  text-shadow: 0 0 3px rgba(255, 255, 255, 0.85);
}
.gj-pick-note {
  font: 400 0.64rem/1.5 "Space Mono", monospace;
  color: var(--gj-on-ink-dim);
  margin: 9px 0 0;
  min-height: 1.5em; /* reserved, so saving text can't shift the layout */
}

/* ── Your own jacket, once you hold one ───────────────────────────────────── */
.gj-mine {
  display: flex;
  align-items: center;
  gap: 16px;
  border: 3px solid var(--gj-gold);
  background: linear-gradient(135deg, var(--gj-ink-3), var(--gj-ink));
  box-shadow: 5px 5px 0 var(--ink, #151413);
  padding: 16px 20px;
  margin: 0 0 22px;
  flex-wrap: wrap;
}
/* Centred against a two-line strip, but top-aligned once the banner picker is
   inside it: against that taller block a centred jacket floats halfway down the
   card and stops reading as the label for the number beside it. */
.gj-mine:not(.gj-mine-slim) { align-items: flex-start; }
.gj-mine-art { flex: none; display: grid; place-items: center; }
.gj-mine-num {
  font: 900 1.6rem/1 "Space Mono", monospace;
  color: var(--gj-gold);
  font-variant-numeric: tabular-nums;
}
.gj-mine-txt {
  font: 700 0.66rem/1.5 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gj-on-ink-dim);
}
/* On the about page the strip is a signpost, not a workspace — the picker lives
   in the Hall beside the holder's actual row, so this variant just links there
   rather than carrying a second copy of the swatches. */
.gj-mine .gj-mine-link,
.gj-mine-link {
  margin-left: auto;
  flex: none;
  color: var(--gj-gold);
  font: 700 0.68rem/1 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-decoration: none;
  border-bottom: 2px solid rgba(230, 184, 67, 0.5);
  padding-bottom: 2px;
}
.gj-mine .gj-mine-link { color: var(--gj-gold); text-decoration: none; }
.gj-mine-link:hover { border-bottom-color: var(--gj-gold); }
@media (max-width: 560px) { .gj-mine-link { margin-left: 0; } }

/* ── The Hall page ────────────────────────────────────────────────────────
   Its own page now, so it gets its own compact header: the count is the story
   here, so it is the largest thing, and the pitch is one button rather than a
   second sales section. */
.gjh-top {
  display: flex;
  align-items: center;
  gap: clamp(14px, 3vw, 28px);
  border: 3px solid var(--gj-gold);
  background: linear-gradient(165deg, var(--gj-ink-3), var(--gj-ink) 62%);
  box-shadow: 6px 6px 0 var(--ink, #151413);
  padding: clamp(16px, 3vw, 26px);
  margin: 0 0 22px;
  flex-wrap: wrap;
}
.gjh-top-art { flex: none; display: grid; place-items: center; }
.gjh-top-txt { flex: 1 1 260px; min-width: 0; }
.gjh-kicker {
  font: 700 0.6rem/1.4 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--gj-gold);
  margin: 0 0 6px;
}
.gjh-count {
  font: 900 clamp(1.9rem, 6vw, 2.8rem)/1 "Space Mono", monospace;
  color: var(--gj-gold);
  margin: 0;
  font-variant-numeric: tabular-nums;
}
.gjh-count small {
  display: block;
  font: 700 0.64rem/1.5 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--gj-on-ink-dim);
  margin-top: 7px;
}
.gjh-sub {
  font: 400 0.9rem/1.5 "Playfair Display", Georgia, serif;
  color: var(--gj-on-ink);
  max-width: 52ch;
  margin: 10px 0 0;
  text-wrap: pretty;
}
.gjh-cta { margin-top: 14px; }

/* ── The in-app modal ─────────────────────────────────────────────────────
   Reuses .modal-overlay / .modal from styles.css for the shell, positioning and
   focus trap, and repaints only the inside. */
.modal.jacket-modal {
  background: linear-gradient(170deg, var(--gj-ink-3) 0%, var(--gj-ink) 58%);
  border: 3px solid var(--gj-gold);
  color: var(--gj-on-ink);
  max-width: 620px;
}
.jacket-modal .modal-close { color: var(--gj-on-ink); }
/* The modal header is a row, not a stack: the drawn jacket beside the price
   rather than above it. Stacked, the art plus the title plus the price plus the
   count pushed the perk list almost entirely below the fold on a phone, which
   is the one screen where this modal does most of its selling. */
.jacket-top {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 2px 0 14px;
  border-bottom: 2px solid rgba(230, 184, 67, 0.4);
  margin-bottom: 14px;
}
.jacket-art { flex: none; display: grid; place-items: center; }
.jacket-top-txt { min-width: 0; flex: 1 1 auto; text-align: left; }
.jacket-top h3 {
  font: 900 clamp(1.3rem, 5vw, 1.8rem)/1.05 "Playfair Display", Georgia, serif;
  color: var(--gj-on-ink);
  margin: 0 0 2px;
}
.jacket-kicker {
  font: 700 0.56rem/1.3 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--gj-gold);
  margin: 0 0 5px;
}
/* The price block inherits .gj-price from the page, but in the modal it sits in
   a row beside the art and must not take the page's centred, oversized scale. */
.jacket-top .gj-price { margin: 6px 0 0; font-size: clamp(1.5rem, 6vw, 2rem); }
.jacket-top .gj-price small { margin-top: 5px; }
.jacket-top .gj-left { margin: 10px 0 0; }
@media (max-width: 520px) {
  .jacket-top { gap: 12px; }
}

.jacket-body { padding: 0 2px; }
.jacket-count {
  font: 400 0.86rem/1.5 "Playfair Display", Georgia, serif;
  color: var(--gj-on-ink);
  margin: 0 0 14px;
}
.jacket-count b { color: var(--gj-gold); }

/* The perk list. Same .perk-head / .perk-txt shape the catalog emits for the
   Pass modal, minus the icon column the catalog no longer produces — each entry
   is a gold rule, a bold name and a line of description. */
.jacket-perks {
  list-style: none; margin: 0 0 16px; padding: 0 2px 0 0;
  display: grid; gap: 13px;
  max-height: 44vh; overflow-y: auto;
  /* A scrollable region needs an edge, or it looks like the card is clipped. */
  scrollbar-width: thin;
}
@media (min-width: 720px) { .jacket-perks { grid-template-columns: 1fr 1fr; gap: 14px 20px; } }
.jacket-perks .perk-head {
  grid-column: 1 / -1;
  display: flex; flex-direction: column; gap: 2px;
  border-bottom: 2px solid var(--gj-gold);
  padding-bottom: 5px; margin-top: 4px;
}
.jacket-perks .perk-head-title {
  font: 900 0.98rem/1.2 "Playfair Display", Georgia, serif;
  color: var(--gj-gold);
}
.jacket-perks .perk-head-note {
  font: 700 0.6rem/1.45 "Space Mono", monospace;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--gj-on-ink-dim);
}
.jacket-perks li:not(.perk-head) {
  border-left: 3px solid var(--gj-gold);
  padding-left: 11px;
}
.jacket-perks .perk-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.jacket-perks .perk-txt strong { font: 800 0.88rem/1.25 "Playfair Display", Georgia, serif; color: var(--gj-on-ink); }
/* One description per perk: the long one on a wide card, the short one on a
   phone, so the list stays scannable where the room is tightest. */
.jacket-perks .perk-txt > span { font: 400 0.78rem/1.5 "Playfair Display", Georgia, serif; color: var(--gj-on-ink-dim); }
.jacket-perks .perk-sm { display: none; }
@media (max-width: 560px) {
  .jacket-perks .perk-txt > span:not(.perk-sm) { display: none; }
  .jacket-perks .perk-sm { display: block; }
}
.jacket-cta { width: 100%; text-align: center; }
.jacket-alt {
  margin: 14px 0 0; padding-top: 13px;
  border-top: 1.5px solid rgba(246, 238, 219, 0.2);
  text-align: center;
}
.jacket-alt a { color: var(--gj-gold); font: 700 0.72rem/1.5 "Space Mono", monospace; text-transform: uppercase; letter-spacing: 0.06em; }
.jacket-alt-sep { color: var(--gj-on-ink-dim); margin: 0 8px; }

/* ── The hub header button ────────────────────────────────────────────────
   Sits in .hub-header-right, left of the GOAT Score tile — the one strip
   present on every hub screen. Built to match the neighbouring .score-tile and
   .account-btn-gear in height and weight rather than shout over them: this is
   a way in, not a sales banner, and the actual pitch lives on the page it
   opens. Dark ground with gold trim so it reads as the jacket's own colour
   without competing with the score tile beside it. */
.hub-jacket-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  padding: 8px 13px;
  border: 2px solid var(--gj-gold);
  background: linear-gradient(135deg, var(--gj-ink-3), var(--gj-ink));
  color: var(--gj-gold);
  box-shadow: 2px 2px 0 var(--ink, #151413);
  font: 800 0.66rem/1 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-decoration: none;
  white-space: nowrap;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.hub-jacket-btn:hover { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--ink, #151413); }
.hub-jacket-btn:active { transform: translate(1px, 1px); box-shadow: 1px 1px 0 var(--ink, #151413); }
.hub-jacket-btn:focus-visible { outline: 3px solid var(--gj-gold); outline-offset: 2px; }
.hub-jacket-art { flex: none; display: grid; place-items: center; }
.hub-jacket-art .gj-art { display: block; }
/* The Hall button is the quieter of the two: same frame, no art, so the pair
   reads as one control with a primary and a secondary rather than two rival
   buttons. It carries no icon at all, which is also what keeps it legible when
   the jacket button collapses to art-only on a phone. */
.hub-hall-btn { background: transparent; color: var(--gj-gold); box-shadow: 2px 2px 0 var(--ink, #151413); }
.hub-hall-btn:hover { background: rgba(230, 184, 67, 0.14); }
/* On a phone the header is already tight — keep the jacket, drop the word. The
   emoji alone is unambiguous next to a gold border, and aria-label on the
   anchor keeps it announced properly. */
@media (max-width: 720px) {
  /* The jacket button drops its word and keeps the art; the Hall button has no
     art, so it keeps its word instead and shortens. Dropping the label from
     both would leave two identical gold rectangles. */
  .hub-jacket-btn { padding: 7px 9px; gap: 0; }
  .hub-jacket-txt { display: none; }
  .hub-hall-btn { padding: 7px 10px; }
  .hub-hall-txt { font-size: 0.58rem; }
}

/* ── The hub promo button ─────────────────────────────────────────────────── */
.gj-promo {
  display: flex; align-items: center; gap: 12px; width: 100%;
  border: 2px solid var(--gj-gold);
  background: linear-gradient(135deg, var(--gj-ink-3), var(--gj-ink));
  box-shadow: 3px 3px 0 var(--ink, #151413);
  padding: 10px 14px; margin: 0 0 12px;
  cursor: pointer; text-align: left;
  font-family: "Playfair Display", Georgia, serif;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.gj-promo:hover { transform: translate(-1px, -1px); box-shadow: 5px 5px 0 var(--ink, #151413); }
.gj-promo:focus-visible { outline: 3px solid var(--gj-gold); outline-offset: 2px; }
.gj-promo-art { flex: none; display: grid; place-items: center; }
.gj-promo-art .gj-art { display: block; }
.gj-promo-txt { flex: 1 1 auto; min-width: 0; font-size: 0.84rem; line-height: 1.35; color: var(--gj-on-ink); }
.gj-promo-txt strong { color: var(--gj-gold); }
.gj-promo-left {
  flex: none;
  font: 800 0.62rem/1 "Space Mono", monospace;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--gj-ink);
  background: var(--gj-gold);
  border: 2px solid var(--gj-ink);
  padding: 5px 9px;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 560px) {
  .gj-promo { gap: 9px; padding: 9px 11px; }
  .gj-promo-txt { font-size: 0.76rem; }
  .gj-promo-left { font-size: 0.55rem; padding: 4px 7px; }
}

/* ── A holder's own page (/jacket/N) ──────────────────────────────────────
   The eleventh addition, and the only one that is a thing rather than a
   setting: a permanent public page per holder, showing the number, the banner
   and whatever that player has already put on a public leaderboard.

   Every figure on it comes from a collection that was already
   `allow read: if true` and already keyed by uid, so the page adds no writes,
   no documents, no rules and no moderation surface. It is a rearrangement of
   public data, which is also why it can be read with no sign-in — being
   sendable is the entire value of it.

   Dark card, cream stat blocks: the same split the rest of this file uses, and
   here it does an extra job. The jacket identity is the card at the top; the
   numbers underneath belong to the game, not to the tier, so they stay on the
   page's own ground rather than being dressed up in gold. */
.jp-card {
  display: flex;
  align-items: center;
  gap: clamp(14px, 3vw, 26px);
  border: 3px solid var(--gj-gold);
  background: linear-gradient(165deg, var(--gj-ink-3), var(--gj-ink) 62%);
  box-shadow: 6px 6px 0 var(--ink, #151413);
  padding: clamp(18px, 3vw, 28px);
  margin: 0 0 20px;
  flex-wrap: wrap;
}
.jp-card-art { flex: none; display: grid; place-items: center; }
.jp-card-txt { flex: 1 1 240px; min-width: 0; }
.jp-kicker {
  font: 700 0.6rem/1.4 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--gj-gold);
  margin: 0 0 6px;
}
/* The number is the largest thing on the page on purpose — it is the object
   being shown, and the stats are context for it.

   Scoped `.legal .jp-num` because it is an <h2> and styles.css:1471 sets
   `.legal h2 { font-size: 1.25rem }` at (0,1,1), which beats a lone class.
   Unscoped, this rule lost, and the single most important thing on the page
   rendered at body size — the same trap that painted the hero's ghost button
   invisible. Inside `article.legal`, a bare class is not enough. */
.legal .jp-num,
.jp-num {
  font: 900 clamp(2.6rem, 9vw, 4.2rem)/0.95 "Space Mono", monospace;
  color: var(--gj-gold);
  margin: 0;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.jp-who {
  margin: 10px 0 0;
  font: 700 1.05rem/1.3 "Playfair Display", Georgia, serif;
  color: var(--gj-on-ink);
}
/* The banner pill carries its own colours from the catalog; this makes it
   bigger than a board row's, since here it is the name rather than a cell.

   It also undoes the ellipsis .gj-banner applies on a board. Clipping a handle
   to fit a leaderboard row is right; clipping it on that person's OWN page is
   not — a 40-character handle came out as "AVeryLongHandleRightAtTheFo…" on
   the one page whose subject is them. Here it wraps and shows in full. */
.jp-banner {
  font-size: 1.05rem;
  padding: 3px 13px;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  overflow-wrap: anywhere;
}
.jp-since {
  margin: 8px 0 0;
  font: 400 0.68rem/1.5 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gj-on-ink-dim);
}
/* An unclaimed number: same shape, no gold, dashed — it reads as an empty slot
   rather than a broken page. */
.jp-card.jp-open {
  border-style: dashed;
  border-color: var(--ink, #151413);
  background: transparent;
  box-shadow: none;
}
.jp-open .jp-kicker { color: var(--muted, rgba(21, 20, 19, 0.66)); }
.jp-open .jp-num { color: var(--ink, #151413); }
.jp-open .jp-who { color: var(--ink, #151413); }
.jp-open .jp-since { color: var(--muted, rgba(21, 20, 19, 0.66)); }

.jp-sec { margin: 0 0 20px; }
.jp-sec-h {
  font: 700 0.64rem/1 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted, rgba(21, 20, 19, 0.66));
  margin: 0 0 9px;
}
/* auto-fit rather than a fixed column count: the number of cells depends on
   what this player has actually done, so the grid has to look deliberate at
   two cells and at seven. */
.jp-strip, .jp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 9px;
  margin: 0 0 20px;
}
.jp-grid { margin: 0; }
.jp-cell {
  border: 2px solid var(--ink, #151413);
  background: var(--surface, #fff7df);
  box-shadow: 3px 3px 0 var(--ink, #151413);
  padding: 11px 13px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.jp-cell-v {
  font: 900 1.25rem/1.15 "Space Mono", monospace;
  color: var(--ink, #151413);
  font-variant-numeric: tabular-nums;
  /* Wraps rather than ellipsises. Not every value here is a number — "Hall of
     Famer ×2" is a value too, and with `nowrap` plus ellipsis it came out as
     "Hall of Fam…" in a 161px cell on a phone. A truncated rank is worse than
     a two-line one. `break-word` only splits a word when there is no other
     option, so long figures still keep their digits together. */
  white-space: normal;
  overflow-wrap: break-word;
}
.jp-cell-l {
  font: 700 0.56rem/1.3 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted, rgba(21, 20, 19, 0.66));
}
.jp-empty {
  font: 400 0.86rem/1.55 "Playfair Display", Georgia, serif;
  color: var(--muted, rgba(21, 20, 19, 0.66));
  max-width: 60ch;
  margin: 0 0 20px;
}
/* `stretch`, not `center`: the filled button has 3px borders and 15px padding
   while the outlined one has 2px and 13px, so centring them left a 48px button
   next to a 42px link. Stretch makes the shorter one match without having to
   keep two padding scales in sync. */
.jp-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: stretch; margin: 0 0 24px; }
/* An anchor stretched to a button's height still needs its label centred. */
.jp-actions > .gj-cta-ghost { display: flex; align-items: center; }
/* .gj-cta is an <a> everywhere else on this tier and a <button> here, so the
   button needs the box the anchor rule does not give it. */
button.jp-share {
  font: 700 0.72rem/1 "Space Mono", monospace;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  cursor: pointer;
}
/* Same .legal-specificity trap the hero CTAs hit, and it caught BOTH
   declarations this time: styles.css:1490 sets `.legal a { color: var(--ink);
   text-decoration: underline }` at (0,1,1). The colour was scoped here from
   the start; the underline was not, so the button shipped with a link's
   underline through it. Both need the two-class selector. */
.jp-actions .gj-cta-ghost, .jp-actions .jp-ghost {
  color: var(--gj-gold);
  text-decoration: none;
}

/* A taken Hall row is now a link to that holder's page. The <li> keeps being
   the flex row's owner, so the anchor has to become a flex row too — an inline
   anchor wrapping three spans would collapse the layout the row depends on.
   `.legal a` in styles.css (0,1,1) would otherwise underline it and repaint it
   near-black on the dark row, so the two-class selector clears that. */
.gj-taken .gj-wall-link,
.gj-wall-link {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 11px;
  text-decoration: none;
  color: inherit;
}
.gj-wall li.gj-taken:hover { border-color: var(--gj-gold-hi); }
.gj-wall-link:focus-visible { outline: 2px solid var(--gj-gold); outline-offset: 3px; }
