/*
 * Manga's own tokens, copied as VALUES rather than imported.
 *
 * ⚠ This page is standalone by design — no server, no build step a reader
 * runs, nothing fetched from the engine — so it cannot link the platform's
 * stylesheet. The values below are `public/core-ui.css`'s, and they are the
 * only thing here that has a second source; `tests/theme-matches-manga.ts`
 * reads them back out of that file so the two cannot drift silently.
 */
:root{
  color-scheme: dark;
  --paper:#050508;
  --panel:#151518;
  --card:linear-gradient(150deg,#2c2c37 0%,#18181e 65%,#201727 100%);
  --rule:#35353d;
  --ink:#f8fafc;
  --soft:#a1a1aa;
  --accent:#ff2a5f;
  --on-accent:#fff;
  --field:rgba(255,255,255,0.07);
  --field-rule:rgba(255,255,255,0.15);
  --shadow:0 25px 50px -12px rgba(0,0,0,0.8);
}
:root[data-theme=light]{
  color-scheme: light;
  --paper:#fff8e6;
  --panel:rgba(255,255,255,0.92);
  --card:linear-gradient(150deg,#fffdf5 0%,#ffe3ad 100%);
  --rule:rgba(28,28,30,0.16);
  --ink:#1c1c1e;
  --soft:#5c5c61;
  --accent:#e0623f;
  --on-accent:#fff;
  --field:rgba(28,28,30,0.04);
  --field-rule:rgba(28,28,30,0.14);
  --shadow:0 20px 45px -15px rgba(28,28,30,0.18);

  /* ⚠ Derived, per theme: white on the light accent is 3.3:1, and this one is
     read at 12.5px. The light page also needs a darker ink for accent TEXT,
     which on a near-white panel is 3.1:1 at the platform's own value. */
  --accent-fill:color-mix(in srgb, var(--accent) 82%, #000);
  --accent-ink:color-mix(in srgb, var(--accent) 74%, #1c1c1e);
  --sunk:rgba(28,28,30,0.05);
  --control-rule:rgba(28,28,30,0.55);
  --lift:0 1px 0 rgba(255,255,255,0.65) inset, 0 1px 2px rgba(28,28,30,0.10);
  --lift-strong:0 1px 0 rgba(255,255,255,0.5) inset, 0 2px 6px rgba(28,28,30,0.16);

  /* The engine's two shadow inks. Light theme collapses them into one; dark
     cannot (see the dark block). `--btn-mix` is the %-of-fill that survives in
     a button's own shadow, and 60% was confirmed too heavy for light. */
  --sticker:rgba(28,28,30,0.16);
  --inset-rim:rgba(28,28,30,0.16);
  --btn-mix:82%;
  --selectable:#c2410c;
  --cell:#f2ece0;
  --inset-bg:rgba(28,28,30,0.04);
}

/*
 * ⚠ "Follow the system" HAS TO BE ABLE TO MEAN LIGHT.
 *
 * The dark palette sits on bare `:root` with no `prefers-color-scheme` block at
 * all, so a machine set to light rendered dark under the default setting and
 * the auto option could only ever produce one of the two themes. Guarded on
 * `:not([data-theme])` rather than on a value, so BOTH explicit choices beat it
 * whatever the system says, and it applies only when nobody has chosen.
 *
 * ⚠ The values are the light block's, and they are here because a media query
 * cannot inherit a selector. Anything changed above has to be changed here too;
 * `tests/the-shell-speaks-manga.ts` compares the two and fails if they drift.
 */
@media (prefers-color-scheme: light){
  :root:not([data-theme]){
  color-scheme: light;
  --paper:#fff8e6;
  --panel:rgba(255,255,255,0.92);
  --card:linear-gradient(150deg,#fffdf5 0%,#ffe3ad 100%);
  --rule:rgba(28,28,30,0.16);
  --ink:#1c1c1e;
  --soft:#5c5c61;
  --accent:#e0623f;
  --on-accent:#fff;
  --field:rgba(28,28,30,0.04);
  --field-rule:rgba(28,28,30,0.14);
  --shadow:0 20px 45px -15px rgba(28,28,30,0.18);

  /* ⚠ Derived, per theme: white on the light accent is 3.3:1, and this one is
     read at 12.5px. The light page also needs a darker ink for accent TEXT,
     which on a near-white panel is 3.1:1 at the platform's own value. */
  --accent-fill:color-mix(in srgb, var(--accent) 82%, #000);
  --accent-ink:color-mix(in srgb, var(--accent) 74%, #1c1c1e);
  --sunk:rgba(28,28,30,0.05);
  --control-rule:rgba(28,28,30,0.55);
  --lift:0 1px 0 rgba(255,255,255,0.65) inset, 0 1px 2px rgba(28,28,30,0.10);
  --lift-strong:0 1px 0 rgba(255,255,255,0.5) inset, 0 2px 6px rgba(28,28,30,0.16);

  /* The engine's two shadow inks. Light theme collapses them into one; dark
     cannot (see the dark block). `--btn-mix` is the %-of-fill that survives in
     a button's own shadow, and 60% was confirmed too heavy for light. */
  --sticker:rgba(28,28,30,0.16);
  --inset-rim:rgba(28,28,30,0.16);
  --btn-mix:82%;
  --selectable:#c2410c;
  --cell:#f2ece0;
  --inset-bg:rgba(28,28,30,0.04);
}
}


/*
 * The tokens this file owns, as opposed to the ten above that the platform
 * owns. Anything here may be tuned; anything above has to move in core-ui.css
 * first, and `tests/theme-matches-manga.ts` is what says so.
 */
:root{
  /* ONE accent, and it is attention: the primary action, the thing that is
     refusing, and nothing else. A current tab is not attention, it is
     orientation, so it is drawn in ink. */
  --accent-fill:color-mix(in srgb, var(--accent) 85%, #000);
  --accent-ink:var(--accent);
  --accent-wash:color-mix(in srgb, var(--accent) 14%, transparent);

  /* Surfaces, from the page up: paper, panel, the thing inside a panel. */
  --sunk:rgba(255,255,255,0.05);
  --raised:color-mix(in srgb, var(--ink) 8%, var(--panel));
  /* ⚠ NOT the same ink as --sticker. An outer sticker shadow wants to be
     darker than the surface it falls on; an inset rim on an already-dark fill
     needs to be LIGHTER to read as a recess at all. core-ui.css records this
     as an owner report: reusing the near-black here made dark inputs
     invisible. */
  --sticker:#0b0b0e;
  --inset-rim:rgba(210,210,220,0.15);
  --btn-mix:60%;
  --selectable:#fb923c;
  --cell:#1d1d22;
  --inset-bg:rgba(255,255,255,0.08);

  /* ⚠ A CONTROL'S EDGE IS ITS OWN REQUIREMENT: 3:1 against what it sits on.
     The platform's --rule and --field-rule are surface hairlines drawn at
     1.4:1 to 1.7:1 here, which is right for dividing a panel from a page and
     is not enough to say "this is a thing you can press". Measured on the
     painted pixel, not on the declaration. */
  --control-rule:rgba(255,255,255,0.45);

  /* A control looks pressable: a hairline lift above, a soft drop below. */
  --lift:0 1px 0 rgba(255,255,255,0.07) inset, 0 1px 2px rgba(0,0,0,0.45);
  --lift-strong:0 1px 0 rgba(255,255,255,0.12) inset, 0 2px 6px rgba(0,0,0,0.55);

  /* ⚠ Hue-free on purpose. A ring in the accent disappears on the accent
     button, which is the control most worth reaching by keyboard; ink against
     paper is the one pair that is loud on every surface this page paints. */
  --focus-ink:var(--ink);

  /* ⚠ A muted ink that still clears 4.5:1 at 12px. The platform's --soft is a
     LABEL colour on a panel; on a control's own fill it lands at 4.2:1, and the
     × that deletes a row is the smallest thing on the page. */
  --soft-strong:color-mix(in srgb, var(--ink) 30%, var(--soft));

  /* The engine's own: --shell-radius is 10px, a field is 8, a modal 16. */
  --r-s:8px;
  --r-m:10px;
  --r-l:16px;
  --r-pill:999px;

  /* A tint, not a stroke. Panels are separated by tone in this system. */
  --tint:color-mix(in srgb, var(--ink) 6%, transparent);
  --tint-strong:color-mix(in srgb, var(--ink) 10%, transparent);
  --hairline:inset 0 -1px 0 color-mix(in srgb, var(--ink) 10%, transparent);
  --motion:120ms;

  /* One rhythm. Four steps is enough for a page this dense. */
  --s-1:4px;
  --s-2:8px;
  --s-3:12px;
  --s-4:16px;

  /* One scale. Caps labels at the bottom, the masthead at the top. */
  --t-2xs:11px;
  --t-xs:12px;
  --t-s:13px;
  --t-m:13.5px;
  --t-l:15px;
  --t-xl:17px;
  --t-2xl:21px;
}
*{box-sizing:border-box}

/*
 * ⚠ EVERY control says where the keyboard is. There was almost nothing before
 * this: the browser's own ring is drawn under a `border-radius` in places and
 * suppressed by a `box-shadow` in others, and on the accent button an accent
 * ring is invisible. One rule, on the one selector that means "reached by
 * keyboard" rather than "clicked".
 */
:where(button,input,select,textarea,a,[tabindex]):focus-visible{
  outline:2px solid var(--focus-ink);
  outline-offset:2px;
}

/*
 * ⚠ `hidden` HAS TO WIN. The browser's own rule is `[hidden]{display:none}`,
 * one specificity column below any `#id{display:…}` — so `#door`, `#work` and
 * `#assetOut` all carried a `display:grid` that silently beat it, and the two
 * doors stayed on screen underneath the tool, 402px of it, pushing the work
 * area off the bottom of a phone. With the body clipping, that content was not
 * scrolled away: it was unreachable.
 */
[hidden]{display:none!important}

/*
 * ⚠ The whole app is ONE SCREEN. `overflow:hidden` on the body is what makes
 * that a rule rather than an intention: a pane that outgrows its box has to
 * say so and scroll itself, and nothing can quietly push the page taller.
 *
 * ⚠ `100dvh`, not `100vh`. A phone's toolbars shrink the viewport as you
 * scroll, and `vh` is the tall measurement — which is exactly how a layout
 * that "fits" ends up 90px off the bottom on the device it was built for.
 */
html,body{height:100%;height:100dvh}
/*
 * ⚠ THE HOUSE STACK, and both faces are VENDORED.
 *
 * `HOUSE_TYPE` in src/print-document/typeStacks.ts is Asap for display and
 * Carlito for body, and `fonts/` carries exactly those files. A face this
 * package does not ship falls back silently and the page renders in the
 * machine's own serif: Bitter is what the ENGINE loads, from Google, and this
 * tool has no network by design, so naming it here bought Georgia.
 *
 * ⚠ Asap is what you PRESS, Carlito is what you READ. core-ui.css declares
   `--manga-font: 'Bitter','Asap',serif` and puts Asap on headings only; the
   tool had Asap on running prose, which is most of "it looks generic". */
body{margin:0;background:var(--paper);color:var(--ink);
  font:15px/1.5 Carlito,Calibri,'Helvetica Neue',Arial,sans-serif;
  display:grid;grid-template-rows:auto 1fr;overflow:hidden}
main{min-height:0;min-width:0;display:grid;padding:14px;gap:12px}

header{padding:10px var(--s-4);box-shadow:var(--hairline);display:flex;gap:var(--s-3);align-items:center;
  background:var(--card);flex-wrap:wrap}
/* ⚠ The header carries five controls now and Portuguese is longer than
   English: at 407px "+ Adicionar arquivos" pushed the row 5px past the fold.
   The words shrink before the row wraps, because a wrapped header costs the
   stage a whole line. */
@media (max-width:600px){
  header{padding:8px var(--s-3);gap:var(--s-2)}
  header .icon-btn{padding:5px 9px;font-size:10px;letter-spacing:.05em}
  h1{font-size:var(--t-l)}
  .tagline{display:none}
}
h1{margin:0;font:900 var(--t-xl)/1 Asap,'Helvetica Neue',Arial,sans-serif;letter-spacing:-.01em}
h2,h3,h4{font-family:Asap,'Helvetica Neue',Arial,sans-serif;font-weight:800}
h1 b{color:var(--accent-ink);font-weight:900}
.tagline{font-size:var(--t-xs);color:var(--soft)}
.grow{flex:1}
/* The shell's pill: no stroke, a tint ground, and it LIFTS rather than
   changing colour (.manga-shell-section-header and every other pill). */
.icon-btn{border:0;background:var(--tint);color:var(--soft-strong);border-radius:var(--r-pill);
  padding:6px 12px;min-height:30px;font:800 var(--t-2xs)/1 Asap,'Helvetica Neue',Arial,sans-serif;
  text-transform:uppercase;letter-spacing:.07em;cursor:pointer;
  transition:transform var(--motion) ease,filter var(--motion) ease}
.icon-btn:hover{color:var(--ink);transform:translate(-1px,-1px);filter:brightness(1.06)}
.icon-btn:active{transform:translateY(1px)}

/* ---- the two doors ---- */
/* Capped and centred: two cards stretched across a wide screen read as two
   banners, not as a choice. */
#door{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr));gap:16px;
  align-content:center;justify-items:stretch;min-height:0;
  width:min(880px,100%);margin:0 auto}
.door{background:var(--card);border:0;border-radius:var(--r-l);padding:26px;
  text-align:left;cursor:pointer;color:var(--ink);display:flex;flex-direction:column;gap:9px;
  box-shadow:var(--shadow);font:inherit;transition:border-color .12s,transform .12s}
.door:hover{border-color:var(--accent);transform:translateY(-2px)}
.door:active{transform:translateY(0)}
.door h2{margin:0;font:600 var(--t-2xl)/1.2 Asap,'Helvetica Neue',Arial,sans-serif}
.door p{margin:0;font-size:var(--t-m);color:var(--soft);line-height:1.5}
.door .door-then{margin-top:var(--s-1);font-size:var(--t-xs);color:var(--accent-ink);font-weight:600}

/* ---- the working screen ---- */
/*
 * ⚠ A COLUMN, not a row template. The step bar, the optional screen bar and the
 * stage are three children of which one hides, and a fixed `auto 1fr` gave the
 * stage whatever row was left: adding the screen bar pushed the whole tool to
 * the bottom of the page behind a band of nothing. Same defect as `.tool-stage`
 * had, one level up.
 */
#work{min-height:0;min-width:0;display:flex;flex-direction:column;gap:12px}
#work > .bar{flex:0 0 auto}
#work > .shell{flex:1 1 auto;min-height:0}
.bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
/* ⚠ WRAPS. Eight tabs in a nowrap row made a 680px band that the page grid
   could not shrink below, so every screen on a phone scrolled SIDEWAYS as well
   as down — the one combination that is worse than either alone. */
.tabs{display:flex;gap:5px;flex-wrap:wrap;min-width:0}
/*
 * ⚠ A CURRENT TAB IS NOT AN ALERT. Three accent-filled things used to sit on
 * one phone screen at once — the tab you are on, Build, and Save project — and
 * an accent that means three different things means none of them. Where you
 * are is drawn in ink; the accent is left to the primary action and to a
 * refusal.
 */
.tab{border:0;background:var(--tint);color:var(--soft-strong);border-radius:var(--r-pill);
  padding:8px 14px;min-height:32px;white-space:nowrap;cursor:pointer;
  font:900 var(--t-2xs)/1 Asap,'Helvetica Neue',Arial,sans-serif;text-transform:uppercase;letter-spacing:.06em;
  transition:transform var(--motion) ease,filter var(--motion) ease,box-shadow var(--motion) ease}
.tab:hover{color:var(--ink);transform:translate(-1px,-1px);filter:brightness(1.06)}
.tab[aria-selected=true]{background:var(--ink);color:var(--paper);box-shadow:2px 2px 0 0 var(--sticker)}

/*
 * A step and a view inside it are two different questions, so they are drawn
 * two different ways: the step you are in is a filled pill, the view inside it
 * is an underlined word. A second row of pills would read as ten equal
 * choices, which is the screen this replaced.
 */
.subbar{gap:var(--s-3);border-bottom:1px solid var(--rule)}
.subtab{border:0;border-bottom:2px solid transparent;border-radius:0;background:transparent;
  color:var(--soft-strong);font:inherit;font-size:var(--t-s);padding:4px 1px 5px;cursor:pointer;
  box-shadow:none;white-space:nowrap;transition:color .12s,border-color .12s}
.subtab:hover{color:var(--ink);border-bottom-color:var(--field-rule)}
.subtab:active{transform:none}
.subtab[aria-selected=true]{color:var(--ink);border-bottom-color:var(--accent);font-weight:600}
.stage{min-height:0;min-width:0;display:grid;gap:12px}
/*
 * ⚠ A COLUMN, not a row template.
 *
 * This stage was `grid-template-rows: auto auto minmax(0,1fr) auto` over five
 * children, three of which hide depending on the tab. A hidden child is not a
 * grid item, so every panel took whatever row it happened to land in: on Print
 * and play with a real project open, the parts list took an unbounded `auto`
 * row and the PREVIEW got the leftovers. Measured at 1440x900 with 24 parts:
 * the iframe was **2 pixels tall**, which is the "big white square crammed
 * into a 40px band" the owner could only read by zooming to 20%.
 *
 * In a column each panel states its own share and hiding one gives its space
 * back to the others, whatever the tab is doing.
 */
/*
 * ONE SHELL, on every screen: the rail holds every control, the stage holds one
 * artifact, the status line is at the bottom. There used to be two stages here
 * and which you got depended on which screen you were on.
 */
.shell{display:grid;grid-template-columns:var(--rail,300px) 9px minmax(0,1fr);
  min-height:0;min-width:0;background:var(--panel);border-radius:var(--r-l);overflow:hidden}
.shell[data-wide=true]{grid-template-columns:minmax(0,1fr)}
.shell[data-wide=true] > .rail,.shell[data-wide=true] > .split{display:none}
/* ⚠ The end margin is not decoration: a scroller whose last control sits flush
   against the fold hides a few pixels, and the owner's own rule is that a
   marginal scroll is worse than either extreme. This gives the last group room
   to clear the edge, so the rail either fits or plainly scrolls. */
/*
 * ⚠ A REAL END GUTTER, 64px, and it is what makes the scroll unambiguous.
 *
 * The owner's rule is that a marginal scroll is worse than either extreme, and
 * a form that overflows by twelve pixels is exactly that. Shaving the rhythm
 * until it fits is a fight you lose the next time a group is added; a scroller
 * whose last control has room to clear the edge either fits or plainly does
 * not, whatever it holds.
 */
/* ⚠ And it is paid for ONLY when the rail actually scrolls (`data-scrolls`,
   set from the measured height): a fixed 64mm of nothing under a short form is
   how a 185px form came to hide 44px — the gutter itself was the overflow, and
   the rule it exists to serve was the rule it broke. */
.rail{padding:var(--s-3);display:flex;flex-direction:column;gap:var(--s-3);
  overflow:auto;min-height:0;min-width:0;background:var(--tint);scroll-padding-block-end:var(--s-4)}
.rail[data-scrolls=true]{padding-bottom:64px}
.rail > *{flex:0 0 auto}
/* The support line is quiet on purpose: it sits under the work, never over it. */
/*
 * The mini table. ⚠ It scrolls SIDEWAYS inside its own box rather than wrapping:
 * a table has no line break, which is exactly the complaint that produced it.
 */
.tbl-wrap{display:flex;flex-direction:column;gap:6px;align-items:flex-start;max-width:100%;padding-bottom:4px}
/*
 * In the stage the table has the width the work needs, and it uses ALL of it:
 * `table-layout: fixed` divides the stage between the columns instead of
 * letting them size to content and push the box sideways. ⚠ One axis only —
 * a box that scrolls both ways is the case he named, and the fix is to stop
 * the sideways one rather than to allow it.
 */
#docTable{padding:var(--s-3);overflow-y:auto;overflow-x:hidden;min-height:0;
  display:flex;flex-direction:column;gap:8px}
#docTable .tbl{width:100%;table-layout:fixed}
#docTable .tbl input{width:100%;min-width:0}
#docTable .tbl .tbl-wide{width:34%}
#docTable .tbl-rownum{width:2.4rem}
.tbl{border-collapse:collapse;font-size:var(--t-xs)}
.tbl th{text-align:left;font-weight:700;color:var(--soft);padding:2px 4px;white-space:nowrap;
  font-size:var(--t-2xs);text-transform:uppercase;letter-spacing:.04em}
.tbl td{padding:1px 2px}
/* The swatch leads the cell it belongs to; the text stays the value. */
.tbl td:has(.tbl-swatch){display:flex;gap:4px;align-items:center}
.tbl-swatch{width:26px;min-width:26px;padding:0;border:0;background:none;height:26px;cursor:pointer}
.tbl input{min-height:26px;padding:3px 6px;width:96px;box-shadow:inset 2px 2px 0 0 var(--inset-rim)}
.tbl .tbl-wide input{width:220px}
.tbl-rownum{color:var(--soft);font-size:var(--t-2xs);text-align:right;padding-right:4px;white-space:nowrap}
.tbl-text{font-family:ui-monospace,monospace;white-space:pre;overflow-x:auto;width:100%}
.support{margin:0;font-size:var(--t-xs);color:var(--soft);line-height:1.45}
/* Quieter than the alert, louder than the summary: something to fix, not
   something that just failed. */
.notice{margin:2px 0 0;font-size:var(--t-xs);line-height:1.45;color:var(--ink);
  background:var(--tint);border-radius:var(--r-s);padding:6px 9px}
/* Art at its shipping sizes, on the checkerboard so transparency shows. */
.ed-sizes{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end}
.ed-size{margin:0;display:flex;flex-direction:column;gap:4px;align-items:flex-start;max-width:100%}
.ed-size img{display:block;background:var(--cell);border-radius:2px;max-width:100%}
.ed-size figcaption{font-size:var(--t-2xs);color:var(--soft)}
.support a{color:var(--accent-ink)}
.rgroup{display:flex;flex-direction:column;gap:var(--s-2);align-items:flex-start}
.rgroup label{width:100%}
.rgroup label:has(input[type=number]),.rgroup label:has(input[type=text]),.rgroup label:has(select){
  flex-direction:column;align-items:stretch;gap:3px}
.rgroup input[type=number],.rgroup input[type=text],.rgroup select{width:100%}

/*
 * The engine's own `.manga-shell-panel-divider`, turned on its side: a 7% tint
 * of the ink over the panel, a hairline down each long edge, a two-triangle
 * grip, and an invisible hit corridor far wider than the bar it draws. An
 * earlier 18px corridor there was reported as still hard to grab.
 */
.split{position:relative;z-index:5;cursor:col-resize;touch-action:none;
  display:flex;align-items:center;justify-content:center;
  background:color-mix(in srgb, var(--ink) 7%, var(--panel));
  box-shadow:inset 1px 0 0 color-mix(in srgb, #fff 6%, transparent),
             inset -1px 0 0 color-mix(in srgb, var(--ink) 8%, transparent)}
.split::before{content:'';position:absolute;inset:0 -14px}
.split:hover,.split.grabbing{background:color-mix(in srgb, var(--accent) 12%, var(--panel))}
.grip{width:0;height:0;position:relative;display:block}
.grip::before,.grip::after{content:'';position:absolute;left:50%;margin-left:-3px;
  border-top:5px solid transparent;border-bottom:5px solid transparent}
.grip::before{border-right:6px solid var(--soft);top:-9px}
.grip::after{border-left:6px solid var(--soft);top:3px}

.stagepane{display:grid;grid-template-rows:auto minmax(0,1fr);min-height:0;min-width:0}
.stagebar{display:flex;align-items:center;gap:var(--s-2);padding:var(--s-2) var(--s-3);flex-wrap:wrap;
  background:var(--panel);box-shadow:var(--hairline)}
.pill{display:inline-flex;align-items:center;border:0;border-radius:var(--r-pill);padding:5px 11px;
  font:900 var(--t-2xs)/1.3 Asap,'Helvetica Neue',Arial,sans-serif;text-transform:uppercase;letter-spacing:.07em;
  color:var(--soft-strong);background:var(--tint);white-space:nowrap}
.zoom{display:inline-flex;border-radius:var(--r-s);overflow:hidden;background:var(--field);
  box-shadow:inset 3px 3px 0 0 var(--inset-rim)}
.zoom button{border:0;background:none;color:var(--soft-strong);padding:6px 11px;min-height:30px;
  box-shadow:none;border-radius:0;font-size:var(--t-2xs)}
.zoom button:hover:not([disabled]){transform:none;box-shadow:none;background:var(--tint)}
.zoom button[aria-pressed=true]{background:var(--ink);color:var(--paper)}
/*
 * ⚠ The stage is ONE element with one job, and every child fills it. Its
 * predecessor declared three grid rows for a child list that changed length, so
 * with no status box showing, the preview landed on the `auto` row and a row
 * holding one button took the `1fr`: 152px of window over an 1123px document.
 */
.stagebody{position:relative;min-height:0;min-width:0;overflow:auto;padding:var(--s-3);
  background:var(--sunk);display:grid;grid-template-rows:minmax(0,1fr)}
/* ⚠ The stage's children STACK in one cell, so only mutually exclusive views
   may be children. Browsing is three things at once (proposals, parts, the
   originals strip), so they flow inside `#browse` instead — stacking them put
   an invisible proposals panel over the parts and it ate their clicks. */
.stagebody > *{grid-area:1 / 1;min-height:0;min-width:0}
#browse{display:flex;flex-direction:column;gap:var(--s-3);align-content:start;overflow:auto}
/*
 * ⚠ A COLUMN, not a fixed set of grid rows. This is the third time the same
 * defect has been written in this page: a grid with N declared rows over a
 * child list that changes length, so a hidden status box moved the preview onto
 * the `auto` row and it rendered 152px tall over a 1123px document. In a column
 * each child states its own share and hiding one gives its space back.
 */
.doc-out{display:flex;flex-direction:column;gap:var(--s-2);min-height:0;min-width:0}
.doc-out > .doc-status{flex:0 0 auto}
.doc-out > iframe,.doc-out > #assetOut{flex:1 1 auto;min-height:0}
/* ⚠ The art gives way, the controls do not. */
#editor{display:flex;flex-direction:column;gap:10px;min-height:0}
#edStage{display:grid;place-items:stretch;min-height:0}
#edStage .ed-frame{height:100%}
/* The whole window is the drop target; the veil only exists during a drag. */
.dropveil{position:fixed;inset:0;z-index:40;display:grid;place-items:center;
  background:color-mix(in srgb, var(--paper) 82%, transparent);
  border:3px dashed var(--accent);
  font:900 var(--t-xl)/1 Asap,'Helvetica Neue',Arial,sans-serif;color:var(--ink)}
.proj{font:600 var(--t-s)/1.3 Asap,'Helvetica Neue',Arial,sans-serif;color:var(--soft-strong)}

/*
 * ⚠ A refusal has to be VISIBLE. Every failure in this page used to be written
 * into the same 18px grey line as every success, at the bottom, under a
 * preview: the owner pressed Build, nothing happened, and nothing said why.
 */
.alert{margin:0;padding:9px var(--s-3);border:0;border-left:3px solid var(--accent);
  border-radius:var(--r-s);background:var(--accent-wash);color:var(--ink);font-size:var(--t-m)}

/* A panel is a SURFACE: a tone, not a stroke. The engine separates panels by
   tint and reserves strokes for nothing at all. */
.panel{background:var(--panel);border:0;border-radius:var(--r-l);padding:var(--s-4);
  min-height:0;min-width:0}
/* ⚠ The one deliberate stroke on the page: a dashed rectangle is what a drop
   TARGET is, and the engine has no equivalent because it never receives a file. */
.drop{border:1.5px dashed var(--control-rule);border-radius:var(--r-m);padding:18px;text-align:center;
  color:var(--soft);font-size:var(--t-m);background:var(--field);
  box-shadow:inset 4px 4px 0 0 var(--inset-rim);
  transition:border-color .12s,color .12s,background .12s}
.drop:hover{border-color:var(--soft);color:var(--ink)}
/* ⚠ Slimmer once there is a project: an empty invitation to drop files is the
   biggest thing on the screen otherwise, on every tab, forever. */
body[data-has-parts] .drop{padding:7px;font-size:12.5px}
.drop.over{border-color:var(--accent);color:var(--ink)}
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
label{font-size:var(--t-s);color:var(--soft);display:flex;gap:6px;align-items:center}
/*
 * ⚠ The label IS the checkbox's hit target and said nothing: the box is 15px
 * and the sentence beside it ten times that, so the whole row has to read as
 * pressable. The floor is on the LABEL, not the box, for the same reason.
 */
label:has(input[type=checkbox]),label:has(select:not([disabled])){min-height:30px;cursor:pointer}
label:has(input[type=checkbox][disabled]){cursor:not-allowed}
/* A field that cannot be used takes its label with it; dimming only the box
   leaves a live-looking label beside a dead one. */
label:has(input[disabled]),label:has(select[disabled]){color:var(--soft);cursor:not-allowed}
/*
 * SUNK. A field is `border: none` plus an INSET sticker shadow, and the rim is
 * a different ink from the raised one — lighter, because an inset shadow on an
 * already-dark fill needs to be lighter to read as a recess. 44px, which is the
 * engine's own `.player-name-input`; a hit-target pass here had settled on 32.
 */
input,select,textarea{font:inherit;font-size:var(--t-m);font-family:Carlito,Calibri,'Helvetica Neue',Arial,sans-serif;
  padding:7px 11px;min-height:44px;border:0;
  border-radius:var(--r-s);background:var(--field);color:var(--ink);
  box-shadow:inset 4px 4px 0 0 var(--inset-rim);
  transition:box-shadow var(--motion) ease,background var(--motion) ease}
input:hover:not([disabled]),select:hover:not([disabled]),textarea:hover:not([disabled]){
  background:var(--tint)}
/* ⚠ Focus REPLACES the rim; it does not add a second ring beside it. */
:where(input,select,textarea):focus-visible{outline:none;
  box-shadow:inset 4px 4px 0 0 var(--accent-fill), 0 0 8px var(--accent-wash)}
input::placeholder,textarea::placeholder{color:var(--soft);opacity:1}
input[disabled],select[disabled],textarea[disabled]{background:var(--sunk);color:var(--soft);
  box-shadow:none;cursor:not-allowed}
input[type=number]{width:78px;text-align:right}
/*
 * ⚠ The platform's own lobby toggle is the NATIVE control on an inset block:
 * `launcher.js` renders a bare <input type=checkbox> inside a bold label on
 * `--hub-inset-bg`, and nothing in core-ui.css restyles it. An orange square of
 * this tool's own invention read as a different app.
 */
/*
 * ⚠ Owner, 2026-09-06: *"the checkbox is bugged, its inner shadow is bigger
 * than the box"*. Every field takes `inset 4px 4px` — right on a 44px box and
 * a quarter of a 17px one, so the rim swallowed the tick. The recess scales
 * with the control it is cut into.
 */
/*
 * ⚠ Reported TWICE (2026-09-06: *"checkboxes are still weird, their inner
 * shadow have more height than the box itself"*). Shrinking the inset was not
 * enough: a native checkbox draws its own control INSIDE the border box, so a
 * box-shadow on it sits under the tick and around it, and on a 17px control any
 * inset reads as a second, taller box. So the recess moves OFF the control and
 * onto a wrapper that is exactly the control's size, and the control itself
 * carries none.
 */
input[type=checkbox]{width:17px;height:17px;min-height:0;cursor:pointer;flex:0 0 auto;
  appearance:none;-webkit-appearance:none;margin:0;border-radius:3px;
  background:var(--field);box-shadow:inset 1px 1px 0 0 var(--inset-rim);
  display:grid;place-content:center}
input[type=checkbox]::after{content:'';width:9px;height:9px;border-radius:1px;transform:scale(0);
  background:var(--accent-fill);transition:transform var(--motion) ease}
input[type=checkbox]:checked::after{transform:scale(1)}
input[type=checkbox]:focus-visible{box-shadow:inset 1px 1px 0 0 var(--accent-fill),0 0 8px var(--accent-wash)}
label:has(input[type=checkbox]){background:var(--inset-bg);padding:9px 11px;border-radius:var(--r-s);
  font-weight:600;color:var(--ink);gap:9px;width:100%}
label:has(input[type=checkbox][disabled]){color:var(--soft);font-weight:400}
/*
 * RAISED. No border; an offset, zero-blur sticker shadow that GROWS as the
 * element moves the same distance up-left, so the object lifts off the page
 * instead of sliding across it. 4 at rest, 6 hovered, 1 pressed, per
 * core-ui.css's own primary button.
 */
button{font:800 var(--t-s)/1 Asap,'Helvetica Neue',Arial,sans-serif;text-transform:uppercase;letter-spacing:.06em;
  padding:10px 14px;min-height:38px;border:0;border-radius:var(--r-m);
  background:var(--tint-strong);color:var(--ink);cursor:pointer;
  box-shadow:4px 4px 0 0 var(--sticker);
  transition:transform var(--motion) cubic-bezier(.4,0,.2,1),
    box-shadow var(--motion) cubic-bezier(.4,0,.2,1),filter var(--motion) ease}
button:hover:not([disabled]){transform:translate(-2px,-2px);box-shadow:6px 6px 0 0 var(--sticker);
  filter:brightness(1.06)}
button:active:not([disabled]){transform:translate(0,0);box-shadow:1px 1px 0 0 var(--sticker)}
/* ⚠ The fill is the accent DARKENED: white on the platform's own accent is
   3.1:1 dark and 3.3:1 light, and this button carries a sentence. */
button.go{color:var(--on-accent);
  background:linear-gradient(135deg,color-mix(in srgb,var(--accent) 70%,white),var(--accent));
  box-shadow:4px 4px 0 0 color-mix(in srgb,var(--accent) var(--btn-mix),black)}
button.go:hover:not([disabled]){box-shadow:6px 6px 0 0 color-mix(in srgb,var(--accent) var(--btn-mix),black)}
button.go:active:not([disabled]){box-shadow:1px 1px 0 0 color-mix(in srgb,var(--accent) var(--btn-mix),black)}
/*
 * ⚠ ONE accent per screen, and on the Documents step there were two side by
 * side: Build and Save project. Saving your work to a file is a utility, not
 * the thing you came here to make, so it keeps the weight of a .go button and
 * gives up the fill. Styled by id because it is one specific button, not a
 * kind of button: if the id goes, this rule simply stops applying.
 */
#saveProject{background:var(--tint-strong);color:var(--ink);box-shadow:4px 4px 0 0 var(--sticker)}
#saveProject:hover:not([disabled]){box-shadow:6px 6px 0 0 var(--sticker)}
#saveProject:active:not([disabled]){box-shadow:1px 1px 0 0 var(--sticker)}
/*
 * ⚠ Disabled is FLAT, not faded. Opacity took the label to 2.2:1, which is a
 * button you cannot read rather than one you cannot press; the state is said
 * by the missing lift and the cursor.
 */
button[disabled]{background:var(--sunk);color:var(--soft);
  box-shadow:none;transform:none;cursor:not-allowed}
table{width:100%;border-collapse:collapse;font-size:13px}
th{text-align:left;font-size:var(--t-2xs);text-transform:uppercase;letter-spacing:.07em;color:var(--soft);
  padding:5px 7px;border-bottom:1px solid var(--rule)}
td{padding:5px 7px;border-bottom:1px solid var(--rule);font-variant-numeric:tabular-nums}
iframe{width:100%;height:100%;min-height:0;border:0;border-radius:var(--r-m);background:#fff}

/*
 * ⚠ NO SCROLLBAR, YES FADE — the house rule (owner, 2026-08-28). A scroller
 * whose content runs past its end fades that end out, so the page shows
 * through rather than an opaque band covering the content.
 *
 * ⚠ And ONE AXIS, always. `overflow-x:hidden` on every scroller is the rule
 * being enforced rather than hoped for: a box that can scroll both ways is the
 * thing he called out by name.
 */
.scroll{overflow-y:auto;overflow-x:hidden;min-height:0;scrollbar-width:none;
  -webkit-mask-image:linear-gradient(to bottom,#000 0,#000 calc(100% - 26px),transparent 100%);
  mask-image:linear-gradient(to bottom,#000 0,#000 calc(100% - 26px),transparent 100%)}
.scroll::-webkit-scrollbar{display:none}
.scroll[data-at-end=true]{-webkit-mask-image:none;mask-image:none}
/* ⚠ The same end gutter the rail gets, and on the same condition: paid only
   when the box actually scrolls. A grid of cards that overflows by thirteen
   pixels reads as nothing being below it, which is the marginal scroll the
   owner rules out — with the gutter it plainly scrolls instead. */
.scroll[data-scrolls=true]{padding-bottom:64px}

/* ---- the document lanes ---- */
.doc-form{display:flex;flex-direction:column;gap:6px}
.f-hint{line-height:1.35}

.f{display:flex;flex-direction:column;gap:3px}
/* ⚠ line-height on a caps label is height, not taste: at the body's 1.5 a
   twelve-field form is 33px taller than its pane. */
/*
 * The engine draws a section's name as a PILL that lifts, not as a caps rule:
 * `.manga-shell-section-header`, Asap 900 at .62rem, on a tint, and its own
 * comment calls it a tab on a folder.
 */
.f-label{display:inline-flex;align-items:center;width:fit-content;
  font:900 .62rem/1 Asap,'Helvetica Neue',Arial,sans-serif;text-transform:uppercase;letter-spacing:.06em;
  color:var(--soft-strong);padding:4px 9px;border-radius:var(--r-pill);background:var(--tint);
  transition:transform var(--motion) ease,filter var(--motion) ease}
.f-label:hover{transform:translate(-1px,-1px);filter:brightness(1.05)}
.f textarea,.f input{width:100%}
.f-hint{font-size:var(--t-xs);color:var(--soft)}
.f-row{display:flex;gap:6px;align-items:flex-start}
.f-row input,.f-row textarea{flex:1;min-width:0}
.f-qty{flex:0 0 60px!important;text-align:right}
.f-steplabel{flex:0 0 110px!important}
.f-sep{font-size:var(--t-xs);color:var(--soft-strong);align-self:center}
.f-drop{flex:0 0 auto;border:0;background:var(--tint);color:var(--soft-strong);
  border-radius:var(--r-s);cursor:pointer;padding:3px 8px;font-size:14px;line-height:1;
  box-shadow:2px 2px 0 0 var(--sticker);min-width:32px;min-height:32px}
.f-drop:hover:not([disabled]){color:var(--accent-ink);border-color:var(--accent);background:var(--accent-wash)}
.f-add{align-self:flex-start;border:0;background:var(--tint);color:var(--soft-strong);
  border-radius:var(--r-s);cursor:pointer;padding:4px 10px;font-size:var(--t-s);box-shadow:none}
.f-add:hover:not([disabled]){color:var(--ink);border-color:var(--soft);border-style:solid;background:var(--sunk)}
.f-choice{display:flex;gap:5px;flex-wrap:wrap}
.chip{border:0;background:var(--tint);color:var(--soft-strong);border-radius:var(--r-pill);
  padding:4px 11px;font-size:var(--t-s);cursor:pointer;box-shadow:none;
  transition:color .12s,background .12s,border-color .12s}
.chip:hover:not([disabled]){color:var(--ink);border-color:var(--soft)}
.chip[aria-pressed=true]{background:var(--ink);border-color:var(--ink);color:var(--paper);font-weight:600}
.f-rule{border:0;border-top:1px solid var(--rule);margin:4px 0}
.section{border:0;background:var(--tint);border-radius:var(--r-m);padding:10px;display:flex;flex-direction:column;
  gap:9px;background:var(--sunk)}
/* ⚠ 32px: a disclosure head is a control, and the click-target gate holds every
   control to a real target. It reads as a heading, which is exactly why it was
   20px tall and nobody noticed. */
.section-head{display:flex;align-items:center;gap:var(--s-2);min-height:32px}
.section-twist{color:var(--soft);font-size:var(--t-2xs);width:10px}
.section[data-open=false]{padding:9px 11px;gap:var(--s-1)}
.section[data-open=true]{background:var(--raised);border-color:var(--field-rule)}
.section-head b{flex:0 1 auto;font-size:var(--t-m);white-space:nowrap}
/* The collapsed summary shares the head's row and gives up its space first. */
.section-says{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:var(--t-xs);color:var(--soft)}
.blockers{border:0;border-left:3px solid var(--accent);background:var(--tint);border-radius:var(--r-s);padding:10px;
  display:flex;flex-direction:column;gap:7px;font-size:var(--t-s);background:var(--accent-wash)}
.blocker{display:flex;gap:var(--s-2)}
.blocker code{flex:0 0 auto;font-size:var(--t-xs);color:var(--accent-ink);font-weight:600}
.ok{border:0;border-left:3px solid #3f7d45;background:var(--tint);border-radius:var(--r-s);padding:9px 11px;
  font-size:var(--t-s)}
.doc-status{border:0;border-left:3px solid var(--accent);background:var(--tint);border-radius:var(--r-s);
  padding:8px 11px;font-size:var(--t-s);white-space:pre-wrap;
  /* ⚠ Capped. It reports every blocker at once, so a blank document made it
     243px tall and pushed the preview and the print button off the screen —
     the refusal working and then hiding the thing it was refusing about. */
  max-height:22vh;overflow-y:auto;overflow-x:hidden;scrollbar-width:none}
.doc-status::-webkit-scrollbar{display:none}
.hint{font-size:var(--t-xs);color:var(--soft);margin:0}

/* ---- assets ---- */
/* Side by side, so five frames are a glance rather than a scroll. */
/* ⚠ Two across even on a phone. Five frames in one column is 1200px of
   scrolling to see a set whose whole job is to be compared at a glance. */
#assetOut{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(165px,100%),1fr));
  gap:14px;align-content:start}
.asset{display:flex;flex-direction:column;gap:6px;min-width:0}
#parts{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(170px,100%),1fr));gap:12px;align-content:start}
.part{display:flex;flex-direction:column;gap:6px;border:0;border-radius:var(--r-m);padding:8px;
  font:inherit;text-transform:none;letter-spacing:normal;
  background:var(--cell);cursor:pointer;text-align:left;color:var(--ink);font:inherit;min-width:0;
  box-shadow:3px 3px 0 0 var(--sticker);
  transition:transform var(--motion) ease,box-shadow var(--motion) ease,filter var(--motion) ease}
.part:hover{transform:translate(-2px,-2px);box-shadow:5px 5px 0 0 var(--sticker);filter:brightness(1.05)}
.part:active{transform:translate(0,0);box-shadow:1px 1px 0 0 var(--sticker)}
/* The role, on the card that already states it: saying it is changing it. */
/*
 * ⚠ In the card's own corner, out of the flow: as a row it added 30px to every
 * card and took the parts grid to 3.8 screens deep on a phone, which is the
 * rule the shell already enforces. It sits over the art, where a corner label
 * on a real component would be.
 */
.part{position:relative}
/* The quick edits: they belong to the card and never to the card's own click. */
.part-quick{display:flex;gap:4px;align-items:center;width:100%;margin-top:2px}
.part-qty{width:52px;min-height:30px;padding:3px 6px;text-align:center}
.part-tags{flex:1;min-width:0;min-height:30px;padding:3px 6px;font-size:var(--t-2xs)}
.part-tick{position:absolute;top:8px;left:8px;z-index:2}
.picked-bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;padding:8px 10px;margin-bottom:10px;
  background:var(--tint);border-radius:var(--r-s)}
.picked-bar input{flex:1;min-width:120px}
.part-role{position:absolute;top:6px;right:6px;z-index:2;font-size:var(--t-2xs);padding:4px 9px;min-height:30px;
  background:var(--paper);box-shadow:2px 2px 0 0 var(--sticker)}
/* ⚠ A state is an INSET ring plus an outward bleed, never a border: a border
   would change the box, which is why the engine clips panels with
   `overflow-clip-margin` rather than `hidden`. */
.part[aria-pressed=true],.part.is-chosen{
  box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--selectable) 90%,transparent),
    0 0 6px color-mix(in srgb,var(--selectable) 38%,transparent),
    3px 3px 0 0 var(--sticker)}
.part img{width:100%;height:112px;object-fit:contain;background:#fff;border-radius:var(--r-s)}
.part b{font:600 var(--t-s)/1.3 Asap,'Helvetica Neue',Arial,sans-serif;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.part small{color:var(--soft);font-size:var(--t-xs)}
/*
 * ⚠ Denser on a phone, because the card size that reads well on a desktop
 * turns a real project into a very long list: 24 parts measured 4182px of
 * scroll, ten times the panel's own height, which is a pane you get lost in
 * rather than one you scroll.
 */
@media (max-width: 560px){
  #parts{grid-template-columns:repeat(auto-fill,minmax(min(104px,100%),1fr));gap:8px}
  .part img{height:62px}
  .part b{font-size:12px}
  .part small{font-size:10.5px}
}
/*
 * ⚠ And denser on a SHORT screen, which is the OTHER axis and the rule above
 * cannot see it: a laptop at 1024x640 has room across and none down, so the
 * same 24 parts at the desktop card size stack six rows deep — 3.7x the pane's
 * own height, which is the depth ceiling, on a machine with a wide screen.
 */
@media (max-height: 700px){
  #parts{grid-template-columns:repeat(auto-fill,minmax(min(132px,100%),1fr));gap:8px}
  .part img,.part-supplied{height:76px}
}
/*
 * ⚠ The warn state is GONE. It was a red ring plus red text on every card the
 * detector had an opinion about, which with a real project is every card: an
 * alarm that fires on everything says nothing, and the offer already asks the
 * question in words with a yes and a no on it.
 */
.part small{color:var(--soft);font:400 var(--t-xs)/1.35 Carlito,Calibri,'Helvetica Neue',Arial,sans-serif}
.part .part-size{font-variant-numeric:tabular-nums}
.part-supplied{display:grid;place-items:center;height:112px;background:var(--sunk);border-radius:var(--r-s);
  color:var(--soft);font-size:var(--t-xs)}
/* the editor */
.ed-bar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:10px}
.ed-bar:last-child{margin:10px 0 0}
.ed-field{display:flex;gap:5px;align-items:center;font-size:var(--t-s);color:var(--soft)}
.ed-num{width:70px;text-align:right}
.ed-name{flex:1;min-width:120px;font-size:14px}
/* ⚠ Outlined, never filled: the one destructive control on the page must not
   look like the one control you are meant to press. */
.ed-danger{border-color:var(--accent);color:var(--accent-ink);background:transparent;font-weight:600}
.ed-danger:hover:not([disabled]){background:var(--accent-wash);border-color:var(--accent)}
.ed-frame{display:grid;place-items:center;min-height:0;overflow:hidden;background:var(--field);
  border:0;box-shadow:inset 4px 4px 0 0 var(--inset-rim);border-radius:var(--r-m);padding:10px}
/* ⚠ The grid is drawn ON the art, at the art's own aspect, so the lines a
   person sees are the lines that get cut. A separate diagram of the grid is a
   second thing that can disagree with the first. */
/*
 * ⚠ Bounded by the FRAME, not by a slice of the viewport. `46vh` is a guess
 * about how much room there is, and it was wrong: with the controls given their
 * space back, a tall card spilled out of the frame and over the back picker
 * underneath it. `100%` of a box that is itself sized by the layout cannot.
 */
/*
 * ⚠ The box wears the ART'S OWN ASPECT, set from the image's natural size when
 * it loads. Bounding an inline-block by height instead let the box stay wider
 * than the picture inside it, and the grid overlay is `inset: 0` of this box —
 * so the lines a person saw stopped being the lines that get cut.
 */
.ed-art{position:relative;display:block;height:100%;max-width:100%;min-height:0}
.ed-art img{display:block;width:100%;height:100%;object-fit:contain;background:#fff;border-radius:4px}
.ed-cells{position:absolute;inset:0;display:grid}
.ed-cell{border:1px dashed color-mix(in srgb,var(--accent) 70%,transparent);background:transparent;
  cursor:pointer;padding:0;border-radius:0}
.ed-cell:hover{background:color-mix(in srgb,var(--accent) 18%,transparent)}
.ed-cell[data-skip=true]{background:color-mix(in srgb,var(--paper) 70%,transparent);border-style:solid;
  border-color:var(--rule)}
.mm{width:74px;text-align:right}
.mm-unit{color:var(--soft);font-size:var(--t-xs)}
.asset-head{display:flex;gap:6px;align-items:baseline;font-size:var(--t-s);flex-wrap:wrap}
.asset .f-add{align-self:stretch;text-align:center;padding:3px 8px;font-size:var(--t-xs)}
.asset-use{color:var(--soft);font-size:var(--t-2xs)}
.asset-canvas{display:block;width:100%;height:auto;border:0;border-radius:var(--r-s);background:#fff;
  box-shadow:3px 3px 0 0 var(--sticker);
  /* ⚠ Bounded, so a card's height does not depend on which shape it holds. A
     portrait frame is 25% taller than a square, which put the tallest row a
     few pixels past the pane and produced a marginal scroll — the worst kind. */
  max-height:22vh;object-fit:contain}
/* ⚠ --line was never declared anywhere: this border simply did not paint. */
.pairs{border:0;background:var(--tint);border-radius:var(--r-m);padding:9px;margin-bottom:11px;display:grid;gap:7px}
.pairs p{margin:0;font-weight:600}
.pair-head{display:flex;gap:9px;align-items:center;flex-wrap:wrap;padding-bottom:4px;border-bottom:1px solid var(--rule)}
.pair-head p{flex:1 1 240px}
/* The two designs a proposal would pair, as pictures. */
.pair-art{display:flex;gap:4px;flex:0 0 auto}
.pair-art img{width:26px;height:34px;object-fit:contain;background:#fff;border:0;border-radius:3px;
  box-shadow:2px 2px 0 0 var(--sticker)}
.pair{display:flex;gap:9px;align-items:center;flex-wrap:wrap;font-size:var(--t-s);color:var(--soft)}
.pair span{flex:1 1 220px}
.pair button{padding:3px 9px;font-size:12px}
.ed-backthumb{width:34px;height:auto;max-height:44px;object-fit:contain;border:0;
  border-radius:var(--r-s);background:#fff}
.ed-pick{max-width:230px}
input[data-bad]{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}
/* ⚠ Sticky: with twenty-three uploads the proposal banner scrolled out of the
   list long before the owner reached the component it was about. */
.pairs{position:sticky;top:0;z-index:2;background:var(--panel)}
.ed-back-row{display:grid;gap:8px}
.ed-backhead{display:flex;gap:9px;align-items:center;flex-wrap:wrap;font-size:var(--t-s);color:var(--soft)}
.ed-backs{display:flex;gap:8px;flex-wrap:wrap}
/* ⚠ Not a <select>. Its popup takes the browser's own colours, so on the dark
   theme it came up white on white and could not be read at all; and a back is
   chosen by looking at the art, never by reading a filename. */
.ed-back{display:grid;gap:3px;justify-items:center;width:96px;padding:6px;background:var(--cell);
  font:inherit;text-transform:none;letter-spacing:normal;
  border:0;border-radius:var(--r-m);cursor:pointer;color:var(--ink);box-shadow:3px 3px 0 0 var(--sticker);
  transition:transform var(--motion) ease,box-shadow var(--motion) ease}
.ed-back img{width:100%;height:56px;object-fit:contain;background:#fff;border-radius:var(--r-s)}
.ed-back b{font-size:var(--t-xs);font-weight:600;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ed-back small{font-size:var(--t-2xs);color:var(--soft)}
.ed-back:hover{border-color:var(--soft);transform:translateY(-2px);box-shadow:var(--lift-strong)}
.ed-back--suggested{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset}
.ed-back--suggested small{color:var(--accent-ink);font-weight:600}
/* ⚠ An option list takes the browser's palette unless it is told otherwise. */
select option{background:var(--panel);color:var(--ink)}
/*
 * ⚠ FIRST, and once. The name, the designers, the player count and the clock are
 * facts about the game, not about a document, and every document was asking for
 * them again with its own editable copy. They live here now and the four
 * document forms print what this says.
 */
.front{display:grid;gap:9px;margin-bottom:14px;padding-bottom:12px;border-bottom:1px solid var(--rule)}
/* One row per designer: a person is a row, never a fragment of a string. */
.front-people{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.front-person{display:flex;gap:2px;align-items:center}
.front-person input{min-width:120px}
/* One person's details, open one at a time so four boxes never become sixteen. */
.front-contact{display:flex;flex-direction:column;gap:4px;margin:2px 0 6px;padding:8px 9px;
  background:var(--inset-bg);border-radius:var(--r-s)}
.front-contact-row{display:flex;flex-direction:column;gap:2px;align-items:stretch}
.front-contact-row span{font-size:var(--t-2xs);color:var(--soft)}
.front-row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.front-row label{font-size:var(--t-s)}
/* ⚠ A caps label belongs above a field, not beside one: in this row it sits
   in a line of sentence-case labels and read as a heading dropped into it. */
.front-row .f-label{text-transform:none;letter-spacing:0;font-weight:400;font-size:var(--t-s)}
.front h2{margin:0;font:600 var(--t-m)/1.2 Asap,'Helvetica Neue',Arial,sans-serif;text-transform:uppercase;
  letter-spacing:.08em;color:var(--soft)}
/*
 * ⚠ A refusal is a CALLOUT, not four lines of pink prose. Accent text at
 * 12.5px is 3.1:1 on the light panel, and on the phone this paragraph wraps to
 * four lines: the accent moves onto the rule beside it, where it is a mark
 * rather than the body, and the sentence is read in ink.
 */
.front .gap{color:var(--ink);font-size:var(--t-s);margin:0;line-height:1.45;
  border-left:3px solid var(--accent);border-radius:0 var(--r-s) var(--r-s) 0;
  background:var(--accent-wash);padding:6px 10px}
.front input[type=text]{min-width:150px}
.identity{margin-bottom:4px}
.identity-line{display:flex;gap:9px;align-items:center;flex-wrap:wrap;font-size:var(--t-s)}
.originals{margin-top:14px;border-top:1px solid var(--rule);padding-top:11px}
.originals p{margin:0 0 var(--s-2);font-size:var(--t-s);color:var(--soft)}
.orig-strip{display:flex;gap:8px;flex-wrap:wrap}
/* ⚠ An original is a card you can put back, so it is a card — set back, not
   outlined: the engine's own disabled treatment is a fade plus a shallower
   sticker, and it keeps its pointer events because being un-actionable and
   being un-openable are different questions. */
.orig{display:grid;gap:3px;justify-items:center;width:104px;padding:6px;background:var(--cell);
  border:0;border-radius:var(--r-m);cursor:pointer;color:var(--soft);opacity:.72;
  font:inherit;text-transform:none;letter-spacing:normal;
  box-shadow:2px 2px 0 0 var(--sticker);
  transition:transform var(--motion) ease,box-shadow var(--motion) ease,opacity var(--motion) ease}
.orig:hover{opacity:1;transform:translate(-2px,-2px);box-shadow:4px 4px 0 0 var(--sticker)}
.orig img{width:100%;height:52px;object-fit:contain;background:#fff;border-radius:var(--r-s);opacity:.75}
.orig b{font-size:var(--t-2xs);font-weight:600;max-width:100%;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;color:var(--ink)}
.orig small{font-size:var(--t-2xs)}
.orig:hover{border-color:var(--soft);border-style:solid;background:var(--sunk)}
.orig:hover img{opacity:1}

/*
 * A preview that no longer matches the settings says so: the button asks to be
 * pressed and the sheet under it is dimmed. Only the slow path (growing bleed
 * on every part) can get here; everything else rebuilds itself.
 */
button[data-stale]{box-shadow:0 0 0 2px var(--accent)}
iframe[data-stale]{opacity:.45}

/* The export screen is a row of choices over one sentence saying what they do. */
#exportPanel{display:grid;gap:var(--s-2)}
#exportSays{margin:0}

/*
 * A document can take the whole stage. The form is hidden rather than removed,
 * so what somebody typed is still there when they bring it back.
 */

/* Under a phone's width the rail stacks above the stage rather than beside it,
   and the divider goes with it: there is no width left to trade. */
@media (max-width:780px){
  .shell{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,auto) minmax(300px,1fr)}
  /* ⚠ A share of the height, not a cap on it: at 42vh a short rail still left
     the preview 150px, which is a hairline that reads as the build having done
     nothing. The stage's floor is stated here so the rail can never take it. */
  /* ⚠ Measured, not guessed: at 34vh the rail took 307 of the shell's 527 and
     left the preview 139px. The stage's floor above and this share are one
     decision. */
  .rail{max-height:24vh;overscroll-behavior:contain;padding-bottom:var(--s-3)}
  .rail[data-scrolls=true]{padding-bottom:64px}
  .split{display:none}
}

/*
 * ⚠ At a short viewport the rail was hiding 26px, which is the one thing the
 * owner has ruled out: a marginal scroll reads as nothing being there. The
 * rhythm tightens rather than the controls shrinking, because a 44px field is
 * the engine's own number and is not a knob.
 */
@media (max-height:700px) and (min-width:781px){
  .rail{gap:5px;padding:6px var(--s-3)}
  .rail[data-scrolls=true]{padding-bottom:64px}
  .rgroup{gap:4px}
  .front-row{gap:4px}
  .front-people{gap:4px}
  .f-label{padding:2px 8px}
  .rail input,.rail select{min-height:34px;padding:5px 9px}
  .checklist{gap:3px 8px;margin-top:5px}
  /* ⚠ The count alone. Six chips is the right shape when there is room and a
     marginal scroll when there is not, and a marginal scroll is the one thing
     the owner has ruled out in both directions. The label still says 4/6, so
     nothing is hidden, only unlisted. */
  .checklist .check{display:none}
}

/*
 * ⚠ ONE SCROLL TREATMENT, both trays.
 *
 * Owner, 2026-09-06: *"Left tray has no scroll bar and faders; right tray has
 * scroll bar and no faders. We should have a consistent ui pattern."* He is
 * right and the engine already ruled on which one: no bar, yes fade (2026-08-28,
 * reversing an earlier rule, because this shell hides its scrollbars in five
 * places and a region hiding 1229px of content said nothing at all).
 *
 * The fade is a MASK, not an opaque gradient painted over the content: the page
 * shows through and nothing is covered, which is what the owner rejected the
 * first time. It appears only at an end there is still travel toward, so a
 * fully scrolled tray has a hard edge and never implies content that is not
 * there.
 */
.rail,.stagebody{scrollbar-width:none}
.rail::-webkit-scrollbar,.stagebody::-webkit-scrollbar{display:none}
.rail[data-at-start=false],.stagebody[data-at-start=false]{
  --fade-top:transparent 0, #000 22px}
.rail[data-at-end=false],.stagebody[data-at-end=false]{
  --fade-bottom:#000 calc(100% - 22px), transparent 100%}
.rail[data-at-start=false],.rail[data-at-end=false],
.stagebody[data-at-start=false],.stagebody[data-at-end=false]{
  -webkit-mask-image:linear-gradient(to bottom, var(--fade-top, #000 0), var(--fade-bottom, #000 100%));
  mask-image:linear-gradient(to bottom, var(--fade-top, #000 0), var(--fade-bottom, #000 100%))}

/* Steps and screens share one row: the steps lead, the screens follow on the
   right, and the screens are quieter because they live inside a step. */
.subbar{justify-content:flex-end}
.subtab{border:0;background:none;color:var(--soft);cursor:pointer;padding:5px 2px 6px;min-height:30px;
  font:600 var(--t-s)/1 Asap,'Helvetica Neue',Arial,sans-serif;box-shadow:inset 0 -3px 0 transparent;
  transition:color var(--motion) ease,box-shadow var(--motion) ease}
.subtab:hover{color:var(--ink)}
.subtab[aria-selected=true]{color:var(--ink);font-weight:900;box-shadow:inset 0 -3px 0 var(--accent)}
.subbar .subtab + .subtab{margin-left:var(--s-3)}
@media (max-width:760px){
  .bar{row-gap:4px}
  .subbar{justify-content:flex-start;width:100%}
}

/* An offer is the one shape for "the tool noticed something, yes or no". */
.offer{display:flex;align-items:center;gap:6px;margin-top:2px;padding:5px 6px;border-radius:var(--r-s);
  background:var(--inset-bg);font:600 var(--t-2xs)/1.2 Asap,'Helvetica Neue',Arial,sans-serif;color:var(--soft-strong)}
.offer img{width:20px;height:26px;object-fit:contain;background:#fff;border-radius:3px;flex:0 0 auto}
.offer-q{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.offer button{min-height:30px;padding:5px 9px;font-size:10px;box-shadow:2px 2px 0 0 var(--sticker)}
.offer-no{min-width:30px}
.offer button:hover:not([disabled]){transform:translate(-1px,-1px);box-shadow:3px 3px 0 0 var(--sticker)}
.offer-yes{background:var(--accent-fill);color:var(--on-accent)}
.offer-no{padding:5px 8px}

/* The checklist: what the documents use, ticked as you fill it in. */
.checklist{display:flex;flex-wrap:wrap;gap:5px 9px;align-items:center;margin-top:var(--s-2)}
.check{font:400 var(--t-xs)/1.3 Carlito,Calibri,'Helvetica Neue',Arial,sans-serif;color:var(--soft)}
.check[data-done=true]{color:var(--ink)}

/* A range is one fact: two boxes and the word between them, on one line. */
.range{display:flex;align-items:center;gap:6px}
.range input{flex:1;min-width:0;width:auto;text-align:center}
.range-to{font:400 var(--t-xs)/1 Carlito,Calibri,'Helvetica Neue',Arial,sans-serif;color:var(--soft);flex:0 0 auto}

/* Compact: the rail carries a dozen fields and each was costing a full line of
   label above a full-height box. */
.front-row{display:flex;flex-direction:column;gap:var(--s-2)}
.front-people{display:flex;flex-direction:column;gap:5px}
.front-person{display:flex;gap:5px;align-items:center}
.front-person input{flex:1;min-width:0}
.rail .fld > span,.rail .f-label{margin-bottom:1px}
.rail input,.rail select,.rail textarea{min-height:38px;padding:6px 9px}
.ed-back-out{align-self:flex-start}

/* Every label in the rail sits at the left edge of its own field. They were
   drifting to the middle wherever the container centred its children. */
.rail .f-label,.rail .fld > span{align-self:flex-start}
.rail .fld,.rail .front-people,.rail .rgroup{align-items:stretch}
.front-people .f-label{align-self:flex-start}
/*
 * ⚠ Owner, 2026-09-06: *"align stuff on the left config area to the left."*
 * Measured: the designers block started 64px in and a field row 41px in,
 * because these rows are FLEX with a label beside their control — fine on a
 * wide bar, an indent in a 300px rail. In the rail every row stacks and every
 * child starts at the same left edge; what stays inline is what he asked to
 * stay inline (two numbers with a word between them).
 */
.rail .front-row,.rail .front-people{flex-direction:column;align-items:stretch;gap:4px}
.rail .front-row > *,.rail .front-people > *{align-self:stretch;margin-left:0}
.rail .front-person{align-items:center}
.rail .front-row label{align-self:flex-start}

/*
 * ⚠ DENSITY ON A MOUSE. Owner, 2026-09-06: *"fields and buttons are too thick
 * and therefore occupy too much space"*, and *"I'm enjoying using it more on
 * 80% instead of 100%"*.
 *
 * The sizes here are the engine's, and the engine is phone-first: 44px is a
 * thumb, and a thumb is what it was measured against. A mouse is not a thumb,
 * so the touch floor applies where there is touch. Nothing is scaled by a
 * blanket factor — that shrinks type as well, and his complaint is about the
 * BOX, not the words in it.
 */
@media (pointer: fine) {
  .rail input,.rail select,.rail textarea{min-height:32px;padding:4px 8px}
  .rail button{min-height:30px;padding:6px 11px}
  .rail label:has(input[type=checkbox]){padding:5px 9px;gap:7px}
  .rail .f{gap:2px}
  .rail{gap:9px;padding:9px}
  .rail .rgroup{gap:6px}
  .section[data-open=false]{padding:6px 9px}
  .section-head{min-height:30px}
}

/* The part picker: a tight list, because a real project has forty lines of it. */
.pick-all{display:flex;gap:6px}
#pickList{display:flex;flex-direction:column;gap:2px;max-height:34vh;overflow:auto}
#pickList .chk{min-height:28px;padding:4px 9px;font-size:var(--t-xs)}
