/* ============================================================================
   PHASE 2  ·  DESIGN EXPERIMENT ONLY
   ----------------------------------------------------------------------------
   Loaded after theme-phase1.css, which keeps supplying the palette and buttons.
   Phase 1 changed colour and weight and left scale and space alone, so the page
   read as the original tidied up. The difference was never chromatic: the
   headline was 22px against the prototype's 68px, and the step area sat seven
   levels deep inside a panel wrapping a gradient card wrapping an inset sheet.

   This file supplies the scale, the air, and a flat step surface. wizard.html
   was restructured to match; every id the JS reaches for was preserved, so
   wizard.js is still untouched.
   ========================================================================= */

/* ---- [hidden] means hidden -------------------------------------------------
   The browser hides a [hidden] element with a UA-origin `display: none`, and a
   UA rule loses to ANY author rule. So every element this project styles with
   a `display:` lays itself out even while the JS believes it is hidden. Three
   were doing exactly that:

     #results          `#results{display:flex}` in style.css. The ENTIRE empty
                       results skeleton, ~1880px of headings with nothing under
                       them, rendered for every first-time visitor to /analysis.
     #bench-move-wrap  `.speed-solver label{display:flex}`. An orphan "My move".
     .verdict          `display:flex`, added by this file. An empty bordered
                       pill with a status dot under Manual Builder's Check team.

   The launch banner hit this first and got a one-off `.launch-banner[hidden]`
   fix with a comment explaining the trap. Three more instances says the rule
   wants stating once, for everything, instead of per element.

   `!important` is safe here: nothing in app/static/ sets `style.display`, so no
   element ever needs to be visible while still carrying the attribute. */
[hidden] { display: none !important; }

/* ---- the measure ---------------------------------------------------------- */
.shell { width: min(100% - 3rem, 68rem); margin-inline: auto; }
.lab { display: block; }
body { background: var(--page); }

/* ---- the hero ------------------------------------------------------------- */
.lab-hero { padding: clamp(3.5rem, 8vw, 6.5rem) 0 clamp(2rem, 4vw, 3rem); }

.lab-eyebrow {
  display: block; font-size: .72rem; font-weight: 600; letter-spacing: .095em;
  text-transform: uppercase; color: var(--t-faint); margin-bottom: 1.15rem;
}
.lab-display {
  margin: 0;
  /* 22px -> this. The single biggest reason the page felt unchanged. */
  font-size: clamp(2.6rem, 5.8vw, 4.4rem);
  font-weight: 600;
  letter-spacing: -.038em;   /* negative tracking, because it is large */
  line-height: 1.02;         /* leading tightens as size grows */
  color: var(--ink);
}
.lab-display em {
  display: block; font-style: normal; color: var(--accent-dark);
  /* Balance the accent line so a narrow screen cannot orphan the last word.
     At 390px this was breaking as "better than they / you". */
  text-wrap: balance;
}
.lab-lede {
  /* Widened from 46ch: the copy is longer now, and at 46ch it ran to ten
     lines, which reads as a wall rather than an invitation. 54ch is still
     inside the comfortable tracking-back range. */
  margin: 1.35rem 0 0; font-size: 1.13rem; line-height: 1.6;
  letter-spacing: -.004em; color: var(--t-ink-2); max-width: 54ch;
}
/* The sign-off. Warmer than the lede and set apart from it, because it is an
   invitation rather than more explanation. */
.lab-kicker {
  /* Above the lede's 1.13rem, not below it. At 1.06rem this closing line was
     SMALLER than the paragraph it closes, so it read as a trailing aside
     rather than the invitation it is. */
  margin: 1rem 0 0; font-size: 1.22rem; font-weight: 550;
  letter-spacing: -.012em; color: var(--ink);
}

/* ---- the step area -------------------------------------------------------- */
.lab-flow { padding-bottom: clamp(2.5rem, 5vw, 4rem); }

.wiz-progress { display: flex; gap: .3rem; margin-bottom: 1.9rem; }
.wiz-progress i {
  height: 3px; flex: 1; width: auto; border-radius: 999px;
  background: var(--t-surface-2); transition: background .35s var(--t-ease);
}
.wiz-progress i.on { background: var(--accent); }

/* The step heading. wizard.js emits a bare <h2>, so it is targeted by position
   rather than by a class it does not write. */
#wizard h2 {
  margin: 0 0 .45rem;
  font-size: clamp(1.35rem, 2.1vw, 1.75rem);
  font-weight: 600; letter-spacing: -.024em; line-height: 1.18; color: var(--ink);
}
#wizard > .hint, #wizard .cap {
  margin: 0 0 1.5rem; color: var(--muted); font-size: .96rem; max-width: 52ch;
}

/* The entry choice is two-up: only two options, weighed side by side.
   The archetype list is one column, so the eye runs straight down comparing
   four things instead of zig-zagging across a grid. */
.wiz-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.wiz-cards.arch { grid-template-columns: 1fr; gap: .6rem; }
.wiz-card {
  display: block; width: 100%; text-align: left;
  padding: 1.05rem 1.15rem; border-radius: 12px;
  background: var(--card); border: 1px solid var(--line);
  box-shadow: none; cursor: pointer;
  transition: border-color .2s var(--t-ease), background .2s var(--t-ease);
}
.wiz-card:hover { border-color: var(--line-strong); background: var(--t-surface); box-shadow: none; }
.wiz-card.picked, .wiz-card.reco { border-color: var(--ink); background: var(--card); }
/* wizard.js builds these cards from inline <b> and <span>, so they need to be
   made block-level here or the title and its description run together on one
   cramped line. */
.wiz-card b, .wiz-card strong {
  display: block; margin-bottom: .3rem;
  font-size: .99rem; font-weight: 600; letter-spacing: -.012em; color: var(--ink);
}
.wiz-card > span:not(.wiz-glyph), .wiz-card .hint, .wiz-card p {
  display: block; margin: 0;
  font-size: .89rem; line-height: 1.5; color: var(--muted);
}
.wiz-cards:not(.arch) .wiz-card { padding: 1.4rem 1.4rem 1.3rem; }
/* The entry-card icons. Drawn rather than emoji: a compass and a GEAR were
   standing in for "guided" and "describe it yourself", and the gear was always
   wrong, since that card configures nothing.

   Each previews the screen it leads to. A short list of choices, or quotation
   marks for your own words. currentColor, so they inherit the headline blue
   and cost nothing to load. */
/* Three classes on purpose. style.css colours every span in the card via
   `.wiz-card span:not(.fit-tag)`, which is (0,2,1) and beats a bare
   `.wiz-glyph` at (0,1,0), so the icons came out muted grey instead of blue.
   (0,3,0) wins outright rather than depending on which stylesheet loads last. */
.wiz-cards .wiz-card .wiz-glyph {
  display: block; width: 34px; height: 34px;
  margin-bottom: 1rem; color: var(--accent-dark);
}
.wiz-cards .wiz-card .wiz-glyph svg { width: 100%; height: 100%; display: block; }
/* Only the two-up entry cards carry a glyph; the one-column archetype list
   does not, so its rows stay compact. */
.wiz-cards.arch .wiz-card .wiz-glyph { display: none; }

/* Inputs */
.picker input[type="text"], #freeform, .lab-field textarea, .lab-field input[type="email"] {
  font: inherit; font-size: .95rem; color: var(--ink);
  padding: .7rem .85rem; border-radius: 11px;
  border: 1px solid var(--line); background: var(--card);
  outline: none; transition: border-color .2s var(--t-ease); width: 100%;
}
.picker input[type="text"]:focus, #freeform:focus,
.lab-field textarea:focus, .lab-field input[type="email"]:focus { border-color: var(--ink); }
.pick-list {
  border: 1px solid var(--line); border-radius: 11px; box-shadow: var(--t-shadow-2);
}
.pick-item { padding: .55rem .7rem; }
.pick-item:hover, .pick-item.on { background: var(--t-surface); }

/* The action row: primary right, where the eye finishes. */
.wiz-nav, .lab-actions { margin-top: 1.8rem; padding-top: 1.35rem; border-top: 1px solid var(--line); }
.wiz-nav.btnrow, .btnrow.lab-actions { display: flex; gap: .6rem; align-items: center; }
.wiz-nav button, .lab-actions button { flex: 0 0 auto; width: auto; }
.wiz-nav .ghost:first-child { margin-right: auto; }
.lab-actions { justify-content: flex-end; }

/* ---- sections ------------------------------------------------------------- */
.lab-section { padding: 0 0 clamp(3rem, 6vw, 5rem); }
.lab-h2 {
  display: block; margin: 0 0 .5rem;
  font-size: clamp(1.55rem, 2.3vw, 2rem); font-weight: 600;
  letter-spacing: -.026em; line-height: 1.14; color: var(--ink);
}
.lab-sub { margin: 0 0 1.75rem; color: var(--muted); font-size: .97rem; max-width: 52ch; }

/* The showcase is a plain section now, not a collapsible amber panel. */
.showcase-panel { border: 0; border-radius: 0; box-shadow: none; background: none; }
/* padding-right reserves the chevron's corner. It is absolutely positioned at
   right: 0, and without this the subtitle runs straight underneath it once the
   line is long enough to reach the edge, which it is on a phone. */
.showcase-head {
  display: block; background: none; border: 0; width: 100%; text-align: left;
  padding: 0 2.25rem .5rem 0;
}
.showcase-sub { display: block; font-size: .97rem; color: var(--muted); margin-top: .35rem; white-space: normal; }
.showcase-chev { position: absolute; right: 0; top: .2rem; }
.showcase-head { position: relative; }
#showcase-body { padding: 1.25rem 0 0; }
.showcase-hint { display: none; }
.showcase-cards { gap: 1rem; }
.showcase-card {
  border-radius: 14px; border: 1px solid var(--line); background: var(--card);
  box-shadow: var(--shadow);
}
.showcase-card:hover { border-color: var(--line-strong); box-shadow: var(--t-shadow-2); }

/* ---- feedback ------------------------------------------------------------- */
.lab-form { display: grid; gap: 1rem; max-width: 40rem; }
.lab-field { display: grid; gap: .4rem; }
.fb-label { font-size: .84rem; font-weight: 600; letter-spacing: -.006em; color: var(--ink); }
.fb-opt { font-weight: 400; color: var(--t-faint); }
.fb-kinds { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; }
.fb-kind {
  display: flex; align-items: center; gap: .6rem; cursor: pointer;
  padding: .7rem .85rem; border-radius: 10px;
  border: 1px solid var(--line); background: var(--card); font-size: .91rem;
  transition: border-color .2s var(--t-ease), background .2s var(--t-ease);
}
.fb-kind:hover { border-color: var(--line-strong); background: var(--t-surface); }
.fb-kind:has(input:checked) { border-color: var(--ink); }
.fb-kind input { accent-color: var(--ink); margin: 0; }
.lab-reward {
  margin: 0; font-size: .89rem; line-height: 1.55; color: var(--t-ink-2);
  padding: .85rem 1rem; border-radius: 10px; background: var(--t-surface);
}
.lab-reward b { color: var(--ink); font-weight: 600; }
/* Notice at collection, under the feedback form and the chat box. Quiet but
   readable: --muted, not --t-faint. A disclosure nobody can read is not one. */
.fb-privacy {
  margin: .7rem 0 0; font-size: .78rem; line-height: 1.55; color: var(--muted);
}
.fb-privacy a { color: var(--cta); text-decoration: underline; }

/* ---- saved builds --------------------------------------------------------- */
.mybuilds { display: grid; gap: .5rem; }

/* ---- analyze a team you already have --------------------------------------
   Two doors to /analysis: a quiet line under the hero's sign-off, and a slim
   card above the feedback form. Both stay below the page's own weight. The
   hero line is smaller than the kicker it follows, and the card's heading is
   smaller than .lab-h2, because both point away from this page. */
.lab-alt {
  margin: .85rem 0 0; font-size: .95rem; line-height: 1.5;
  letter-spacing: -.004em; color: var(--muted);
}
.lab-alt a { color: var(--accent-dark); font-weight: 550; text-decoration: none; }
.lab-alt a:hover { text-decoration: underline; text-underline-offset: 3px; }
.lab-alt a:focus-visible { outline: 2px solid var(--accent-dark); outline-offset: 2px; border-radius: 3px; }

.alt-card {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem 2rem; padding: 1.25rem 1.4rem;
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--t-surface);
}
.alt-card-copy { min-width: 0; }
.alt-card-h {
  margin: 0 0 .3rem; font-size: 1.28rem; font-weight: 600;
  letter-spacing: -.018em; line-height: 1.2; color: var(--ink);
}
.alt-card-sub { margin: 0; font-size: .95rem; line-height: 1.55; color: var(--t-ink-2); max-width: 56ch; }
/* A link, so press.js (buttons only) does not reach it: the press is CSS here,
   the same way how-it-works.html's .hiw-btn does it. */
.alt-card-btn {
  flex: none; display: inline-block; white-space: nowrap;
  background: var(--cta); color: #fff; text-decoration: none;
  border: 1px solid transparent; border-radius: 9px;
  font-weight: 550; font-size: .95rem; letter-spacing: -.008em;
  padding: .72rem 1.15rem;
  transition: background .18s var(--t-ease);
}
.alt-card-btn:hover { background: var(--cta-hover); color: #fff; }
.alt-card-btn:active { transform: scale(.985); }
.alt-card-btn:focus-visible { outline: 2px solid var(--accent-dark); outline-offset: 3px; }
@media (max-width: 640px) {
  .alt-card { flex-direction: column; align-items: stretch; padding: 1.1rem 1.15rem; }
  .alt-card-btn { text-align: center; }
}
@media (prefers-reduced-motion: reduce) {
  .alt-card-btn { transition: none; }
}

/* ---- the free-build offer ------------------------------------------------- */
.launch-banner {
  width: min(100% - 3rem, 68rem); margin: 0 auto clamp(3rem, 6vw, 5rem);
  border-radius: 12px; padding: 1rem 1.15rem; gap: 1rem;
}

@media (max-width: 640px) {
  .shell, .launch-banner { width: min(100% - 2rem, 68rem); }
  .wiz-cards { grid-template-columns: 1fr; }
  .fb-kinds { grid-template-columns: 1fr; }
  .wiz-nav.btnrow, .btnrow.lab-actions { flex-wrap: wrap; }
  .wiz-nav button, .lab-actions button { flex: 1 1 auto; }
}
@media (prefers-reduced-motion: reduce) {
  .wiz-card, .wiz-progress i, .fb-kind, .showcase-card { transition: none !important; }
}

/* ---- the hero gets out of the way ----------------------------------------
   Once questions start, the headline is dead weight: it competes with the
   question and pushes it below the fold. Driven by theme-phase2.js, which
   watches #wizard rather than modifying wizard.js.

   The 1fr -> 0fr collapse only zeroes rows the template NAMES, so the intro
   has to be a single child. With three it would only collapse the first. */
.lab-hero-in { min-height: 0; }
.lab-hero {
  display: grid; grid-template-rows: 1fr; overflow: hidden;
  transition: grid-template-rows .42s var(--t-ease), opacity .26s var(--t-ease),
              padding-top .42s var(--t-ease);
}
.lab.in-flow .lab-hero {
  grid-template-rows: 0fr; opacity: 0; pointer-events: none;
  padding-top: clamp(2rem, 4vw, 3rem); padding-bottom: 0;
}
@media (prefers-reduced-motion: reduce) { .lab-hero { transition: none; } }

/* ============================================================================
   PHASE 2, PART 2: Manual Builder and Team Analysis.

   These two need far less surgery than Team Lab did. Their panels ARE the
   right structure, a flat list of content sections, so there is no seven-level
   nesting to unpick. What they were missing is the same thing Phase 1 missed
   everywhere: scale and air. Numbers below replace 22px headings, 15px panel
   headings and 15px panel padding.
   ========================================================================= */

/* ---- the page head -------------------------------------------------------- */
.pagehead { background: none; border-bottom: 0; }

/* One way of measuring a container, shared with .shell.

   These used `max-width: Nrem` plus `padding-inline: 1.5rem`, which puts the
   gutter INSIDE the N. .shell uses `width: min(100% - 3rem, Nrem)`, which puts
   it outside. Same number, two different meanings: Team Lab's 68rem produced
   1020px of content starting at x=210, and Analysis's 68rem produced 975px
   starting at x=233. Two pages that ask for the same width landed 45px and
   23px apart, which nobody chose.

   So: no horizontal padding, and the gutter rides in a custom property. The
   property is what makes the narrow-screen override below a one-line change
   instead of a specificity fight, since `body:has(.analysis-layout)
   .pagehead-inner` (0,2,1) outranks anything a media query would reasonably
   write against `.pagehead-inner` (0,1,0). Every rule here reads --gutter off
   the element, so the value wins on cascade order rather than on selector
   weight.

   Manual Builder stays wider on purpose (see the grid note below); it is now
   wide by the same measure as everything else. */
.pagehead-inner, .layout { --gutter: 3rem; margin-inline: auto; max-width: none; }

.pagehead-inner {
  width: min(100% - var(--gutter), 80rem);
  padding: clamp(3rem, 6vw, 5rem) 0 clamp(1.5rem, 3vw, 2.5rem);
}
/* Analysis is the narrower page, so its head lines up with its own grid. */
body:has(.analysis-layout) .pagehead-inner { width: min(100% - var(--gutter), 68rem); }
.pagehead h1 {
  margin: 0 0 .7rem;
  font-size: clamp(2.2rem, 4.4vw, 3.4rem);   /* was 22px */
  font-weight: 600; letter-spacing: -.035em; line-height: 1.04;
  color: var(--ink);
}
.pagehead p {
  margin: 0; font-size: 1.08rem; line-height: 1.55; letter-spacing: -.004em;
  color: var(--t-ink-2); max-width: 46ch;
}

/* ---- the grid -------------------------------------------------------------
   A reading measure governs PROSE, not a tool. Narrowing the Builder to 68rem
   cost its slot grid a column (340px slots could no longer fit two across a
   680px track), leaving all six stacked. So the Builder gets real width and the
   measure is enforced where it belongs: on the paragraphs.
   Analysis stays single-column and narrower, which is what it already was. */
.layout { gap: 1.25rem; padding: 0 0 clamp(3rem, 6vw, 5rem); }
.layout:not(.analysis-layout) { width: min(100% - var(--gutter), 80rem); }
.analysis-layout { width: min(100% - var(--gutter), 68rem); }
.slots { grid-template-columns: repeat(auto-fill, minmax(21rem, 1fr)); gap: 1rem; }

/* ---- panels --------------------------------------------------------------- */
.panel {
  border: 1px solid var(--line); border-radius: 14px;
  box-shadow: var(--shadow);
  padding: clamp(1.15rem, 2vw, 1.6rem);       /* was 15px */
}
.panel + .panel { margin-top: 1.25rem; }
.panel h2 {
  margin: 0 0 .45rem;
  font-size: 1.12rem;                          /* was 15px */
  font-weight: 600; letter-spacing: -.018em; line-height: 1.25;
  color: var(--ink);
}
.hint {
  margin: 0 0 1rem; font-size: .92rem; line-height: 1.55; color: var(--muted);
  max-width: 60ch;
}

/* ---- slot cards ----------------------------------------------------------- */
.slot { border-radius: 14px; border-color: var(--line); }
.slot:hover { border-color: var(--line-strong); }

/* ---- inputs --------------------------------------------------------------- */
#paste, #ana-paste, .panel textarea, .panel input[type="text"], .panel select {
  border-radius: 10px; border: 1px solid var(--line);
  font-size: .92rem; padding: .65rem .8rem;
  transition: border-color .2s var(--t-ease);
}
#paste:focus, #ana-paste:focus, .panel textarea:focus,
.panel input[type="text"]:focus, .panel select:focus { border-color: var(--ink); outline: none; }

/* The analysis chat rides the old gradient hero treatment. Phase 1 already
   flattened .hero-card; this keeps its inner sheet from re-adding a frame. */
.chat-panel .hero-sheet { margin: 0; padding: 1rem 0 0; background: none; box-shadow: none; }
.chat-panel .hero-head { padding: 0; }

/* Dressed as the Theorizer band on Team Lab: same tinted ground, same radius,
   same mark on the heading. The two are the same kind of object, a place you
   describe what you want in words and get reasoning back, so a reader who
   learns one should recognise the other. Values match .lab-flow.shell rather
   than approximating it. */
.chat-panel.hero-card {
  background: var(--t-surface);
  border: 0;
  border-radius: 18px;
  box-shadow: none;
  padding: 2rem 1.5rem clamp(2rem, 3.5vw, 2.5rem);
}
.chat-panel .hero-title {
  display: flex; align-items: center;
  color: var(--ink);
}
/* Same size and colour as the Theorizer's own heading mark, and em-based for
   the same reason: it tracks the title instead of drifting out of proportion. */
.chat-panel .hero-title .head-spark {
  display: inline-flex; align-items: center;
  width: .82em; height: .82em; margin-right: .34em;
  color: var(--accent-dark);
  vertical-align: -.06em;
  flex: none;
}
.chat-panel .hero-title .head-spark svg { width: 100%; height: 100%; display: block; }
/* The panels the chat sits among are white on this tinted band, so the inner
   surfaces have to step the other way to stay legible. */
.chat-panel .chat-log, .chat-panel .chat-inrow textarea { background: var(--card); }

/* ---- tables and data ------------------------------------------------------ */
.threats th {
  font-size: .72rem; letter-spacing: .085em; color: var(--t-faint); font-weight: 600;
}
.threats td { font-size: .9rem; }

@media (max-width: 1020px) {
  .layout { grid-template-columns: minmax(0, 1fr); }
}
/* Matches .shell's own narrow-screen gutter (2rem, so 1rem a side) at the same
   breakpoint. One property, so the head and the grid cannot drift apart. */
@media (max-width: 640px) {
  .pagehead-inner, .layout { --gutter: 2rem; }
}

/* ---- the build disclaimer -------------------------------------------------
   Sits under the build log, just above the result (owner call 2026-10-08):
   before a build it reads under the buttons, because the log is hidden then.
   It sets expectations about the RESULT, so it sits right before the team.
   Quiet by design: honest, not alarming. A hairline above separates it from
   what precedes it without adding another filled surface. */
.wiz-disclaimer {
  margin: 1.25rem 0 0; padding-top: 1.1rem;
  border-top: 1px solid var(--line);
  font-size: .88rem; line-height: 1.6; color: var(--muted);
  max-width: 62ch;
}
.wiz-disclaimer b { color: var(--t-ink-2); font-weight: 600; }

/* ---- the Team Lab sparkle -------------------------------------------------
   A drawn mark, not the ✨ emoji: solid, and locked to the headline's accent
   blue rather than the yellow the emoji renders in.

   The colour is set explicitly instead of inheriting through currentColor,
   because the nav link's own colour changes between states (muted when
   inactive, ink when active) and the mark should stay the same blue on every
   page. Three classes so it beats `.mainnav a` colouring its children. */
.mainnav a .nav-spark {
  display: inline-flex; align-items: center;
  width: 15px; height: 15px; margin-right: .38rem;
  color: var(--accent-dark);
  vertical-align: -.18em;
  flex: none;
}
.mainnav a .nav-spark svg { width: 100%; height: 100%; display: block; }
/* The link becomes a flex row so the mark sits on the text baseline rather
   than shifting the line box. */
.mainnav a:has(.nav-spark) { display: inline-flex; align-items: center; }

/* ---- the sparkle on the step heading --------------------------------------
   Same mark as the nav, scaled to the heading. Colour is explicit for the same
   reason it is in the nav: the heading is ink, and the mark should not inherit
   that. Sized in em so it tracks the heading through its clamp() rather than
   drifting out of proportion at either end. */
#wizard h2 .head-spark {
  display: inline-flex; align-items: center;
  width: .82em; height: .82em; margin-right: .34em;
  color: var(--accent-dark);
  vertical-align: -.06em;
  flex: none;
}
#wizard h2 .head-spark svg { width: 100%; height: 100%; display: block; }
#wizard h2:has(.head-spark) { display: flex; align-items: center; }

/* ============================================================================
   The grand-launch offer.

   Placement: directly under the entry cards. Someone has just read the pitch
   and seen the two ways in, and the next thought is "what does this cost".
   The offer answers it at exactly that beat. Above the cards it would
   interrupt the path to action; further down it arrives after the decision.

   Treatment: a white card with an accent-tinted hairline and a soft tinted
   shadow, so it reads as highlighted without becoming a coloured slab. The old
   banner was a gold gradient with a light sweep, a pulsing dot and four
   sparkles, which is exactly the register this redesign has been removing. The
   offer is good; it does not need to shout.
   ========================================================================= */
.launch-banner {
  width: min(100% - 3rem, 68rem);
  /* Was clamp(3rem, 6vw, 5rem) = 48-80px. The banner tends to land near the
     fold on common laptop heights, and at 48px the next section crowded in
     right behind it, so it read as jammed against the cut rather than as the
     end of a thought. 72-112px gives it room to finish. */
  margin: 0 auto clamp(4.5rem, 8vw, 7rem);
  display: flex; align-items: center; gap: 1.15rem;
  padding: 1.15rem 1.35rem;
  border-radius: 14px;
  background: var(--card);
  border: 1px solid rgba(27, 157, 226, .32);
  box-shadow: 0 2px 14px -2px rgba(27, 157, 226, .14), 0 1px 2px rgba(13, 18, 20, .04);
  color: var(--ink);
}
/* LOAD-BEARING, and the original stylesheet says so in a comment worth keeping:
   the rule above sets display:flex, which as an author style beats the browser's
   own [hidden] rule. Without this line a hidden banner still renders, and the
   people who would see it are the signed-in ones the offer is not for. */
.launch-banner[hidden] { display: none; }

/* Once the questions start, the offer is clutter: the decision to engage is
   already made. Same reasoning as the hero collapse, reusing the class the
   observer already sets. */
.lab.in-flow .launch-banner { display: none; }

/* ---- the build prompt inside an opened showcase card ----------------------
   This is the one place on the page that says, in a full sentence, what the
   product actually does. It was a gold gradient band with a hairline, which
   read as a promo strip stapled under the report rather than as part of it.

   Same tinted surface, radius and centring as the entry cards at the top of
   the page, so a visitor who reaches the end of a build meets a shape they
   already know from the moment they arrived. It stays scoped to an opened
   card: the page is not asking anyone to build until they have seen one. */
.showcase-cta {
  margin-top: 1.5rem;
  padding: clamp(1.9rem, 4vw, 2.5rem) 1.75rem;
  border: 0; border-radius: 18px;
  background: var(--t-surface);
  text-align: center;
}
.showcase-cta p {
  /* 62ch, not the bookend's 52: this copy is longer, and at 52-56 it set as
     four short centred lines in a 1020px band, a column of text down the
     middle of a wide box. 62 lands it on three and fills 57% of the band. */
  margin: 0 auto 1.35rem; max-width: 62ch;
  font-size: 1rem; line-height: 1.55; color: var(--t-ink-2);
}
.showcase-cta p b { color: var(--ink); font-weight: 600; }
/* .primary stretches to its container by default. */
.showcase-cta .primary { width: auto; padding: .8rem 1.7rem; font-size: 1rem; }

.lb-token { width: 46px; height: 46px; flex: none; }
.lb-token::before, .lb-token-ring { display: none; }
.lb-token img { width: 46px; height: 46px; animation: none; transform: none; filter: none; }

.lb-copy { flex: 1 1 auto; min-width: 0; }
.lb-badge {
  display: inline-flex; align-items: center; gap: .42rem;
  background: none; border: 0; padding: 0; box-shadow: none;
  color: var(--accent-dark);
  font-size: .8rem; font-weight: 600; letter-spacing: .095em; text-transform: uppercase;
}
.lb-badge-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); animation: none; box-shadow: none;
}
.lb-title {
  margin: .35rem 0 .15rem;
  font-size: 1.08rem; font-weight: 600; letter-spacing: -.016em; line-height: 1.25;
  color: var(--ink); text-shadow: none;
}
.lb-sub { margin: 0; font-size: .9rem; line-height: 1.5; color: var(--t-ink-2); }
.lb-sub b { color: var(--ink); font-weight: 600; }

.lb-cta { display: flex; flex-direction: column; align-items: flex-end; gap: .45rem; flex: none; }
.lb-fine {
  font-size: .74rem; line-height: 1.4; color: var(--t-faint);
  max-width: 24ch; text-align: right;
}

@media (max-width: 720px) {
  .launch-banner { flex-wrap: wrap; gap: .9rem; }
  .lb-cta { align-items: stretch; width: 100%; }
  .lb-cta .lb-btn { width: 100%; }
  .lb-fine { text-align: center; max-width: none; }
}
@media (max-width: 640px) {
  .launch-banner { width: min(100% - 2rem, 68rem); }
}

/* Two leftovers from the gold banner, both invisible until the CTA was
   actually rendered at the new type size:

   1. `.lb-cta` carried a hardcoded width: 158px, measured for the original
      12.5px button label. Phase 1 raised buttons to 14px, so the text needed
      159px and was being clipped by three pixels: enough to shave the last
      letter, not enough to notice without measuring. Sizing to content instead
      of to a number means the next copy change cannot re-break it.
   2. `.lb-btn` still ran lb-cta-pulse, a box-shadow glow looping forever. The
      original stylesheet warns that this animated value beats any normal
      shadow declaration, so it had been quietly overriding the calm treatment
      the whole time. */
.lb-cta { width: auto; }
.lb-cta .lb-btn {
  width: auto; animation: none; box-shadow: none;
  transform: none; letter-spacing: -.008em;
}
.lb-cta .lb-btn:hover { transform: none; filter: none; }

/* ---- the showcase expand/collapse chevron ---------------------------------
   Bigger, and vertically centred on the heading rather than pinned to a fixed
   top offset, so growing it does not drag it out of alignment.

   Centring uses translateY, and style.css rotates this same element when the
   panel is closed. A bare `transform: rotate(...)` there would REPLACE the
   centring and drop the glyph, so the closed state has to restate both
   transforms together. */
.showcase-head .showcase-chev {
  position: absolute; right: .1rem; top: 50%;
  transform: translateY(-50%);
  font-size: 50px; line-height: 1;
  color: var(--t-faint);
  transition: transform .2s var(--t-ease), color .2s var(--t-ease);
}
.showcase-head:hover .showcase-chev { color: var(--ink-2); }
.showcase-panel.closed .showcase-head .showcase-chev {
  transform: translateY(-50%) rotate(-90deg);
}

/* ============================================================================
   The hero proof panel.

   The empty right side was never the real problem. The problem was that the
   page's strongest asset, actual teams with actual Pokemon in them, did not
   appear until someone scrolled to the showcase. Above the fold a visitor got
   a headline, six lines of prose and two cards: all words, no evidence.

   So this is proof, not decoration. A real finished team from the showcase,
   with the real request that produced it. Deliberately quiet: hairline, no
   drop shadow, muted label. It should read as a specimen on a shelf, not an
   advertisement, and it must not out-shout the headline beside it.
   ========================================================================= */
.lab-hero-in {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 19rem;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}
.lab-hero-copy { min-width: 0; }

.lab-proof {
  padding: 1.15rem 1.25rem 1.25rem;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  /* Optically aligned to the headline's cap height rather than its line box,
     so the panel looks level with the type instead of level with the markup. */
  margin-top: .35rem;
}
.proof-label {
  display: block;
  font-size: .68rem; font-weight: 600; letter-spacing: .095em; text-transform: uppercase;
  color: var(--t-faint); margin-bottom: .9rem;
}
.proof-six {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: .25rem; margin-bottom: 1rem;
  padding-bottom: 1rem; border-bottom: 1px solid var(--line);
}
.proof-six img {
  width: 100%; height: auto; display: block;
  /* Reserve the square before the picture loads. Every sprite is 96x96; a
     slot with no size collapsed the row to 0px on each rotation, and on a
     phone that moved the whole page (owner report 2026-10-08). */
  aspect-ratio: 1 / 1;
  image-rendering: -webkit-optimize-contrast;
}
.proof-ask {
  margin: 0 0 .55rem;
  font-size: .89rem; line-height: 1.45; color: var(--ink-2, #444e56);
  padding-left: .7rem; border-left: 2px solid var(--line-strong);
}
.proof-ask::before { content: "\201C"; }
.proof-ask::after  { content: "\201D"; }
.proof-meta { display: block; font-size: .78rem; color: var(--t-faint); }

/* Below the two-up breakpoint the proof used to step out entirely, on the
   reasoning that it stopped earning its place beside the type. That reasoning
   is about competing for HORIZONTAL space, which is a thing that only exists
   in the two-up layout: once the hero stacks, nothing is beside anything.

   What it cost was the page's only evidence. A phone visitor got a headline,
   nine lines of prose and no Pokemon at all, which is the wrong trade when the
   launch plan points four phone-first platforms at this page.

   Measured at 390x844, which is where the argument actually resolves:

     the intro paragraph   289px
     this strip            169px

   So the paragraph is what costs the space, not the proof. Trimming it (see
   .lede-long below) pays for the strip outright and lands the first entry card
   at 851px against today's 856. The evidence is free.

   Same element, restyled, never a second copy: initHeroProof() in wizard.js
   paints .proof-six / .proof-ask / .proof-meta and rotates them on a timer, so
   turning the card into a strip inherits the rotation and the gallery link
   without the JS knowing anything happened.

   Card becomes strip: hairline on top instead of a box, because a full-width
   bordered card on a phone reads as an advert, and the panel was written to
   read as a specimen. The sprite row is capped rather than stretched so the
   art stays 55px at any width in this range instead of ballooning on a
   tablet. */
@media (max-width: 940px) {
  /* row-gap 0 because .lab-hero-in's gap is a clamp on vw, written for the
     COLUMN split. Stacked it becomes vertical space that swings with the
     window: 71px at 940 and 54px at 390, for the same seam. The strip owns
     its own spacing instead, so the gap above the hairline is one number. */
  .lab-hero-in { grid-template-columns: 1fr; row-gap: 0; }
  .lab-proof {
    margin-top: 2.4rem; padding: 1.05rem 0 0;
    border: 0; border-top: 1px solid var(--line); border-radius: 0;
    background: none;
  }
  .lab-proof.is-link:hover { border-color: var(--line-strong); }
  .proof-label { margin-bottom: .6rem; }
  .proof-six {
    grid-template-columns: repeat(6, minmax(0, 3.2rem));
    gap: .25rem; margin-bottom: .55rem;
    padding-bottom: 0; border-bottom: 0;
  }
  /* Ask and credit share a line here. Stacked they cost a whole line for four
     words, and the strip is paying for itself in scroll depth. */
  .proof-ask {
    display: inline; margin: 0; padding-left: 0; border-left: 0;
    font-size: .95rem;
  }
  .proof-meta { display: inline; font-size: .8rem; margin-left: .35rem; }
}

/* The half of the intro that only phones drop. Everything wider keeps the full
   paragraph, so the desktop hero is byte-identical to what it was. 640px and
   not 940 because the nine lines are a phone problem: the same copy is four
   lines at tablet width, where it costs nothing and reads fine. */
@media (max-width: 640px) {
  .lede-long { display: none; }
}

/* ============================================================================
   The corner mascot.

   ABSOLUTE, never fixed. That single choice is what separates a signature from
   a chat widget. A mascot that follows the scroll is the visual language of
   support bots and cookie prompts, and it would undo the restraint the rest of
   this redesign bought. This one waits at the bottom of the page and is a
   reward for reaching the end.

   The occlusion is the other half. Rising from BEHIND the footer edge reads as
   peeking; the same art floating fully visible in the corner reads as pasted
   on. The footer therefore needs an opaque background and a higher stacking
   order, which is what actually crops her.
   ========================================================================= */
.mascot-anchor {
  position: relative; z-index: 0;
  width: min(100% - 3rem, 68rem);
  margin: 0 auto;
  /* Reserves the band she occupies instead of floating over whatever happens to
     be there. height:0 worked on Team Lab only by luck: its last section is a
     narrow left-aligned form, so the bottom-right corner was empty. Manual
     Builder and Team Analysis fill the full width, and she landed on top of the
     Showdown paste panel's Copy and Import buttons (measured 240x90).

     158px is not arbitrary: she is 210px tall and hangs 52px below this
     element, so 210 - 52 is exactly the height she rises above the footer.
     Rounded up to 160 for a hair of tolerance. Anything that changes her size
     or offset has to change this too, which is why the arithmetic is written
     down rather than left as a magic number. */
  height: 160px;
}
.site-mascot {
  position: absolute; right: -.5rem; bottom: -3.25rem;
  width: 240px; height: auto; display: block;
  pointer-events: none;      /* never intercepts a click meant for the page */
}
/* Opaque and above, so it crops the mascot instead of letting her float over it. */
.footer { position: relative; z-index: 1; background: var(--page); }

/* She stays on phones too (owner call 2026-09-05). The rule that used to hide
   her here claimed the bottom-right corner was "thumb territory and where
   sticky UI lives", and that was simply wrong about this page: she is in
   document flow at the very end, not fixed, so she is only ever on screen once
   the reader has scrolled past everything. Nothing on the site is pinned to the
   bottom of the viewport, the one position:fixed being a full-screen modal.

   Smaller, and the reserved band shrinks with her. Same arithmetic as above,
   restated because the numbers differ: at 170px wide the art is 149px tall
   (620x542 native) and hangs 3.25rem below the anchor, so the band it needs is
   149 - 49, rounded up to 102. */
@media (max-width: 940px) {
  .site-mascot { width: 170px; right: -.25rem; }
  .mascot-anchor { height: 102px; }
}

/* ============================================================================
   A+B: the Theorizer becomes the centre of the page.

   The problem was never that this section was too quiet. It was that the entry
   cards and the showcase cards were the same object, white with a hairline at
   the same radius and weight, so the most important decision on the site
   carried the same weight as a footnote.

   A. The section gets its own ground. On an all-white page one tinted band is
      a strong signal precisely because it is the only one.
   B. The cards get bigger. Size is the oldest hierarchy signal there is and it
      costs no new colour.

   The band applies to the WHOLE flow, not just the entry step, so the tint
   also marks "you are inside the tool" while the questions are running.
   ========================================================================= */
/* .lab-flow.shell, at (0,2,0), because this element carries BOTH classes and a
   bare .lab-flow tied .shell on specificity. The first attempt used a negative
   margin-inline to bleed the band, which silently destroyed .shell's
   `margin-inline: auto` and un-centred the whole section: the band ran off the
   left edge and the cards sat 80px left of the headline.

   Widening instead of bleeding avoids the conflict entirely. The band is the
   shell's 68rem plus its own 1.5rem of padding on each side (71rem), still
   centred, so the CONTENT lands exactly where the shell would have put it
   while the tint extends past it. At narrow widths min(100%) lets the band
   reach the viewport edge, and the 1.5rem padding still matches the shell's
   own gutter, so alignment holds all the way down. */
.lab-flow.shell {
  background: var(--t-surface);
  border-radius: 18px;
  width: min(100%, 71rem);
  margin-inline: auto;
  padding: 2rem 1.5rem clamp(2.25rem, 4vw, 2.75rem);
}
/* The heading sits inside the band now, so it no longer needs its own gap. */
.lab-flow .wiz-progress { margin-bottom: 1.75rem; }

/* B: physical presence. */
.wiz-cards:not(.arch) .wiz-card { padding: 2rem 1.9rem 1.85rem; border-radius: 14px; }
.wiz-cards:not(.arch) .wiz-card b { font-size: 1.12rem; margin-bottom: .4rem; }
.wiz-cards:not(.arch) .wiz-card span:not(.wiz-glyph) { font-size: .93rem; }
.wiz-cards:not(.arch) .wiz-glyph,
.wiz-cards:not(.arch) .wiz-card .wiz-glyph { width: 40px; height: 40px; margin-bottom: 1.15rem; }
.wiz-cards:not(.arch) { gap: 1.15rem; }
/* Cards are white ON the band now, so they need their own edge to lift off it. */
.lab-flow .wiz-card { background: var(--card); box-shadow: 0 1px 2px rgba(13, 18, 20, .04); }

/* The launch banner loses its accent tint. It sits directly under the band, and
   two highlighted things stacked fight each other. One emphasis per screen.

   It also needs its own top margin now. The banner only ever had a margin
   BELOW it, which was invisible while it sat on plain page background. Once
   the Theorizer gained a tinted band the two edges met with nothing between
   them, and the banner read as attached to the band rather than as the next
   thing down. */
.launch-banner {
  border-color: var(--line);
  box-shadow: var(--shadow);
  margin-top: clamp(2rem, 4vw, 3rem);
}

@media (max-width: 640px) {
  /* The shell's gutter is 1rem here, so the band's padding follows it. */
  .lab-flow.shell { width: 100%; padding-inline: 1rem; border-radius: 14px; }
}

/* ---- the wordmark ---------------------------------------------------------
   17px/11px was sized for the old cramped header. With the rest of the page on
   a larger scale it had started to read as small print. */
/* Sized in px, not rem, on purpose: style.css sets :root font-size to 15px,
   so 1.19rem resolved to 17.85px against an original of 17px, a 5% bump that
   read as no change at all. These are the real target sizes. */
.wordmark-name { font-size: 22px; letter-spacing: -.02em; }
.wordmark-sub  { font-size: 13.5px; letter-spacing: .012em; margin-top: 3px; }
/* The monoline mark, 62px tall and leaning 7 degrees right (owner call
   2026-09-10). Three things worth knowing before changing either number:

   HEIGHT, not width. The previous mascot art was 204x160 (wide) and was sized
   `width: 68px`. This mark is 260x248 (near square), so a width of 68 would
   draw it 65px TALL. Height is what the bar is measured by.

   62px is the size that grows the bar. The bar's height is the taller of the
   nav row (60px) and the brand row, plus 12px of padding, so anything up to
   60px leaves it at 72 and 62px takes it to 74. That is deliberate here, not
   an oversight: see the scroll-margin-top note in theme-phase1.css, which is
   the one place that number was written down by hand.

   The rotation is a transform, so it does NOT affect layout: the box stays
   65x62 while the drawn mark is 72x70. It clears the 74px bar by ~2px on each
   edge, which is the whole margin. Increase either the angle or the size and
   the mark starts breaching the bar rather than making it taller. */
.brand .logo { height: 62px; width: auto; transform: rotate(7deg); }
/* Set in px, not rem. The root is 15px here, so a rem value lands on
   fractional pixels and the mark and the wordmark stop agreeing.

   line-height in px for a related reason: the inherited 1.45 gave 21.75px,
   which made the nav link 59.75 tall and the whole bar 71.75. A sticky
   translucent bar that ends on a fraction of a pixel antialiases its own
   bottom edge across two rows, which is half of the seam this is fixing.
   22px lands the bar on a whole 72. */
.mainnav a { font-size: 15px; line-height: 22px; }

/* ---- the active page ------------------------------------------------------
   Option 1: a straight 2px underline. Square ends, so it reads as a tab, which
   is what the nav is. The bar was 3px and rounded, and rounding a heavy
   one-sided border is what produced the cradle shape it had before.

   The label also goes to the darker accent and 600 weight, so the indicator is
   not carrying the job alone: colour, weight and the bar all agree. */
.mainnav a {
  border-radius: 0;
  border-bottom: 2px solid transparent;
  transition: color .18s var(--t-ease), border-color .18s var(--t-ease);
}
.mainnav a.active {
  color: var(--accent-dark);
  font-weight: 600;
  border-bottom-color: var(--accent);
}
.mainnav a:hover:not(.active) { color: var(--ink); }

/* The banner's credit token. The original credit.png is only 48px and went
   soft when the banner blew it up; this is the owner's higher-resolution art,
   resampled to 156px so 52px never upscales, even at 3x. Slightly larger than
   the 46px it replaced, because the art can now carry it. */
.lb-token { width: 52px; height: 52px; }
.lb-token .lb-candy { width: 52px; height: 52px; display: block; }

/* ============================================================================
   THE BUILD CONSOLE

   This is what someone stares at for minutes after paying, so it is the one
   place on the site that should feel like machinery. It stays dark for that
   reason: it becomes the focal point of the page precisely because nothing
   else is.

   What changes is the register. It read as a TERMINAL (navy #16324a, monospace,
   a scrollbar themed to match) and terminal is the aesthetic the voice rules
   ask us to avoid. It is now a STATUS PANEL: near-black in the same family as
   --ink so it belongs to this palette rather than the old one, and set in the
   page's own typeface.

   The emoji stay. They arrive inside the event text from the server, so no
   stylesheet could remove them, and as status markers at a glance they are
   doing real work rather than decorating.
   ========================================================================= */

/* The heading above it. */
.avoid-h {
  font-size: .82rem; font-weight: 600; letter-spacing: .075em; text-transform: uppercase;
  color: var(--t-faint); margin: 1.6rem 0 .7rem;
}
/* Uppercase suits a short label. It does not suit a sentence: the promise that
   used to live inside this heading ran three lines of small caps on a phone,
   which is a wall, not a label. Same words, normal case, under the heading. */
.avoid-note {
  margin: -.35rem 0 .7rem; color: var(--muted); font-size: .9rem; max-width: 46ch;
}

/* ---- progress -------------------------------------------------------------
   The old bar ran BACKWARDS in colour terms: unfilled segments were accent
   blue and filled ones turned orange, so the bright colour spread as a
   warning would. Now the track is quiet and the fill is the accent, which is
   what "progress" reads as everywhere else. */
.ai-progress { gap: 4px; margin: 0 0 1rem; }
.ai-progress i {
  height: 4px; border-radius: 999px;
  background: var(--t-surface-2);
  box-shadow: none;
  transition: background-color .45s var(--t-ease);
}
.ai-progress i.on { background: var(--accent); box-shadow: none; }

/* ---- the log --------------------------------------------------------------- */
/* The log may never run off the bottom of the window (owner report
   2026-10-08). Its cap is the smaller of 320px and what is left of the window
   under the sticky header, the "Build progress" heading, the bar and a gap,
   with a 160px floor so a tiny window still shows a few lines. `100vh` first,
   then `100dvh` where supported (mobile browser bars). wizard.js
   fitRunningLog() holds this height while a build runs and scrolls once. */
#ai-run { --ai-log-cap: max(160px, min(320px, calc(100vh - var(--header-h, 74px) - 110px))); }
@supports (height: 100dvh) {
  #ai-run { --ai-log-cap: max(160px, min(320px, calc(100dvh - var(--header-h, 74px) - 110px))); }
}
#ai-run.is-running .ai-log { min-height: var(--ai-log-cap); }
/* Breathing room between the log (or the disclaimer under it) and "Your team". */
#ai-result { margin-top: 1.75rem; }
.ai-log-wrap { margin-top: 0; border-radius: 12px; overflow: hidden; }
.ai-log {
  padding: 1rem 1.15rem;
  max-height: var(--ai-log-cap, 320px);
  background: var(--surface-dark);     /* tinted toward the accent, not neutral */
  color: #b3cadb;
  font-family: inherit;                /* not monospace: this is prose, not a shell */
  font-size: .875rem;
  line-height: 1.75;
  scrollbar-width: thin;
  scrollbar-color: #35586f var(--surface-dark);
}
.ai-log::-webkit-scrollbar { width: 10px; }
.ai-log::-webkit-scrollbar-track { background: var(--surface-dark); }
.ai-log::-webkit-scrollbar-thumb {
  background: #35586f; border-radius: 8px; border: 2px solid var(--surface-dark);
}
.ai-log::-webkit-scrollbar-thumb:hover { background: #48748f; }
.ai-log::-webkit-scrollbar-corner { background: var(--surface-dark); }

/* Each line. The three states earn their colour; everything else stays quiet
   so the ones that matter can be found by scanning. */
.ai-ev { padding: .05rem 0; }
.ai-ev.gate { color: #e8eef2; font-weight: 550; }
.ai-ev.done { color: #6fd8a4; font-weight: 550; }
.ai-ev.err  { color: #ff9aa5; font-weight: 550; }

/* ---- the instruction slab -------------------------------------------------
   Sits directly above the console and was the same navy, so it has to move
   with it or the two darks would read as a mistake. Monospace is kept HERE on
   purpose: this one really is a command, and the fixed width is what makes it
   scannable. */
/* Option D: this is not a terminal. It is the sentence the reader assembled,
   shown back to them, so it reads as a quiet panel with an accent edge rather
   than a second black slab stacked on the button below it. That leaves exactly
   one dark object in view, and it is the control you are meant to press. */
.wiz-output {
  margin: 1.25rem 0;
  padding: .9rem 1.1rem;
  border-radius: 12px;
  background: var(--t-surface-3);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  color: var(--t-ink-2);
}
.wiz-output code { font-size: .85rem; }

/* The instruction as rows rather than a command line. The label column is
   fixed so the values line up down the box, and 11rem because the longest
   label ("Must have a plan for") wrapped at 9.5 and took its row to two lines.
   Widened rather than shortened: the wording is the clearest of the options
   for someone who has never played this format. Below 560px it stacks, since
   11rem of label plus a list of six Pokemon names does not fit a phone. */
.wiz-inst { display: grid; gap: .5rem; }
.wiz-inst-row {
  display: grid; grid-template-columns: 11rem minmax(0, 1fr);
  gap: .75rem; align-items: baseline;
}
.wiz-inst-k {
  font-size: .68rem; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--t-faint);
}
.wiz-inst-v { font-size: .95rem; line-height: 1.5; color: var(--ink); }
@media (max-width: 560px) {
  .wiz-inst-row { grid-template-columns: 1fr; gap: .1rem; }
}

/* ============================================================================
   THE FINISHED REPORT

   This is the thing someone paid for, so it should read like a document rather
   than like app chrome: a real heading hierarchy, a measure you can track back
   along, and generous leading.

   The measure is applied to PROSE ONLY, not to .ai-report itself. Capping the
   container would squeeze the tables and pastes, which genuinely need width.
   ========================================================================= */
.ai-report {
  padding: clamp(1.5rem, 3vw, 2.25rem);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow);
  font-size: 1rem;
  line-height: 1.7;
  color: var(--ink-2, #444e56);
}
.ai-report p, .ai-report li { max-width: 72ch; }
.ai-report > *:first-child { margin-top: 0; }

/* A real hierarchy. These were all one size at one margin, so a section
   heading and a sub-point looked identical and the document had no shape. */
.ai-report h2 {
  margin: 2.25rem 0 .6rem; font-size: 1.5rem; font-weight: 600;
  letter-spacing: -.024em; line-height: 1.2; color: var(--ink);
}
.ai-report h3 {
  margin: 1.85rem 0 .5rem; font-size: 1.14rem; font-weight: 600;
  letter-spacing: -.016em; line-height: 1.3; color: var(--ink);
}
.ai-report h4, .ai-report h5 {
  margin: 1.4rem 0 .4rem; font-size: 1rem; font-weight: 600;
  letter-spacing: -.01em; color: var(--ink);
}
.ai-report h2:first-child { margin-top: 0; }
.ai-report strong, .ai-report b { color: var(--ink); font-weight: 600; }
.ai-report ul, .ai-report ol { margin: .6rem 0; padding-left: 1.35rem; }
.ai-report li { margin: .3rem 0; }

/* Quiet, not blue. A pull-quote in the accent competed with the prose for
   attention on every single one, and a report is full of them. */
.ai-report blockquote {
  margin: 1rem 0; padding: .85rem 1.1rem;
  border-left: 2px solid var(--line-strong);
  background: var(--t-surface);
  border-radius: 0 10px 10px 0;
  font-size: .95rem; color: var(--t-ink-2);
}
.ai-report blockquote p { margin: 0; }

/* Tables carry the math, so they get hairlines rather than a drawn grid, and
   tabular figures so columns of numbers line up. */
.ai-report table {
  margin: 1.1rem 0; font-size: .92rem; width: 100%;
  font-variant-numeric: tabular-nums;
}
.ai-report th, .ai-report td {
  border: 0; border-bottom: 1px solid var(--line);
  padding: .5rem .7rem; text-align: left; vertical-align: top;
}
.ai-report th {
  background: none; color: var(--t-faint); font-weight: 600;
  font-size: .72rem; letter-spacing: .08em; text-transform: uppercase;
  border-bottom-color: var(--line-strong);
}
.ai-report tr:last-child td { border-bottom: 0; }

.ai-report pre {
  background: var(--t-surface); border: 1px solid var(--line);
  border-radius: 10px; padding: .9rem 1rem; font-size: .82rem; line-height: 1.6;
}
.ai-report code { font-size: .86em; }
.ai-report p code, .ai-report li code, .ai-report td code {
  background: var(--t-surface-2); border-radius: 4px; padding: .1em .35em;
}
.ai-report pre code { background: none; padding: 0; }
.ai-report hr { border: 0; border-top: 1px solid var(--line); margin: 2rem 0; }

/* ============================================================================
   THE TEAM GRID

   Information-dense and doing its job, so this is a lightening pass rather
   than a redesign. The card ground stays in the same value range on purpose:
   the zero-investment stats are hand-blended against it (see the note in
   style.css about opacity breaking subpixel antialiasing), and moving the
   background far would invalidate that blend.
   ========================================================================= */
.team-grid { gap: .75rem; margin: 1.25rem 0 .5rem; }
.tm-card {
  background: var(--t-surface);       /* #f7f8fa, within a hair of the old #f7f9fc */
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: .85rem;
  gap: .55rem;
}
.tm-title b { font-size: .93rem; font-weight: 600; letter-spacing: -.01em; }
.tm-item, .tm-ab { font-size: .76rem; }
.tm-evs-cap { font-size: .66rem; letter-spacing: .09em; color: var(--t-faint); }

/* The stat boxes were each a bordered chip, so six of them per card read as a
   grid of buttons. Ground and figure instead: no borders, the value in ink. */
.tm-stats { gap: 2px 5px; }
.tm-stats > div {
  border: 0; background: #fff; border-radius: 6px; padding: .18rem .4rem;
  font-size: .72rem; font-variant-numeric: tabular-nums;
}
.tm-stats > div.z { background: #fbfcfd; }

/* Move chips were accent blue, which made four of them per card the loudest
   thing in the grid. They are labels, not links. */
.tm-moves { gap: 3px; }
.tm-moves li {
  font-size: .68rem; background: var(--t-surface-2); color: var(--t-ink-2);
  border-radius: 5px; padding: .15rem .4rem;
}

/* ============================================================================
   1. THE SPEND-A-CREDIT DIALOG

   The highest-stakes screen on the site: it is where someone commits to
   paying. It reuses .welcome-overlay / .welcome-card, so both move together.

   The scrim and the card shadow were tinted blue (rgba(31,53,73,...)) from the
   old palette, which read as a wash rather than as depth. Both are neutral ink
   now, and the scrim is darker: a modal is the one place the page SHOULD be
   pushed back, because the whole point is that nothing else is available.
   ========================================================================= */
.welcome-overlay {
  background: rgba(13, 18, 20, .55);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  padding: 1.5rem;
}
.welcome-card {
  border-radius: 16px;
  box-shadow: 0 24px 60px -12px rgba(13, 18, 20, .35), 0 2px 8px rgba(13, 18, 20, .12);
  padding: 1.75rem 1.75rem 1.4rem;
}
.confirm-card { max-width: 30rem; }
.confirm-title {
  margin: 0 0 1.15rem; font-size: 1.3rem; font-weight: 600;
  letter-spacing: -.02em; line-height: 1.25; color: var(--ink);
}
.confirm-points { gap: .95rem; margin-bottom: 1.4rem; }
.confirm-points li {
  gap: .75rem; font-size: .92rem; line-height: 1.55; color: var(--t-ink-2);
}
.confirm-points b { color: var(--ink); font-weight: 600; }
.confirm-ico { width: 22px; font-size: 1rem; }
.confirm-actions { gap: .6rem; }
/* The refund point-of-no-return, stated where the Terms say it is stated.
   Below the buttons so it does not compete with the decision, but inside the
   dialog so it cannot be missed. */
.confirm-fine {
  margin: .9rem 0 0; font-size: .76rem; line-height: 1.55; color: var(--muted);
}
.confirm-fine a { color: var(--cta); text-decoration: underline; }
/* This is the last thing between a visitor and a credit, so both answers are
   set larger and heavier than an ordinary button: whichever one they mean,
   they should be able to hit it without reading twice. */
.confirm-actions button, .confirm-actions button.primary {
  font-size: 1.06rem; font-weight: 650; letter-spacing: -.006em;
  min-height: 50px; padding-inline: 1.1rem;
}
/* The dismiss is still the quiet one. On a payment confirmation the safe
   choice should never be harder to find than the committing one, but it
   should not compete with it either, so it matches in size and steps back in
   colour rather than in weight. */
.confirm-actions button:not(.primary) {
  background: var(--card); border: 1px solid var(--line-strong); color: var(--ink);
  border-radius: 9px;
}
.confirm-actions button:not(.primary):hover { background: var(--t-surface); }

/* ============================================================================
   2. THE TEAM CHECK VERDICT

   Status genuinely earns colour, so the colour stays. What changes is the
   weight: three saturated pastel slabs with matching borders were shouting a
   result that is usually just "fine". Light tint, hairline, and a dot carrying
   the state so the text can sit at a readable size instead of bold-centred.

   PASS / REVIEW / FAIL survive here only as class names. The words a person
   reads are set in the markup, per the voice rules.
   ========================================================================= */
.verdict {
  display: flex; align-items: center; gap: .6rem;
  margin-top: 1rem; padding: .75rem 1rem;
  border-radius: 11px; border: 1px solid;
  font-weight: 550; font-size: .92rem; text-align: left; line-height: 1.45;
}
.verdict::before {
  content: ""; flex: none; width: 8px; height: 8px;
  border-radius: 50%; background: currentColor;
}
.verdict.PASS   { background: rgba(39, 163, 94, .07);  color: #1d7a46; border-color: rgba(39, 163, 94, .24); }
.verdict.REVIEW { background: rgba(217, 154, 6, .08);  color: #8a6206; border-color: rgba(217, 154, 6, .26); }
.verdict.FAIL   { background: rgba(228, 96, 109, .07); color: #b23a47; border-color: rgba(228, 96, 109, .26); }

/* The checklist had every row in its own bordered box, so twelve results read
   as twelve cards. Hairline-separated rows instead: one list, scannable down
   the status column. */
.checklist { gap: 0; margin-top: 1.1rem; border-top: 1px solid var(--line); }
.checklist li {
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
  background: none; padding: .7rem .15rem;
  grid-template-columns: 4.6rem 1fr; gap: .75rem;
  font-size: .87rem; line-height: 1.5;
}
.checklist .status {
  font-size: .68rem; font-weight: 600; letter-spacing: .075em;
  text-transform: uppercase; padding-top: .18rem;
}
.checklist .detail { color: var(--muted); margin-top: .1rem; }

/* ============================================================================
   THE ACCOUNT UI

   ⚠ account.js injects its own <style id="acct-style"> into document.head at
   RUNTIME, which lands after these stylesheets and therefore wins any tie on
   specificity. Every menu rule below is scoped under .topbar to sit at (0,2,x)
   and beat it outright, rather than relying on source order it would lose.
   ========================================================================= */
.acct-chip { gap: .6rem; }
.acct-credits {
  background: var(--t-surface-2);
  border-color: transparent;
  color: var(--ink);
  font-size: .8rem; font-weight: 600;
  padding: .32rem .7rem;
  font-variant-numeric: tabular-nums;
}
/* The name links to the account page (2026-10-11). Plain text went
   unnoticed, so the owner picked, from four mockups, a button the same shape
   and height as Buy and Sign out beside it, with the name in blue and a down
   arrow. --accent-dark on white is 5.8:1. line-height: normal because a link
   inherits the body's taller line-height and would stand 3px above the
   buttons. The NAME truncates, not the button, which is why style.css's
   max-width and overflow are undone here. */
.acct-name {
  display: inline-flex; align-items: center; gap: .35rem;
  max-width: none; overflow: visible; line-height: normal;
  padding: .42rem .65rem .42rem .8rem;
  border: 1px solid var(--line-strong); border-radius: 9px; background: var(--card);
  font-size: .82rem; font-weight: 600; letter-spacing: -.006em;
  color: var(--accent-dark); text-decoration: none;
  transition: background .18s var(--t-ease), border-color .18s var(--t-ease);
}
.acct-name-text { max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-chev, .acct-person { flex: none; }
.acct-person { display: none; }
.acct-name:hover { background: var(--t-surface); border-color: var(--accent); }
.acct-name:focus-visible { outline: 2px solid var(--accent-dark); outline-offset: 2px; }
.acct-name[aria-current="page"] { background: var(--t-surface-2); border-color: var(--accent-dark); }
/* Phones. Credits + Buy + the name + Sign out share one row, and a long name
   ran 66px past a 375px screen (measured), scrolling the page sideways. So
   the name is cut at about nine letters, which fits every name down to a
   360px phone. Below that even a short name does not fit (320px measured 21px
   over with "Dev · Rob"), so a person icon stands in for it: that rule lives
   in the one 359px block further down, beside the nav's. The full name is
   still spoken: the link's aria-label carries it. */
@media (max-width: 480px) {
  .acct-name { padding-left: .65rem; padding-right: .55rem; }
  .acct-name-text { max-width: 72px; }
}
.acct-btn {
  font-size: .82rem; font-weight: 550; letter-spacing: -.006em;
  border: 1px solid var(--line-strong); border-radius: 9px;
  padding: .42rem .8rem;
  transition: background .18s var(--t-ease), border-color .18s var(--t-ease);
}
.acct-btn:hover { background: var(--t-surface); filter: none; }
.acct-btn.primary { border-color: transparent; }
.acct-btn.primary:hover { background: var(--cta-hover); filter: none; }

/* Scoped to beat the runtime-injected block. */
.topbar .acct-menu {
  border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 18px 44px -10px rgba(13, 18, 20, .22), 0 2px 6px rgba(13, 18, 20, .08);
  padding: .35rem; min-width: 15rem;
}
.topbar .acct-menu button, .topbar .acct-menu a {
  border-radius: 8px; padding: .5rem .65rem; font-size: .88rem;
}
.topbar .acct-menu button:hover, .topbar .acct-menu a:hover {
  background: var(--t-surface-2);          /* not an accent wash: this is a list, not a selection */
}
.topbar .acct-menu .acct-menu-head {
  font-size: .68rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--t-faint); opacity: 1; padding: .5rem .65rem .3rem;
}
.topbar .acct-menu .buy-save {
  color: #1d7a46; background: rgba(39, 163, 94, .12); font-weight: 600;
}
.topbar .acct-menu .buy-per { color: var(--muted); opacity: 1; font-size: .74rem; }
/* Point-of-sale fine print: currency + refund policy, visible before payment.
   Quiet, but it has to stay READABLE - this is the disclosure, not decoration,
   so it takes --muted (4.5:1) rather than --t-faint, and the link is underlined
   because colour alone is not an accessible way to mark one. */
.topbar .acct-menu .buy-fine {
  margin: .3rem .1rem 0; padding: .45rem .55rem 0;
  border-top: 1px solid var(--line);
  /* .76rem, a step ABOVE the .74rem per-build price beside it: the terms of
     the sale should never be the smallest thing in the menu. */
  color: var(--muted); font-size: .76rem; line-height: 1.45;
}
.topbar .acct-menu .buy-fine a {
  display: inline; padding: 0; color: var(--cta);
  text-decoration: underline; font-size: inherit;
}
.topbar .acct-menu .buy-fine a:hover { background: none; }

/* The account cluster is built by JS once /api/bootstrap answers, so unlike the
   regulation chip it cannot simply be rendered into the served HTML: what it
   says depends on who is signed in.

   At most widths nothing already on screen moves when it lands - brand, nav
   and the reg chip measure 0px of movement, because #topbar-right is
   margin-left:auto and simply extends leftward into empty space.
   ⚠️ CORRECTED 2026-10-04: not at every width. Between ~963px and ~1061px the
   Sign in button is what wraps the row, the bar grows 74px -> 136px and the
   whole page drops 62px (a 0.089 layout shift, measured). So the server now
   ships the SIGNED-OUT shape up front, invisible (.acct-pending, see
   _ACCT_PLACEHOLDER in server.py). Signed-in space is still NOT reserved, and
   the old reasoning holds for it: 230px signed in, 341px with a long name, and
   holding that open would leave a hole in every signed-out visitor's header.

   The fade below still matters: the chip appears rather than snaps.
   Opacity only, no movement - this is chrome, and a header that slides on load
   draws exactly the attention it should not. */
.acct-chip.acct-arrive { animation: acct-arrive .3s var(--t-ease) both; }
@keyframes acct-arrive { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .acct-chip.acct-arrive { animation: none !important; opacity: 1 !important; }
}

/* Doubled class, because the toast is fixed to the body and .topbar is not an
   ancestor, so there is nothing to scope under. */
.acct-toast.acct-toast {
  /* Same family as the console rather than pure neutral: with everything else
     tinted toward the accent, a flat #0d1214 toast reads as a stray. */
  background: var(--surface-dark); color: #fff;
  border-radius: 10px; padding: .7rem 1.1rem;
  font-size: .88rem; font-weight: 500;
  box-shadow: 0 12px 32px -8px rgba(13, 18, 20, .4);
}

/* ============================================================================
   THE BAD-DAY STATES

   Build failed, out of credits, a paused site, a lost connection. Most of these
   already land in the build console and inherit .ai-ev.err. These are the ones
   that surface elsewhere.

   The principle: a problem should read as calm and specific, not alarming. The
   person seeing it is already frustrated, and a red slab does not help them.
   ========================================================================= */

/* .notes is the shared notice block. Carrying role="alert" means it is a real
   failure rather than an aside, so it earns the fail colour: the Analysis
   parse error is the one that uses it. */
.notes[role="alert"] {
  background: rgba(228, 96, 109, .06);
  border-color: rgba(228, 96, 109, .26);
  color: #8f2f3a;
}
.notes[role="alert"] b, .notes[role="alert"] strong { color: #7d2932; }

/* Feedback form result. Was bold 13px in flat pass/fail colour with nothing to
   sit in, so it read as an afterthought under a form someone just committed to. */
.fb-status {
  margin-top: .9rem; padding: .7rem .95rem; border-radius: 10px;
  font-size: .88rem; font-weight: 500; line-height: 1.5;
}
.fb-status.ok  { background: rgba(39, 163, 94, .08);  color: #1d7a46; }
.fb-status.err { background: rgba(228, 96, 109, .07); color: #8f2f3a; }

.empty-slot-msg { font-size: .85rem; font-weight: 500; color: var(--muted); }

/* ============================================================================
   MOBILE HEADER ORDER  (fixes a pre-existing bug, not one this redesign made)

   style.css states the intent in a comment: "drop the nav to its own
   full-width row beneath the brand + reg chip". It implements that with
   .brand{order:1} and .regchip{order:2}, which works only while the reg chip
   is a direct child of .topbar-inner.

   It stops being one. account.js runs later and MOVES the chip into a new
   .topbar-right wrapper, which has no order of its own and so defaults to 0,
   ahead of the brand at 1. The result on a phone is four stacked rows in the
   wrong order: Sign in, then the reg chip, then the logo, then the nav, with
   the header eating 204px before any content.

   So the fix belongs on the wrapper the JS creates. Everything the old rule
   said about the chip now has to be said about its container. */
@media (max-width: 940px) {
  .topbar-inner .topbar-right {
    order: 2;
    margin-left: auto;
    flex: 0 1 auto;
    flex-wrap: nowrap;
    gap: .6rem;
  }
  /* Belt and braces: if the chip is ever a direct child again (account.js not
     loaded, or a page without it), it still lands in the same place. */
  .topbar-inner > .regchip { order: 2; }
}
/* The reg chip's two forms (owner call 2026-10-12). The server writes both
   (server.py, _regchip_html); this decides which shows. Where the full
   "Regulation M-C · Pokémon Champions" would not fit, the chip shrinks to
   "M-C" instead of disappearing, which it used to do at 480px and below. It
   also fixes a sideways scroll: from 481px to about 600px a signed-in bar
   (credits, Buy, name, Sign out, then the full chip) ran up to 144px past the
   screen, because the chip was only hidden at 480px and below. */
.regchip .reg-short { display: none; }
@media (max-width: 720px) {
  .regchip .reg-full { display: none; }
  .regchip .reg-short { display: inline; }
  .regchip { padding: 5px 10px; font-size: 13px; }
}
/* Below 414px even "M-C" has no room, so the chip hides there, as it always
   did on phones. Measured: signed in with Buy and a long name the row is
   387px wide (6px past a 400px screen); signed out at 375px it pushes Sign in
   onto a row of its own (+45px of header). Letting the name shrink to make
   room was tried and rejected: it left 5px of name at 360px.
   Both selectors on purpose: account.js reparents the chip into .topbar-right
   only when the credit gate is on, so without this the header is one row
   taller in local dev than it is in production. */
@media (max-width: 413px) {
  .topbar-right .regchip,
  .topbar-inner > .regchip { display: none; }
}

/* "Build Library" in the nav (owner ask 2026-10-12), "Library" where it would
   not fit. Its own class, not .nav-long, because its breakpoint is wider: on
   481-940px the nav sits on its own full-width row and the full names already
   end at 461px of a 481px screen, so "Build " (about 40px) waits for 561px,
   where the row was measured to hold all four full names. On desktop it moves
   the signed-in header's wrap to two rows from about 1340px to about 1400px;
   signed out it stays one row at 1280 and 1366 (measured 2026-10-12).
   aria-label keeps "Build Library" for screen readers. */
@media (max-width: 560px) {
  .mainnav .nav-long-lib { display: none; }
}

/* ============================================================================
   PHONE PASS: the wizard, the build console, the report

   Everything below came out of walking the real flow at 375px and measuring,
   not from eyeballing a breakpoint. Each block says what it was.
   ========================================================================= */

@media (max-width: 480px) {
  /* The three nav links overflow the 335px row at their desktop padding. Now
     339.1px at the larger 15px label, so the trim goes to .35rem, which buys
     13.5px and lands them at ~326px. Padding is the cheaper thing to spend
     here than legibility: this is the site's only navigation. */
  .mainnav a { padding-left: .35rem; padding-right: .35rem; }
  /* Four tabs since the Build Library (2026-10-08): at their full names they
     end at 405px on a 375px phone and the whole page scrolls sideways. On a
     phone the two long labels drop their first word ("Builder", "Analysis");
     each link keeps its full name in aria-label, so a screen reader still
     hears "Manual Builder". Measured: the row then ends inside 335px. */
  .mainnav .nav-long { display: none; }

  /* iOS Safari zooms the whole page in when you focus a control whose text is
     under 16px, and does not zoom back out. The Pokémon search box was 13.3px,
     so the first tap of the wizard's main interaction threw the layout. This is
     the one place a magic number is the fix rather than a smell.

     The list repeats the field selectors from the form blocks above on purpose:
     a media query adds no specificity, so a bare `input` here loses to the
     `.picker input[type="text"]` and `.panel select` rules this same file
     already sets. Adding a field elsewhere means adding it here too. */
  input, textarea, select,
  .picker input[type="text"], #freeform,
  .lab-field textarea, .lab-field input[type="email"],
  #fb-message, #fb-email,
  #paste, #ana-paste, .panel textarea,
  .panel input[type="text"], .panel select { font-size: 16px; }

  /* 34px is under the finger. The nemesis chips are the only targets on the
     "anything to avoid?" step, so they get the full 44px. */
  .nemarch, .chips button, .chip { min-height: 44px; }

  /* Back / Skip / Continue and friends: 40px was a mouse target. */
  .wiz-nav button, .btnrow button, .wiz-card { min-height: 44px; }

  /* Side by side at 170px each, "✨ Build this team" wrapped to three lines and
     stretched "Start over" to match. The primary action gets its own row.
     Stacking the container, not widening the children: this row is `nowrap`,
     so a 100% flex-basis just pushes "Start over" off the right edge. */
  .wiz-run-btns { flex-direction: column; align-items: stretch; }

  /* Buys back 11px of measure per side for the report's prose. */
  .ai-report { padding: 1.15rem; }
}

/* ---------------------------------------------------------------------------
   THE LAST 24 PIXELS: tabs that genuinely do not fit

   The block above trims nav padding to .35rem, which lands the three tabs at
   325px and fits them on every mainstream phone. It does not fit the smallest:
   measured on the real page, the tabs end at a fixed 345px (they sit at their
   min-content width once the row is tighter than they are), so a viewport
   under 345px cuts "Team Analysis" off - 25px of it at 320px - and because the
   940px block trades the base overflow-x:auto for visible, it cannot even be
   swiped into view. The page scrolls sideways instead.

   Owner call 2026-09-14: shrink the labels, and ONLY where they would actually
   be cut off. So this is deliberately not folded into the 480px block above -
   that would shrink type on 360/375/390/412px phones that have room to spare.

   Why 359 and not the measured 345: right above the real threshold the
   clearance is 0-5px, which is inside the variation between one device's font
   and another's (SF on iOS, Roboto on Android, Segoe on Windows). 359px is the
   first width below the narrowest common phone, so the cushion costs nothing:
   no mainstream device sits between 345 and 359 to be shrunk needlessly.

   Measured at 320px: 15px label ends 25px PAST the edge. 13px alone still ends
   2px past it, which is why the padding moves too - and padding is the right
   thing to spend first, per the note in the block above. Together they clear by
   7px at 320, and by 20px at both 344 and 359.

   No !important anywhere: this sits after `.mainnav a { font-size: 15px }` in
   the same file at equal specificity, so source order decides it.
   --------------------------------------------------------------------------- */
@media (max-width: 359px) {
  .mainnav a {
    font-size: 13px;
    letter-spacing: .2px;
    padding-left: .25rem;
    padding-right: .25rem;
  }
  /* The account button: a person icon stands in for the name (see the
     .acct-name rules above for the measurement). */
  .acct-name-text { display: none; }
  .acct-person { display: block; }
}

/* The report card carries `overflow-x: auto`, so a wide table used to scroll
   the ENTIRE report sideways: the reader dragged the prose off screen to read
   one row of numbers, and dragging it back was on them. Hand the scroll to the
   table instead, so the document stays put and only the table moves.
   max-content + max-width lets a narrow table stay its natural size. */
@media (max-width: 720px) {
  .ai-report { overflow-x: visible; }
  .ai-report table {
    display: block;
    width: max-content;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* A Showdown paste is `white-space: pre` and its longest line is wider than a
   phone. Same treatment, at every width: the block scrolls, the page does not. */
.ai-report pre { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* Two columns of 152px clipped the content that matters most. Measured on a
   real report: "Hatterene" needed 62px in a 54px title box, and the SP spread
   needed 141px in a 126px one, so a name and a spread were both cut off. The
   six ARE the deliverable, so below 560px they get the full width each. */
@media (max-width: 560px) {
  .team-grid { grid-template-columns: 1fr; }
}

/* A type list is one unbreakable token to the line breaker, so
   "Electric/Flying/Psychic/Bug/Rock/Normal," measured 287px inside a 262px
   list item and ran past the edge. break-word (not anywhere) only takes effect
   on a word that genuinely does not fit, and leaves min-content sizing alone,
   so ordinary prose and table columns are untouched. */
.ai-report p, .ai-report li, .ai-report td, .ai-report th {
  overflow-wrap: break-word;
}

/* A four-column table is a desktop object. On a phone the threat table put a
   171-character game plan into a 149px column, which wrapped to nine lines and
   left the other three columns 300px of white space each; the alternative,
   letting it keep its natural width, is two screens of sideways dragging to
   read one row. So below 560px it stops pretending to be a table: each row
   becomes a labelled block, full measure, no horizontal scroll at all.

   The headings move into a data-label on every cell (see flushTable in
   wizard.js) because CSS cannot reach across the table to read them. The
   thead is clipped rather than removed, so a screen reader still gets it.

   This block must stay AFTER the 720px one above: same specificity, and it
   exists to undo it. */
@media (max-width: 560px) {
  .ai-report table {
    display: block; width: auto; max-width: none; overflow-x: visible;
    margin: 1.25rem 0;
  }
  .ai-report table thead {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .ai-report table tbody { display: block; }
  .ai-report table tr {
    display: block; padding: .85rem 0;
    border-bottom: 1px solid var(--line);
  }
  .ai-report table tr:last-child { border-bottom: 0; }
  .ai-report table td { display: block; border: 0; padding: 0 0 .5rem; }
  .ai-report table td:last-child { padding-bottom: 0; }
  .ai-report table td::before {
    content: attr(data-label);
    display: block;
    font-size: .7rem; letter-spacing: .08em; text-transform: uppercase;
    color: var(--t-faint); margin-bottom: .1rem;
  }
  /* A cell with no heading (a ragged table) shouldn't leave a blank line. */
  .ai-report table td:not([data-label])::before { display: none; }
  .ai-report table td:first-child { font-weight: 600; color: var(--ink); }
}

/* The headline is written as two lines: the statement, then the accent half.
   At the desktop size the accent line is 319px of text in a 343px phone
   column, which is close enough that "actually want to play" broke to two
   lines and orphaned "actually" on its own. Measured at 375px: 38px is the
   largest size where BOTH halves still fit on one line each (290px and
   319px), so the phone keeps the intended shape instead of a four-line
   stack. 10.1vw holds that ratio down to 320px. */
@media (max-width: 480px) {
  .lab-display { font-size: clamp(2rem, 10.1vw, 2.5rem); }
}

/* ============================================================================
   THE FEEDBACK FORM OPENS ON A CHOICE

   Eight controls (note, email, the bot check, the reward line, the button)
   sat open at the foot of the landing page for every visitor. Now the two
   kinds are the whole section until one is picked.

   Same 1fr/0fr grid collapse as the hero, and the same requirement: exactly
   one child row, .fb-rest-in, or there is nothing for the browser to animate
   between. visibility does the other half of the job, keeping the collapsed
   controls out of the tab order rather than merely out of sight.
   ========================================================================= */
/* max-height, not the 1fr/0fr grid collapse used for the hero. That trick
   needs the fr track to resolve against the row's max-content, and inside this
   form it resolved to the padding alone (13.5px against 411px of content), so
   the section opened to a sliver. Measured max-height is the boring mechanism
   that works, and theme-phase2.js clears it to `none` once the transition ends
   so the status line and the bot check can appear later without being clipped. */
.fb-rest {
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  visibility: hidden;
  transition: max-height .4s var(--t-ease), opacity .26s var(--t-ease),
              visibility 0s linear .4s;
}
.fb-rest.open {
  opacity: 1;
  visibility: visible;
  transition: max-height .4s var(--t-ease), opacity .26s var(--t-ease),
              visibility 0s;
}
.fb-rest-in { padding-top: .9rem; }

/* The choice is now the section's one job, so it gets room to be one. */
.fb-kinds { margin-bottom: .25rem; }

@media (prefers-reduced-motion: reduce) {
  .fb-rest { transition: none; }
}

/* The Theorizer band needs space under it.

   Every other section on this page is separated by the PRECEDING one's
   padding-bottom, which works because they share the page background. The
   band does not: it is a filled surface, so its 41px of bottom padding sits
   INSIDE it and leaves nothing between its edge and the next heading.
   Measured at 0px, which is why "Fresh from the Lab" read as attached to the
   band once the wizard was past its first step.

   On the band, not on the section below it. The launch banner sits between
   the two in the DOM, so `.lab-flow + .lab-section` matches nothing; and the
   banner is display:none in the flow state, hidden for signed-in visitors and
   absent entirely when the credit gate is off, so any rule that assumes it is
   there gets the spacing wrong in three different states. A margin on the
   band is true in all of them, and collapses against the banner's own top
   margin when the banner IS showing rather than stacking with it. */
.lab-flow { margin-bottom: clamp(1.6rem, 3.4vw, 2.6rem); }

/* Used for a single frame while theme-phase2.js measures where the hero
   collapse is going to leave the wizard. Everything has to be still for that
   measurement, including the transitions declared in style.css. */
.no-anim, .no-anim *, .no-anim *::before, .no-anim *::after {
  transition: none !important;
  animation: none !important;
}

/* ---- the build button -----------------------------------------------------
   The ✨ emoji is drawn by the OS in its own colours, so it stayed yellow on a
   blue button no matter what the CSS said. The same vector mark the nav and
   the step headings use takes currentColor instead, which on this button is
   white. Sized in em so it tracks the label rather than drifting if the label
   size changes again.

   1.25em rather than matching the emoji's 1.4em: an emoji glyph carries its
   own internal padding, so the visible mark inside it is smaller than its
   font-size suggests. A vector at the same nominal size reads noticeably
   bigger, which is the direction asked for. */
.wiz-run-btns button { font-size: 1rem; }
.btn-spark {
  display: inline-flex; align-items: center;
  width: 1.25em; height: 1.25em; margin-right: .42em;
  color: currentColor;
  vertical-align: -.22em;
  flex: none;
}
.btn-spark svg { width: 100%; height: 100%; display: block; }
.btn-label:has(.btn-spark) { display: inline-flex; align-items: center; }

/* The "search any legal Pokémon" line under the twelve. Tighter above than a
   normal note, because it belongs to the picker under it rather than to the
   grid above it. */
.avoid-note.nem-any { margin-top: 1rem; margin-bottom: .55rem; }

/* ---- the Theorizer's name on its own band --------------------------------
   Its own row above the progress bar, not floated into a corner. On the left
   the bar starts at the same x, so a positioned badge would sit on top of the
   first segments; and a row needs no separate rule for phones, where a corner
   badge would have to move anyway. */
.flow-tag {
  display: inline-flex; align-items: center;
  margin-bottom: 1.1rem;
  padding: .34em .72em;
  border-radius: 999px;
  background: rgba(27, 157, 226, .1);
  color: var(--accent-dark);
  font-size: .72rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; line-height: 1;
}

/* The proof card's crossfade between builds. Opacity only, no movement: this
   sits directly beside the headline, and anything that slides pulls the eye
   off the thing the page is actually saying. The sprites, the quote and the
   byline fade together so the card never shows one build's art over another's
   caption. */
.lab-proof .proof-six,
.lab-proof .proof-ask,
.lab-proof .proof-meta { transition: opacity .26s var(--t-ease); }
.lab-proof.swapping .proof-six,
.lab-proof.swapping .proof-ask,
.lab-proof.swapping .proof-meta { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .lab-proof .proof-six,
  .lab-proof .proof-ask,
  .lab-proof .proof-meta { transition: none; }
}

/* The proof card is a link to the gallery now, so it has to look like one.
   Kept quiet: it sits beside the headline, and a card that lifts and shadows
   on hover would compete with the two entry cards, which are the actual
   decision on this page. */
.lab-proof.is-link { cursor: pointer; transition: border-color .18s var(--t-ease); }
.lab-proof.is-link:hover { border-color: var(--line-strong); }
.lab-proof.is-link .proof-meta { text-decoration-color: transparent; }
.lab-proof.is-link:hover .proof-meta { color: var(--accent-dark); }
@media (prefers-reduced-motion: reduce) { .lab-proof.is-link { transition: none; } }

/* ============================================================================
   NARROW-SCREEN REPAIRS  ·  /analysis

   Three separate defects, all invisible above 560px and all found by measuring
   a 390px viewport rather than by looking at it. Grouped here because they are
   one story: this page's data density was never given a phone treatment, and
   the redesign's larger type made the worst of them worse.
   ========================================================================= */

/* ---- 1. the page scrolled sideways ----------------------------------------
   scrollWidth 452 against a 390px viewport. Not the threat table, which is
   correctly wrapped in .tablewrap and scrolls inside itself. It was the three
   solver <select>s: a select sizes to its widest option and, as a grid item
   with the default min-width:auto, refuses to shrink below it. "Garchomp ·
   Adamant Earthquake @ Life Orb" made them 417px wide inside a 358px column.

   Pre-existing, but this redesign took .speed-solver select from 13px to 15px
   (16px on mobile, to stop iOS zooming the field on focus), and a bigger font
   means a wider longest option, so we widened the overflow we inherited. */
.speed-solver label { min-width: 0; }
.speed-solver select { width: 100%; min-width: 0; max-width: 100%; }

/* ---- 2. the remove button sat on top of the species name -------------------
   Measured 15-34px of overlap on all six chips at 390px ("Hatte✕ne").

   The chip reserves a 26px right gutter for the absolutely-positioned ✕, so
   the padding was never the problem. The 66px sprite was. At two-up on a
   390px screen the chip is 155px, which leaves .chip-info 45px of room for a
   name that wants 74px, and a single word cannot wrap: it just overflows, out
   past the chip's own edge and under the button.

   Stacking the chip gives the name the full 121px content width instead of
   the 45px left over beside the sprite. overflow-wrap is the backstop for a
   forme name longer than anything currently in the dex. */
@media (max-width: 560px) {
  .team-chip {
    flex-direction: column; align-items: center; text-align: center;
    gap: 4px; padding: 10px 8px 8px;
  }
  .chip-sprite { width: 52px; height: 52px; }
  .chip-info { min-width: 0; max-width: 100%; }
  .team-chip b { overflow-wrap: anywhere; }
  /* Back to the corner of a taller box, and away from the centred name. */
  .chip-remove { top: 4px; right: 4px; }
}

/* ---- 3. the threat table's payoff columns were off-screen ------------------
   The table scrolls inside .tablewrap, which is correct and, on a phone,
   useless: the two columns worth reading, the damage roll and your
   super-effective answer, are the two that start past the right edge, behind a
   horizontal scroll nobody goes looking for. Rows were also ~100px tall of
   mostly whitespace, so the page paid full height for content it hid.

   Same card-stacking treatment as .ai-report table above: one block per
   threat, headings moved into data-label on each cell (added in analysis.js,
   because CSS cannot reach across a table to read its thead). The thead is
   clipped rather than removed so a screen reader still gets it.

   .tablewrap has to stop being a scroll container here, or the stacked blocks
   would sit inside a scroller that no longer has anything to scroll. */
@media (max-width: 560px) {
  .tablewrap { overflow-x: visible; }
  .threats { display: block; width: auto; }
  .threats thead {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .threats tbody { display: block; }
  .threats tr {
    display: block; padding: .9rem 0;
    border-bottom: 1px solid var(--line);
  }
  .threats tr:last-child { border-bottom: 0; }
  .threats td { display: block; border: 0; padding: 0 0 .55rem; }
  .threats td:last-child { padding-bottom: 0; }
  .threats td::before {
    content: attr(data-label);
    display: block;
    font-size: .7rem; letter-spacing: .08em; text-transform: uppercase;
    color: var(--t-faint); margin-bottom: .15rem;
  }
  .threats td:not([data-label])::before { display: none; }

  /* The rank and the name are the block's title, so they ride together on one
     line instead of becoming two labelled fields of their own. */
  .threats td.rank {
    display: inline-block; padding: 0 .5rem .55rem 0;
    font-size: 1.05rem; vertical-align: middle;
  }
  .threats td.rank::before { display: none; }
  .threats td.tname {
    display: inline-flex; align-items: center; gap: .35rem;
    padding-bottom: .55rem; vertical-align: middle;
    font-size: 1.05rem; font-weight: 600;
  }
  .threats td.tname::before { display: none; }
  /* Speed is one number: it reads as a value beside its label, not under it. */
  .threats td.num { display: flex; align-items: baseline; gap: .4rem; }
  .threats td.num::before { margin-bottom: 0; }
  /* The all-clear row is a sentence, not a field. */
  .threats td.allclear { padding: .5rem 0; }
}

/* ===========================================================================
   THE ARRIVAL · a finished build lands
   ---------------------------------------------------------------------------
   Until now the end of a paid build was `box.hidden = false`: the progress bar
   reached 100% and a heading was simply there, after minutes of waiting. The
   launch banner, which announces a FREE credit, had four keyframe animations.
   The thing someone paid for had none.

   Three moves, all cheap and all on transform/opacity so they stay on the
   compositor: the result rises into place, the six deal in one card at a time,
   and one light sweep crosses the team. The sweep is the same gesture the
   launch banner already makes, so it reads as a house move rather than a new
   vocabulary.

   Scoped to .ba-hero (the heading + the six) on purpose. #ai-result also holds
   the full report, which runs to thousands of pixels, and a light bar crossing
   THAT is a bar sliding down a page, not a team arriving.

   Fired only by playArrival() in wizard.js, which is only reached on a genuine
   fresh completion. Opening a saved build does not replay it.
   ======================================================================== */

.ba-hero { position: relative; }

/* Was an inline style="margin-top:18px" on the h2. Kept in px, not converted:
   style.css moves :root font-size to 16px in one media block, so the same rem
   value would not be the same 18px everywhere it used to be. */
.ba-title { margin-top: 18px; }

#ai-result.ba-rise { animation: ba-rise .5s var(--t-ease) both; }
@keyframes ba-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* A brief lift, not a permanent glow: it peaks at 35% and is gone by the end. */
.ba-title.ba-glow { animation: ba-glow 1s ease-out .2s both; }
@keyframes ba-glow {
  0%, 100% { text-shadow: none; }
  35%      { text-shadow: 0 0 22px rgba(27, 157, 226, .45); }
}

/* Per-card delays are set in JS: the count is the team's, not a constant, so
   nth-child rules would silently stop animating past whatever was hardcoded. */
.ba-cascade .tm-card { animation: ba-pop .5s cubic-bezier(.2, 1.5, .45, 1) both; }
@keyframes ba-pop {
  from { opacity: 0; transform: translateY(14px) scale(.9); }
  to   { opacity: 1; transform: none; }
}

.ba-sheen {
  position: absolute; inset: 0; overflow: hidden;
  pointer-events: none; border-radius: 12px;
}
.ba-sheen i {
  position: absolute; top: 0; bottom: 0; width: 42%;
  background: linear-gradient(100deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(27, 157, 226, .13) 45%,
    rgba(255, 255, 255, .7) 55%,
    rgba(255, 255, 255, 0) 100%);
  transform: translateX(-140%);
  animation: ba-sweep 1.05s var(--t-ease) .18s both;
}
@keyframes ba-sweep {
  from { transform: translateX(-140%); }
  to   { transform: translateX(340%); }
}

/* Reduced motion gets the settled pose, not a missing one. playArrival() also
   bails before appending the sweep, so this is the belt to that pair of braces:
   it still has to be here for anyone who turns the setting on mid-animation. */
@media (prefers-reduced-motion: reduce) {
  #ai-result.ba-rise,
  .ba-title.ba-glow,
  .ba-cascade .tm-card,
  .ba-sheen i {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .ba-sheen { display: none !important; }
}

/* ============================================================================
   THE SHAREABLE TEAM IMAGE (team-card.js)

   The button sits beside "Your team", right next to the six it draws: a
   control belongs beside what it affects, and the export row at the foot of
   the result is several thousand pixels of report away. The dialog reuses
   .welcome-overlay / .welcome-card like the build confirmation, so every
   dialog on the site is the same dialog.
   ========================================================================= */
/* Bottom-aligned: the share button sits level with the team's NAME, not
   floating beside the small "Your team" label above it. */
.ba-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  flex-wrap: wrap; gap: .5rem 1rem;
}
/* `#wizard h2` sets the heading's margins at (1,0,1), which is why the old
   `.ba-title { margin-top: 18px }` never applied: the title has always sat
   flush with the top. Zeroed here to match, because a flex item's margins do
   not collapse and the old bottom margin would push the six down by 7px. */
#wizard .ba-head .ba-title { margin: 0; }

.share-img-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
  cursor: pointer; line-height: 1.2;
}
.share-img-btn svg, .share-actions svg { flex: none; }
.share-img-btn svg { color: var(--accent-dark); }
.ba-head .share-img-btn { padding: 8px 14px; }
.share-img-btn:active { transform: scale(.97); transition: transform .1s ease-out; }

/* ---- naming a build (2026-09-24) --------------------------------------------
   The result's heading is the team's name (the player's own, else the
   report's), with the old "Your team" kept as a small label above it, and
   Rename edits it in place. The same name labels the saved list and titles
   the share image. */
.ba-name { display: flex; flex-direction: column; min-width: 0; flex: 1 1 18rem; }
.ba-eyebrow {
  font-size: .72rem; font-weight: 600; letter-spacing: .095em;
  text-transform: uppercase; color: var(--t-faint); margin-bottom: .3rem;
}
.ba-name-row { display: flex; align-items: center; flex-wrap: wrap; gap: .25rem .6rem; min-width: 0; }
.ba-title { overflow-wrap: anywhere; }
.ba-rename {
  display: inline-flex; align-items: center; gap: .3rem;
  min-height: 32px; padding: .25rem .55rem; border: 0; border-radius: 8px;
  background: none; color: var(--accent-dark); cursor: pointer;
  font-size: .86rem; font-weight: 550;
}
.ba-rename:hover { background: var(--t-surface-2); }
.ba-rename:active { transform: scale(.97); transition: transform .1s ease-out; }
.ba-rename-form { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.ba-name-input {
  flex: 1 1 16rem; min-width: 0; font: inherit; font-size: 1.15rem; font-weight: 600;
  letter-spacing: -.012em; color: var(--ink); background: var(--card);
  border: 1px solid var(--line-strong); border-radius: 9px; padding: .5rem .75rem;
}
/* Two classes, so this beats button.primary's full width from style.css. */
.ba-rename-form button, .ba-rename-form button.primary {
  flex: 0 0 auto; width: auto; min-height: 42px; padding: 0 1.05rem;
}
.ba-rename-note { margin: .45rem 0 0; font-size: .82rem; line-height: 1.45; color: var(--muted); }
.ba-rename-note.err { color: var(--fail); }
/* A renamed build leads with its name in the saved list; the request that
   made it follows, quieter. */
.mybuild-name { font-weight: 700; color: var(--ink); }
.mybuild-req { font-weight: 500; color: var(--muted); }
@media (prefers-reduced-motion: reduce) {
  .share-img-btn:active, .ba-rename:active { transform: none; }
}

/* The preview is the point of this dialog, so it takes as much of the window
   as it can (owner ask 2026-09-24). Never narrower than the old fixed 62rem;
   wider until the WHOLE dialog would stop fitting the window's height, so
   Download / Copy / Share stay on screen. 232px is the dialog's own title,
   line, buttons and padding plus the overlay's margin (measured at 227).
   Capped at 1280px of image: the PNG is 2560 wide, so that is 1:1 on a 2x
   screen and anything wider only blurs it. The vh line is the fallback for
   browsers without dvh, which drop the second declaration whole. */
.share-card {
  max-width: max(min(100%, 62rem), min(1280px + 3rem, (100vh - 232px) * 16 / 9 + 3rem));
  max-width: max(min(100%, 62rem), min(1280px + 3rem, (100dvh - 232px) * 16 / 9 + 3rem));
  padding: 1.5rem 1.5rem 1.2rem;
}
.share-title {
  margin: 0 2rem .3rem 0; font-size: 1.3rem; font-weight: 600;
  letter-spacing: -.02em; line-height: 1.25; color: var(--ink);
}
.share-sub { margin: 0 0 1rem; font-size: .92rem; line-height: 1.5; color: var(--t-ink-2); }
/* Fixed to the image's own 16:9 before it exists, so nothing below it jumps
   when the drawing lands. */
.share-frame {
  aspect-ratio: 16 / 9; border-radius: 10px; overflow: hidden;
  background: var(--t-surface-2); border: 1px solid var(--line);
  display: grid; place-items: center;
}
.share-img { display: block; width: 100%; height: auto; }
.share-wait { margin: 0; padding: 1rem; font-size: .92rem; color: var(--muted); text-align: center; }
.share-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1rem; }
.share-actions:empty { display: none; }
.share-actions button, .share-actions button.primary {
  flex: 1 1 9rem; width: auto; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
}
.share-status { min-height: 1.4em; margin: .6rem 0 0; font-size: .86rem; color: var(--muted); text-align: center; }
.share-status:empty { margin-top: 0; }

/* The result's export row has five buttons since Share image joined it
   (2026-09-24). Five fit one line down to about 1020px; below that the
   labels squeezed onto two and three lines and at 481px the row ran 17px
   off the side. So the main action takes its own line and the four export
   buttons share the next, and at 680px and below they pair up, since four
   across there breaks every label onto two lines. */
@media (max-width: 1020px) {
  #ai-result .btnrow { flex-wrap: wrap; }
  #ai-result .btnrow button.primary { flex-basis: 100%; }
}
@media (max-width: 680px) {
  #ai-result .btnrow button:not(.primary) { flex: 1 1 calc(50% - 4px); }
}

@media (max-width: 480px) {
  .ba-rename-form .ba-name-input { flex-basis: 100%; }
  .ba-rename-form button, .ba-rename-form button.primary { flex: 1 1 0; }
  .share-card { padding: 1.1rem 1rem 1rem; }
  .ba-head .share-img-btn { margin: 0; min-height: 44px; }
  /* The result's export row and the sample-build reader's row. Their Copy /
     Download buttons rendered as unstyled browser buttons until they took the
     ghost style alongside the share button, and at the ghost's padding four
     no longer fit across a 375px phone (7px of sideways scroll), while three
     in the reader broke every label onto two lines. The main action gets its
     own line and the rest pair up; a lone last button fills its line. */
  #ai-result .btnrow, #showcase-viewer .btnrow { flex-wrap: wrap; }
  #ai-result .btnrow button, #showcase-viewer .btnrow button {
    flex: 1 1 calc(50% - 4px); padding-inline: 10px;
  }
  #ai-result .btnrow button.primary { flex-basis: 100%; }
}

/* ---- the saved-build view's top back link (2026-09-24) ----------------------
   Quiet on purpose: a way out, not an action. Same voice as the Rename link.
   The negative inline margin puts its TEXT on the heading's left edge rather
   than its padding. */
.saved-back {
  display: inline-flex; align-items: center; gap: .3rem;
  min-height: 32px; margin: 0 0 .5rem -.55rem; padding: .25rem .55rem;
  border: 0; border-radius: 8px; background: none; cursor: pointer;
  color: var(--accent-dark); font-size: .9rem; font-weight: 550;
  text-decoration: none;           /* it is a link when it leads to /account */
}
.saved-back:hover { background: var(--t-surface-2); }
@media (max-width: 480px) {
  .saved-back { min-height: 44px; }
}
