/* OpenRefinery · Partner Upload — the vendor-facing surface.
 *
 * Self-contained on purpose: this file is served on the vendor host and
 * styles.css is not, so it cannot @import it — and it makes no remote request
 * of any kind. What it MUST NOT do is drift. Every colour in the :root block
 * below is copied verbatim from public/styles.css, and should move with that
 * palette — the two files may say less than each other, never something else.
 *
 * The type floor is the same standard too, and the exception list it used to
 * carry is now empty: .sect and th were the two label selectors allowed under
 * 12px, and the guide's own rules closed both — the mono face has a 12px floor,
 * so .sect went to 12, and th is prose and went to the 13px sans label rung.
 * Nothing on this surface is under 12px. This page reads small type worse than
 * the operator door does, because its readers are business users reading a
 * second language on whatever machine the office gave them, so where
 * styles.css sets 11px on a sans label — .req, .opt, .userchip b, .seclabel —
 * this file sets 12px instead.
 *
 * Re-tokenised to the OpenRefinery brand guide, presentation only: every
 * data-i18n key, element id and class name is untouched, so partner.js and
 * partner.i18n.js still find everything they address.
 */

/* ===== type, with no request that can hang ===============================
 *
 * ZERO network font requests, on BOTH doors. This portal used to open its
 * operator stylesheet with two @import urls — one to a type foundry's CDN, one
 * to Google Fonts — while the vendor stylesheet refused both. The vendor
 * stylesheet was right, and the reason it gave is the reason this file now
 * does the same: fonts.googleapis.com is blocked in mainland China and
 * characteristically HANGS rather than failing fast, and an @import at the head
 * of a page's only stylesheet is render-blocking, so the reader gets a white
 * page for as long as the connection takes to time out. A rule that holds on
 * one of two surfaces is a rule nobody can rely on, so it holds on both: no
 * <link> to a font host, no @import, nothing to hang on.
 *
 * Both families are therefore declared with `local()` sources only. A machine
 * with the brand fonts installed renders the brand; a machine without falls
 * straight through to the stack below, and neither machine makes a request.
 *
 * NO FONT BINARY WAS COPIED, because there is none to copy: the brand repo is
 * a written guide and a .docx, and ships no artwork and no font files. If
 * Manrope's 400 and 700 TTFs are later dropped into public/, this becomes one
 * relative `url()` added at the FRONT of each `src` list below, ahead of the
 * `local()` entries — same-origin, so still no third-party request.
 *
 * MANROPE 400 AND 700 ONLY. The guide forbids synthesized intermediate
 * weights, so no rule in this file asks for 500, 600 or 800: hierarchy is made
 * from size, from the two weights that exist, and from colour.
 *
 * The stacks carry PingFang SC and Microsoft YaHei ahead of the generic. Han
 * characters reach both doors — a vendor's filename, a reviewer's reply — and
 * must not land in whatever fallback the OS picks at random.
 */
@font-face {
  font-family: 'Manrope';
  src: local('Manrope Regular'), local('Manrope-Regular'), local('Manrope');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Manrope';
  src: local('Manrope Bold'), local('Manrope-Bold');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: local('JetBrains Mono'), local('JetBrainsMono-Regular'), local('JetBrains Mono Regular');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: local('JetBrains Mono Bold'), local('JetBrainsMono-Bold');
  font-weight: 700; font-style: normal; font-display: swap;
}

:root {
  /* One light scheme, declared. The guide has no dark palette and the nine
     tokens have no dark counterparts, so `color-scheme: light` stops the
     browser painting native controls, scrollbars and form widgets dark around
     a page that stays light — which is the only way a dark preference has ever
     shown up on these two surfaces. */
  color-scheme: light;

  /* ---- THE NINE. The OpenRefinery palette, verbatim from the brand guide.
     Nothing below this block introduces a colour of its own: every rule in
     this file resolves to one of these nine, to an alpha of one of them, or to
     one of the three semantic colours derived under `pass/warn/fail` below,
     and each of those says where it came from. --------------------------- */
  --almond:       #F7F3E8;  /* the canvas — roughly 60% of any surface */
  --warm-ivory:   #FBF9F4;  /* the one light ground that lifts off the canvas */
  --sand:         #E9DEC9;  /* light cards, dividers, table row shading */
  --honey:        #D4AB73;  /* RARE — see THE HONEY BUDGET below */
  --umber:        #362815;  /* body text and headlines */
  --espresso:     #45392C;  /* dark surfaces and the logo tile */
  --oat:          #EDE2CC;  /* primary buttons and quiet controls */
  --umber-tan:    #725E47;  /* secondary text on light */
  --almond-tan:   #E1D5BE;  /* secondary text on dark; hairlines on light */

  /* ---- role names, each one of the nine under the job it does -----------
     These exist so a rule can say what it means. They are aliases, not new
     colours: every value on the right is `var(--<one of the nine>)`. */
  --line-strong:  var(--umber-tan);  /* bounds anything clickable or typable.
                                        WCAG 1.4.11 wants 3:1 for that and
                                        Umber-tan measures 5.58:1 on Almond,
                                        so it also passes as text if it ever
                                        has to. Almond-tan measures 1.31:1 and
                                        must never bound a control. */
  --line:         var(--almond-tan);  /* rules and dividers, which carry no
                                         information on their own */
  --category:     var(--espresso);   /* the one non-status categorical mark.
                                        The admin grant map needs a third
                                        colour that reads as CATEGORY (this is
                                        a domain, not a benchmark) rather than
                                        as STATUS, and the guide ships no cool
                                        hue to spend on it. Espresso is
                                        structural rather than semantic, which
                                        is exactly the distinction wanted: a
                                        domain chip beside a real warning now
                                        reads as furniture, not as a verdict. */

  /* ---- alphas. AN ALPHA OF A TOKEN IS STILL THAT TOKEN, and that is the
     whole rule that keeps the palette at nine: below this block there is no
     literal colour whose channels are not one of the declared ones.
     Exactly three channel triples appear down there, and this is the complete
     list of them and of why each is inline rather than named:
       54,40,21   Umber. The five named alphas here cover the jobs that recur;
                  six drop-shadow rules want a strength of their own and spell
                  it from the same triple. No shadow on either surface is
                  neutral black, because a neutral shadow over a warm canvas
                  greys it.
       247,243,232  Almond, at 0.88 — and it is named --veil, because the
                  sticky nav on both doors is the one and only surface that
                  wants the canvas to show through it.
       138,90,31 / 138,60,49  --warn and --fail, at 0.06 / 0.13 / 0.5, for the
                  three states of the two coloured `.btn` variants. Six
                  one-off strengths for one control family; naming them would
                  be six tokens to describe one button, so they stay inline
                  beside it and the triples are stated here. */
  --wash:         rgba(54,40,21,0.10);  /* inline code grounds, bar tracks */
  --focus-halo:   rgba(54,40,21,0.14);  /* the soft ring under a focused field */
  --shade:        rgba(54,40,21,0.25);  /* card shadow */
  --scrim:        rgba(54,40,21,0.30);  /* the ground behind a modal */
  --veil:         rgba(247,243,232,0.88);  /* the sticky nav, canvas showing through */

  /* ---- semantic colours. NOT IN THE BRAND GUIDE — derived here, and this
     comment is the derivation, because an undocumented green is how a tenth
     colour gets into a nine-colour palette.

     The guide's palette is entirely warm and entirely low-chroma, so a stock
     traffic light (#2E7D32 / #ED6C02 / #D32F2F) sits on Almond like three
     stickers. Each of the three below is instead the nearest *hue* that still
     reads as pass / warn / fail, pulled to the palette's own chroma and to a
     lightness that clears 4.5:1 on the Almond canvas — so they read as this
     family's green, amber and red rather than as a UI kit's:

       --pass  #315F43  6.65:1 on Almond  (Umber's value, green hue)
       --warn  #8A5A1F  5.32:1 on Almond  (a darkened Honey — same hue family)
       --fail  #8A3C31  6.83:1 on Almond

     Each also clears 4.5:1 on its own 12% tint, which is the only other ground
     any of them lands on, and each is the letterform as well as the fill — so
     none of them is ever the pale-text-on-pale-tint failure the palette this
     file replaced had to be measured out of. `--fail-strong` is the one
     darkened variant, for the hover state of a destructive control, where the
     fill goes darker under a label that stays light.

     On a dark ground these three would need lightening; no rule in this file
     puts them on Espresso, so no lightened set is declared rather than
     declared and left to rot. -------------------------------------------- */
  --pass:         #315F43;
  --pass-tint:    rgba(49,95,67,0.12);
  --warn:         #8A5A1F;
  --warn-tint:    rgba(138,90,31,0.12);
  --fail:         #8A3C31;
  --fail-tint:    rgba(138,60,49,0.12);
  --fail-strong:  #6E3028;

  /* ---- focus.
     UMBER ON LIGHT, AND THIS IS A FIX RATHER THAN A PREFERENCE. The palette
     this file replaced ringed every control in its accent colour, and that
     accent was a mid-tone: an accent-coloured ring on a near-white ground is
     a ring nobody can see. Honey has the same problem here and worse — it
     measures 1.92:1 against Almond and 1.60:1 against Warm Ivory, against the
     3:1 WCAG 1.4.11 wants of a focus indicator, so Honey CANNOT ring anything
     on a light ground. Umber measures 12.83:1 on Almond and 13.4:1 on Warm
     Ivory, so --focus is Umber and every ring in this file uses it.

     --focus-dark exists for the other case: a ring that lands on Espresso,
     where Umber is 1.86:1 and useless and Honey is 5.26:1 and correct. Every
     ring on these two surfaces currently sits on a light ground (the dark
     fills here — the avatar disc, the `go` buttons, the command block, the
     toast — either take no focus or take it with `outline-offset` positive,
     which puts the ring outside the fill and back on the light ground), so
     --focus-dark is declared for the dark-surface rule and applied where a
     ring does land inside one. */
  --focus:        var(--umber);
  --focus-dark:   var(--honey);

  /* ---- THE HONEY BUDGET.
     Honey is a rare accent: one Honey phrase or focal action per surface,
     never body copy, never a large background. The palette this file replaced
     used its accent as a workhorse — 80-odd rules: every hover colour, every
     active tab, every focus ring, every chip tint. Those are now Umber (ink),
     Sand (hover ground), Oat (quiet control) and --focus (rings), which is why
     the accent count drops from eighty to two.

     Honey is spent, in this file, on exactly these:
       1. `.submit` — the one focal action of the surface, Honey fill under an
          Umber label (6.69:1), with an Umber-tan hairline because Honey alone
          is only 1.92:1 against the canvas and a fill needs a boundary.
       2. `.tab.active` / `.subtab[aria-selected="true"]` — the 2px underline
          under the phase you are standing in. One mark, shape not text, and
          the only thing on the page stating position.
     Nothing else. A third Honey would make the first two stop meaning
     anything, and the progress bar, the vendor dot, the drag outline and the
     live-password field — all accent-coloured before — are structural now.

     Radii: 12px / 0.75rem throughout, the guide's one radius. `50%` survives
     only where the shape is a circle (the avatar disc, a status dot) and a
     radius cannot express that. */

  /* ---- the two families, and their fallbacks ---------------------------- */
  --font-sans:    'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;

  /* Clear space around the mark on all four sides: the height of the lowercase
     "r" in the wordmark. The wordmark is 16px Manrope Bold, whose x-height is
     0.517em, so the r stands 8.3px — 8px. The nav lockup uses this as its gap
     and as its side padding, and nothing may sit inside it. */
  --mark-clear:   8px;
}


* { margin:0; padding:0; box-sizing:border-box; }
body {
  font-family:var(--font-sans);
  background:var(--almond); color:var(--umber);
  font-size:16px; line-height:1.5; -webkit-font-smoothing:antialiased;
}
.hidden { display:none !important; }

/* ===== nav ===== */
nav {
  position:sticky; top:0; z-index:50; height:60px;
  display:flex; align-items:center; justify-content:space-between; padding:0 28px;
  background:var(--veil); backdrop-filter:blur(20px);
  border-bottom:1px solid var(--line);
}
/* THE LOCKUP. Both the flex `gap` and the vertical padding are --mark-clear
   (8px — the height of the lowercase "r" in a 16px Manrope Bold wordmark),
   which is the clear space the guide asks for on all four sides of the mark.
   Nothing may sit inside it: the nav's own 28px of side padding keeps the left
   edge clear, this gap keeps the wordmark off the tile, and the padding keeps
   the nav's own border off the top and bottom. The tile is 40px against the
   guide's 32px standalone minimum, and tile + gap + wordmark measures about
   160px against its 120px minimum for a full lockup. */
.nav-left { display:flex; align-items:center; gap:var(--mark-clear); padding-block:var(--mark-clear); }
/* THE MARK IS DRAWN, NOT FETCHED — the construction and the arithmetic are in
   the comment above the <svg> in the markup. The literal colours sitting on the
   SVG's own presentation attributes there are a fallback of last resort; these
   four rules are the authority, so the mark moves with the palette and no
   colour of it lives in two places.
   THE RAYS ARE NEVER RECOLOURED. `.mark .rays` is the only rule in this file
   that touches their stroke and there is no override of it anywhere, on any
   state, in any media query. */
.mark { width:40px; height:40px; flex:none; display:block; }
.mark .tile { fill:var(--espresso); }
.mark .rays { stroke:var(--honey); }
.mark .pearl-in { stop-color:var(--warm-ivory); }
.mark .pearl-out { stop-color:var(--honey); }
/* The wordmark: Manrope Bold, Umber on light. Stated rather than inherited,
   because the wordmark's colour is a brand rule and not a consequence of
   whatever <body> happens to say. */
.wm { font-family:var(--font-sans); font-weight:700; font-size:16px; letter-spacing:-0.01em; color:var(--umber); }
/* --focus-dark IS DECLARED AND NOT APPLIED ON THIS SURFACE, and that is not an
   oversight. The three dark fills this page has — the avatar disc, the on half
   of the language toggle, and `.rowbtn.go` — all ring themselves from the
   outside with a positive `outline-offset`, which puts the ring on the light
   canvas where Umber is the correct colour. This file has no dark block with a
   focusable child, so nothing here may take a Honey ring. The token stays
   because the :root block is copied verbatim from styles.css and must not
   drift from it; the operator door does have such a block and does apply it. */
.sect { font-family:var(--font-mono); font-size:12px; font-weight:400; color:var(--umber-tan); border:1px solid var(--line); border-radius:12px; padding:2px 8px; margin-left:6px; letter-spacing:0.06em; }
.nav-right { display:flex; align-items:center; gap:16px; }
.userchip {
  display:flex; align-items:center; gap:7px; font-size:13px; font-weight:400; color:var(--umber-tan);
  background:var(--oat); border:1px solid var(--line); border-radius:12px; padding:4px 12px 4px 5px;
}
.userchip b { width:24px; height:24px; border-radius:50%; background:var(--espresso); color:var(--almond); display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:700; }
/* A button here, not the portal's <a> — logout is a POST. Everything a button
   does not inherit is spelled out. */
.signout {
  margin-left:2px; padding:0 0 0 9px; border:none; border-left:1px solid var(--line);
  font-family:inherit; font-size:12px; font-weight:400; color:var(--umber-tan);
  background:none; cursor:pointer; white-space:nowrap;
}
.signout:hover { color:var(--umber); }
.signout:focus-visible { outline:2px solid var(--focus); outline-offset:2px; border-radius:12px; }

/* The language toggle (issue #97). OUTSIDE .userchip, so it is on the sign-in
   card as well as the upload view: the reader who most needs it is the one who
   has not got past the login form yet.

   Segmented rather than a <select>: two options, both of which have to be
   readable BEFORE the switch — a reader who cannot read the page cannot be
   asked to open a menu to find out what is in it. "中文" is legible to the
   person who needs it and "EN" to the person who does not, at all times. */
.langtog {
  display:flex; align-items:center; gap:2px;
  background:var(--oat); border:1px solid var(--line); border-radius:12px; padding:3px;
}
.langtog button {
  font-family:inherit; font-size:13px; font-weight:700; line-height:1.4; color:var(--umber-tan);
  background:none; border:none; border-radius:12px; padding:3px 10px; cursor:pointer; white-space:nowrap;
}
.langtog button.on { background:var(--espresso); color:var(--almond); }
.langtog button:hover:not(.on) { color:var(--umber); }
.langtog button:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }

/* ===== layout ===== */
/*
 * TWO MEASURES, NOT ONE (issue #127).
 *
 * This was a single 860px column, and the reason given was sound as far as it
 * went: "One column. The portal's preview panel names folders and canonical
 * filenames, which is the storage grammar — this surface is never shown it."
 * True, and it is why there is no second column here. But it sized the WHOLE
 * page off a fact about the form, and the page also carries a seven-column data
 * grid. That grid wants 1089px and was being handed 776px, so reaching `delete`
 * meant scrolling the date off the left edge — you could see what you were
 * deleting or the button that deletes it, never both.
 *
 * So the container now carries the portal's own measure (public/styles.css) and
 * every surface that is READ rather than scanned declares its own narrower one
 * below. Widening the form instead would have been the wrong reading of "match
 * the portal": the portal is wide because it runs a form BESIDE a preview panel,
 * and a 1560px-wide single form column is worse than the cramped table was.
 */
/* UNDER-FILLED ON PURPOSE. White space is part of the brand, so the view
   breathes more than it did — 48px above and 96px below, against 32/80 — and
   nothing was made denser to pay for it. */
.view { max-width:1560px; margin:0 auto; padding:48px 40px 96px; }
.view.narrow { max-width:440px; padding-top:64px; }
/* The form, the pasted-key card, and the two panels that open under a row. All
   four are prose and controls read top to bottom, so they keep the measure the
   whole page used to have — the number is unchanged, only its scope is. */
.card, .findings, .delbox { max-width:860px; margin-inline:auto; }
.card {
  background:var(--warm-ivory); border:1px solid var(--line); border-radius:12px;
  padding:28px 32px 32px;
  box-shadow:0 24px 48px -32px var(--shade);
}
h1 { font-family:var(--font-sans); font-weight:700; font-size:24px; letter-spacing:-0.01em; }
.tabtitle { font-family:var(--font-sans); font-weight:700; font-size:18px; letter-spacing:-0.01em; }
.fsub { font-size:14px; color:var(--umber-tan); margin:2px 0 22px; }
.listwrap { margin-top:32px; }
.listwrap .fsub { margin:2px 0 12px; }

/* ===== form ===== */
.field { margin-bottom:18px; }
.flabel { display:flex; align-items:baseline; gap:8px; font-size:13px; font-weight:700; margin-bottom:7px; }
.req { color:var(--umber); font-size:12px; font-weight:700; }
.opt { color:var(--umber-tan); font-size:12px; font-weight:400; }
.fhint { font-size:13px; font-weight:400; color:var(--umber-tan); margin-top:5px; }
input, select {
  width:100%; font-family:inherit; font-size:16px; color:var(--umber);
  background:var(--almond); border:1px solid var(--line-strong); border-radius:12px;
  padding:9px 12px; outline:none; transition:border-color .2s;
}
input::placeholder { color:var(--umber-tan); opacity:1; }
input:focus, select:focus { border-color:var(--focus); box-shadow:0 0 0 3px var(--focus-halo); }

/* ===== either axis (issue #124) ===== ======================================
   Benchmark and Domain, bracketed together so the requirement can be stated
   ONCE and be true. It used to sit on each field as its own `required` chip,
   which said "both" twice — a rule neither the core nor the naming grammar ever
   had, and one that shut out a vendor holding a benchmark grant and no domain
   grant entirely.

   A real <fieldset>/<legend> rather than a div with a heading, because the
   grouping is the message: a screen reader announces the legend before EACH
   control inside it, so "at least one — either is enough" is heard while the
   listener is on the field it applies to, which is the one place a hint above
   the pair would not reach.

   Every browser default is overridden. A fieldset ships with a border, its own
   margins and a legend that notches through it, none of which resemble anything
   else on this form; what is wanted is the quiet bracket below — a rule down the
   left, the same gesture as a blockquote, saying "these two go together" without
   drawing a box that competes with the inputs inside it. */
/* NEUTRAL, not accent (issue #167). This group is a heading over two fields,
   and it was painted in exactly the ink `.req` uses for the word "required" —
   same token, same 12px, one weight apart — so a rule that was already
   SATISFIED still read as a live validation error. It is static markup: nothing
   toggles it, so it looked identical whether the vendor had answered or not.
   The border goes with it, for the same reason at lower volume. */
.eitheraxis {
  border:none; border-left:2px solid var(--line-strong); border-radius:0;
  margin:0 0 18px; padding:2px 0 2px 14px; min-width:0;
}
.eitheraxis > legend {
  float:left; width:100%; padding:0; margin:0 0 10px;
  font-size:12px; font-weight:700; letter-spacing:.02em; color:var(--umber-tan);
}
/* The float above takes the legend out of flow, so the first field would ride
   up beside it without this. `float` is what makes a legend wrap and align like
   ordinary text at all — the default rendering box cannot be laid out. */
.eitheraxis > legend + * { clear:both; }
/* The last field inside carries the group's own bottom margin, not its own. */
.eitheraxis > .field:last-of-type { margin-bottom:0; }
/* What the blank side becomes. Sits under both fields because it is about the
   pair, and the `x` is set in mono because it is a literal path segment — the
   thing that will appear in the folder name — not a word in the sentence. */
.axisnote { margin-top:12px; }
.axisnote code {
  font-family:var(--font-mono); font-size:12px; font-weight:700;
  background:var(--wash); color:var(--umber); border-radius:12px; padding:1px 5px;
}

.seg { display:flex; background:var(--sand); border:1px solid var(--line-strong); border-radius:12px; padding:3px; gap:3px; }
.seg button {
  flex:1; font-family:inherit; font-size:14px; font-weight:700; color:var(--umber-tan);
  background:transparent; border:none; border-radius:12px; padding:8px 10px; cursor:pointer;
}
.seg button.on { background:var(--warm-ivory); color:var(--umber); font-weight:700; box-shadow:0 1px 4px rgba(54,40,21,0.12); }
.seg button .segsub { display:block; font-weight:400; font-size:12px; color:var(--umber-tan); margin-top:1px; }

/* .tagrow, .tag, .tag.on, .tag.ghost and .taginput are all gone with the domain
   chips they dressed (issue #96). The domain is one required choice from a
   select now, styled like the benchmark select beside it, because it names a
   folder level rather than labelling the contents. A rule left behind here is
   how the control gets put back without anyone noticing it was removed — the
   same reason the '＋ add domain' rules went when that control did. */

/* ===== files ===== */
.drop {
  border:1.5px dashed var(--line-strong); border-radius:12px; background:var(--oat);
  padding:26px 20px; text-align:center; cursor:pointer; transition:all .2s;
}
.drop:hover, .drop.over { border-color:var(--umber); background:var(--sand); }
/* A glyph, not a rung of the type ladder: a single ↑ sized to be the drop
   zone's target, in the sans face because the mono face is for code, ids and
   terminal blocks and an arrowhead is none of those. */
.drop .dico { font-size:24px; color:var(--umber); font-family:var(--font-sans); }
.drop .dmain { font-size:16px; font-weight:700; margin-top:4px; }
.drop .dsub { font-size:13px; color:var(--umber-tan); margin-top:3px; }
.filerow {
  display:flex; align-items:center; gap:10px; margin-top:10px;
  background:var(--sand); border:1px solid var(--line); border-radius:12px; padding:9px 12px; font-size:13px;
}
.filerow .fname { font-family:var(--font-mono); font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:260px; }
.filerow .fsize { color:var(--umber-tan); flex:none; font-variant-numeric:tabular-nums; }
.filerow .rm { margin-left:auto; background:none; border:none; color:var(--umber-tan); cursor:pointer; font-size:16px; padding:0 4px; width:auto; }
.pbar { flex:1; height:6px; min-width:50px; background:var(--wash); border:1px solid var(--line); border-radius:12px; overflow:hidden; }
/* Structural, not accented. The bar fill was the accent colour and is now
   Espresso: a progress bar is furniture that happens to move, and this
   surface's one Honey is spent on the button that starts the upload. */
.pbar i { display:block; height:100%; border-radius:12px; background:var(--espresso); transition:width .3s; }
.pbar i.done { background:var(--pass); }
.pbar i.err { background:var(--fail); }
.pct { font-family:var(--font-mono); font-size:12px; font-weight:400; color:var(--umber-tan); flex:none; width:56px; text-align:right; font-variant-numeric:tabular-nums; }

.formerr {
  background:var(--fail-tint); color:var(--fail); border:1px solid var(--fail);
  border-radius:12px; padding:10px 14px; font-size:14px; font-weight:400; margin-bottom:14px;
}
/* THE ONE FOCAL ACTION OF THE SURFACE, and this file's whole Honey budget.
   Sign in on the first screen, Send the delivery on the second, and never both
   at once. This was a 135° two-stop gradient and the guide bans gradients, so
   it is a flat Honey fill under an Umber label (6.69:1) with an Umber-tan
   hairline — Honey measures 1.92:1 against the canvas, under the 3:1 a control
   boundary needs, so the fill cannot be its own edge. */
.submit {
  width:100%; font-family:inherit; font-size:16px; font-weight:700; color:var(--umber);
  background:var(--honey); border:1px solid var(--line-strong); border-radius:12px;
  padding:12px; cursor:pointer; letter-spacing:0.01em;
}
.submit:hover { opacity:.92; }
.submit:disabled { opacity:.5; cursor:default; }
/* THE CONSENT BOUNDARY, made visible before the value exchange.
   A bordered block rather than another .fhint: a hint is advice about the field
   above it and reads as skippable, and this is the one paragraph on the surface
   that is not. Sand ground, Umber-tan hairline, 14px supporting copy — quiet on
   purpose, because a boundary stated loudly reads as a warning, and it is not
   one. It sits above the button, because below the button is after the
   decision. */
.consent {
  margin:18px 0 0; padding:12px 14px;
  background:var(--sand); border:1px solid var(--line-strong); border-radius:12px;
  font-size:14px; line-height:1.5; color:var(--umber);
}
#loginBtn + .fhint { text-align:center; margin-top:12px; }

/* ===== the receipt ===== */
/* The single thing on this page a partner is meant to write down, so it is
   sized to be read off a screenshot rather than squinted at. */
.receipt {
  display:block; margin-bottom:20px; padding:14px 16px;
  background:var(--pass-tint); border:1px solid var(--pass); border-radius:12px;
  color:var(--pass); font-size:16px; font-weight:700;
}
/* One click takes the whole code. The banner directly under it says "Keep this
   code", which is an instruction to copy — so the string it points at has to be
   selectable in one gesture rather than dragged across character by character.
   `all` rather than `text` because half a receipt is never what anyone wanted.
   Same treatment as .confirmlabel code.pickable below. */
/* THE KPI RUNG — 28px bold, the guide's size for the one number on a surface,
   and this is that number: the only identifier this page ever gives a vendor
   and the one they read back down a phone line.
   It stays MONO above the 14px ceiling the guide sets for the mono face, and
   that is the one deliberate exception in either stylesheet. The ceiling is
   there so code does not shout; the reason a receipt is mono in the first place
   is that O/0 and l/1 have to be told apart by someone transcribing it by hand,
   and that reason gets stronger as the string gets bigger, not weaker. The
   alternative — 28px Manrope Bold — would be a receipt nobody can read aloud
   reliably. */
.receipt b {
  font-family:var(--font-mono); font-size:28px; font-weight:700; letter-spacing:0.04em;
  -webkit-user-select:all; user-select:all; cursor:text;
}
.receipt .rsub { font-size:13px; font-weight:400; margin-top:4px; }

/* ===== the deliveries table ===== */
.tablewrap { overflow-x:auto; background:var(--warm-ivory); border:1px solid var(--line); border-radius:12px; padding:6px 18px 14px; }
table { width:100%; border-collapse:collapse; font-size:13px; min-width:720px; }
/* COLUMN HEADERS ARE PROSE, NOT CODE. This was 11px mono, which broke both of
   the guide's rules about the mono face at once: it is for code, hashes, ids
   and terminal blocks and never for prose, and it lives between 12 and 14px.
   A column name is a word, so it takes the sans face at the 13px label rung
   and keeps its tracking and its caps — caps on a column name is a table
   convention, not a status label shouting. */
th { font-family:var(--font-sans); font-size:13px; font-weight:700; letter-spacing:0.1em; text-transform:uppercase; color:var(--umber-tan); text-align:left; padding:12px 10px 8px; border-bottom:1px solid var(--line-strong); white-space:nowrap; }
td { padding:10px; border-bottom:1px solid var(--line); vertical-align:top; white-space:nowrap; }
tr:last-child td { border-bottom:none; }
/* SAND ROW SHADING — the guide's table rule, the light ground alternating with
   Sand. Two things it deliberately does not do. It does not touch
   `table.matrix`, whose rows already carry meaning in their own grounds
   (section, unknown, add) and whose first column is sticky with a ground of its
   own; zebra there would fight both. And it paints `td` rather than `tr`, so a
   cell that states something by its ground keeps saying it — every such rule in
   this file is a class on the cell and outranks a row's turn in the
   alternation. */
table:not(.matrix) tbody tr:nth-child(even) td { background:var(--sand); }
td.mono { font-family:var(--font-mono); font-size:13px; font-variant-numeric:tabular-nums; }
/* The receipt again, in the row it belongs to — and this is the copy that gets
   pasted into an email weeks later, once the banner is long gone. It carries
   the click-to-select treatment the comment always claimed it had. */
td.receipt-cell {
  font-family:var(--font-mono); font-size:13px; font-weight:400; color:var(--umber); letter-spacing:0.03em;
  -webkit-user-select:all; user-select:all; cursor:text;
}
/* `overflow-wrap:anywhere` IS LOAD-BEARING, and `white-space:normal` alone was
   the silent no-op #125 recorded on the portal's own table: "Wrapping without
   that property reads as a fix in the diff and changes nothing on screen."
   CSS offers a line break after a hyphen and NEVER after an underscore, and
   #83's grammar joins every segment of a delivery name with underscores — so
   `Sample_Kiwi_FrontierBench_5tasks_20260808.zip` had no break opportunity at
   all. Measured before the fix: the box held at its 280px max-width while the
   text inside wanted 325px and 513px, so the name did not widen the column, it
   spilled straight OUT of it and across the Delivery cell beside it. */
.filenames { font-family:var(--font-mono); font-size:12px; color:var(--umber-tan); white-space:normal; overflow-wrap:anywhere; max-width:280px; line-height:1.5; }
/* ===== the filename disclosure (issue #368) =====
 *
 * PORTED FROM public/styles.css, NOT REDESIGNED. The staff door has carried
 * this exact mechanism since issue #172 and the owner compares the two doors by
 * eye, so the class names, the geometry and the three data attributes are the
 * same ones — a partner panel that behaved almost like the staff panel would be
 * worse than one that behaves identically. The two files cannot share a
 * stylesheet (partner.html links only this one), so it is copied with its
 * reasoning rather than referenced.
 *
 * The point of the whole thing is `position:absolute` on `.rowdetail`: the
 * panel is out of flow, so a 519-file delivery is exactly as tall as a one-file
 * delivery. Making it a block child inside the cell reproduces the bug.
 */
.review-disclosure { position:relative; width:max-content; max-width:100%; }
.file-disclosure { margin-bottom:4px; }
.filecount {
  font-family:var(--font-mono); font-size:12px; font-weight:700;
  line-height:1.4; color:var(--umber); background:var(--warm-ivory);
  border:1px solid var(--line-strong); border-radius:12px; padding:2px 6px;
  cursor:pointer; white-space:nowrap;
}
.filecount:hover { border-color:var(--umber); background:var(--sand); }
.filecount:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }
.rowdetail {
  position:absolute; z-index:55; top:calc(100% + 5px); left:0;
  width:max-content; min-width:220px; max-width:min(420px,80vw);
  max-height:min(360px,60vh); overflow:auto; padding:9px 11px;
  color:var(--umber); background:var(--warm-ivory); border:1px solid var(--line);
  border-radius:12px; box-shadow:0 8px 28px rgba(54,40,21,.15);
  white-space:normal; user-select:text;
}
/* THE COMPANION RULE, and it is load-bearing. `[hidden] { display:none }` lives
   in the user-agent stylesheet, and the author-origin `display:block` below
   would beat it in both directions without this — the panel would be open
   permanently. See test/hidden-attribute-css.test.ts. */
.rowdetail[hidden] { display:none; }
/* WHICH WAY IT OPENS is decided in JS and applied here (codex round-2 review of
   PR #369). `.tablewrap` is `overflow-x:auto`, and CSS computes the other axis
   to `auto` with it, so the wrapper clips vertically too — measured, the bottom
   row's panel overhung it by 22px. placeRowDetail() picks the side with room
   and clamps max-height to it; this rule is only the `up` half of that. */
.review-disclosure[data-drop="up"] > .rowdetail { top:auto; bottom:calc(100% + 5px); }
/* The same complete detail is a transient tooltip on pointer/keyboard focus and
   a pinned panel after click. Absolute in both modes, so a long filename can
   never contribute a pixel to the table row's height. */
.review-disclosure[data-pointer-preview="true"]:not([data-dismissed="true"]) > .rowdetail[hidden],
.review-disclosure:not([data-dismissed="true"]):focus-within > .rowdetail[hidden] { display:block; }
/* The domain, in the pill the staff table gives it (public/styles.css .ttag).
   A slug is a folder level and can be long — `user-logs-v-s-generated-logs` is
   live — so the cell wraps rather than holding the column open. */
/* `break-word`, NOT `anywhere`, and the difference is the whole rule. Both let
   a long slug wrap; only `anywhere` also shrinks the element's MIN-CONTENT
   width to a single character, and an auto-layout table hands a column its
   min-content width when it is short of room. Measured with `anywhere`: the
   Domain column collapsed to 46px and rendered `agentic` as `agen`/`tic` over
   two lines, on a table that had 1478px to spend. With `break-word` the column
   asks for the longest word and `user-logs-v-s-generated-logs` still wraps
   instead of holding the column open. */
.ttag {
  font-family:var(--font-mono); font-size:12px; color:var(--umber);
  background:var(--wash); border-radius:12px; padding:2px 6px;
  white-space:normal; overflow-wrap:break-word; display:inline-block; max-width:150px;
}
/* Tasks and size under the file chip. `.sub` was already the class partner.js
   put on a second line inside a cell — the Status column's lineage and
   upload-stopped lines have carried it since #154 — and this stylesheet had no
   rule for it at all, so those lines rendered at full body size. One rule, both
   uses; naming a second class for the same job is how the two drift. */
td .sub { font-size:12px; color:var(--umber-tan); margin-top:3px; }
.pill { font-family:var(--font-mono); font-size:12px; font-weight:400; border-radius:12px; padding:2px 6px; }
.pill.ok { background:var(--pass-tint); color:var(--pass); }
.pill.warn { background:var(--warn-tint); color:var(--warn); }
.pill.mute { background:var(--wash); color:var(--umber-tan); }
.empty { text-align:center; color:var(--umber-tan); padding:40px 0 32px; font-size:16px; }
.tabnote {
  margin-top:12px; padding:9px 12px; border-radius:12px; font-size:13px;
  background:var(--pass-tint); color:var(--pass); border:1px solid var(--pass);
}
/* The same strip carries a failure — a list that would not load, an upload that
   landed but whose refresh did not. Distinct colour, because "Deleted TW-K4Q7"
   and "could not load your deliveries" rendered identically is how a failure
   reads as a success. */
.tabnote.err { background:var(--fail-tint); color:var(--fail); border-color:var(--fail); }
.tabnote.hidden { display:none; }

/* ===== buttons ===== */
.rowbtn {
  font-family:inherit; font-size:13px; font-weight:700; line-height:1.5; color:var(--umber);
  background:var(--almond); border:1px solid var(--line-strong); border-radius:12px;
  padding:3px 9px; cursor:pointer; white-space:nowrap; width:auto;
}
.rowbtn:hover { border-color:var(--umber); color:var(--umber); }
.rowbtn:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }
/* A row can carry both Read and Delete at once — the two answer independent
   questions (is there a thread, is this still fresh). Without a gap they abut. */
.rowbtn + .rowbtn { margin-left:6px; }
.rowbtn.go { background:var(--espresso); border-color:var(--espresso); color:var(--almond); }
.rowbtn.go:hover { background:var(--umber); border-color:var(--umber); color:var(--warm-ivory); }
/* Quiet until hovered: a delete that draws the eye on every row invites the
   click it is trying to make deliberate. */
.rowbtn.del { color:var(--umber-tan); border-color:var(--line); }
.rowbtn.del:hover { color:var(--fail); border-color:var(--fail); }
.rowbtn.danger { background:var(--fail); border-color:var(--fail); color:var(--warm-ivory); }
.rowbtn.danger:hover { background:var(--fail-strong); border-color:var(--fail-strong); color:var(--warm-ivory); }
/* Last, and naming .danger and .go explicitly: both are rules of equal
   specificity, so a lone .rowbtn:disabled loses to them and a dead confirm
   button still reads as a live one. */
.rowbtn:disabled, .rowbtn.go:disabled, .rowbtn.danger:disabled,
.rowbtn:disabled:hover, .rowbtn.go:disabled:hover, .rowbtn.danger:disabled:hover {
  background:var(--sand); border-color:var(--line); color:var(--umber-tan); cursor:default;
}

/* ===== delete confirmation (inline) ===== */
.delbox {
  display:flex; flex-direction:column; gap:14px; margin-top:14px; padding:16px 18px;
  background:var(--warm-ivory); border:1px solid var(--fail); border-radius:12px;
  box-shadow:0 24px 48px -32px var(--shade);
}
.delhead { display:flex; align-items:center; justify-content:space-between; gap:12px; }
.ovtitle { font-family:var(--font-sans); font-weight:700; font-size:16px; }
.ovtitle.danger { color:var(--fail); }
.ovsub { font-family:var(--font-mono); font-size:12px; color:var(--umber-tan); }
.seclabel { display:flex; align-items:baseline; gap:8px; font-size:12px; font-weight:700; letter-spacing:0.1em; text-transform:uppercase; color:var(--umber-tan); margin-bottom:-6px; }
.pubfiles {
  max-height:190px; overflow-y:auto; background:var(--sand);
  border:1px solid var(--line); border-radius:12px; padding:8px 10px;
}
.pubfile { display:flex; gap:12px; font-family:var(--font-mono); font-size:13px; padding:2px 0; }
.pubfile .nm { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pubfile .sz { margin-left:auto; flex:none; color:var(--umber-tan); font-variant-numeric:tabular-nums; }
.warnbox {
  background:var(--warn-tint); color:var(--warn); border:1px solid var(--warn);
  border-radius:12px; padding:10px 12px; font-size:13px; line-height:1.5;
}
.warnbox b { font-weight:700; }
.warnbox.danger { background:var(--fail-tint); color:var(--fail); border-color:var(--fail); }
.delbox .formerr { margin-bottom:0; }
.delbox .rowbtn.danger { align-self:flex-start; padding:8px 16px; font-size:14px; }
.confirmlabel { display:flex; flex-direction:column; gap:6px; font-size:13px; color:var(--umber-tan); }
/* `overflow-wrap:anywhere` because THIS TOKEN IS NOW A FILENAME (#127, codex
   review [P1]). It held a receipt — nine characters from a fixed alphabet, which
   fits at any width — and it holds whatever the vendor named their file, which
   nothing caps below the filesystem's 255 bytes. CSS never breaks after an
   underscore and #83's grammar is underscore-joined, so without this the string
   sets its own width and runs off the card.
   Measured: 45 and 71 characters fit at every width, 91 overflows a 768px
   window, and a legal 224-character name renders 1764px wide inside an 824px
   card — on one line, at every viewport. The one thing a destructive
   confirmation may not be is unreadable. */
.confirmlabel code { font-family:var(--font-mono); font-size:13px; font-weight:400; color:var(--umber); background:var(--sand); padding:1px 5px; border-radius:12px; overflow-wrap:anywhere; }
/* One click takes the whole string — it is typed back character for character
   in front of the only irreversible action on this page. */
.confirmlabel code.pickable { -webkit-user-select:all; user-select:all; cursor:text; }
.confirmlabel input {
  font-family:var(--font-mono); font-size:13px; color:var(--umber); letter-spacing:0.03em;
  background:var(--almond); border:1px solid var(--line-strong); border-radius:12px; padding:8px 10px;
}
.confirmlabel input:focus-visible { outline:2px solid var(--focus); outline-offset:1px; border-color:var(--fail); }

/* ===== what we found (issue #41) ===== */
/* Same box grammar as .delbox above — inline, under the row it is about — but
   in the house border rather than the danger one. It carries a verdict that may
   be bad news; the container itself is not the alarm. */
.findings {
  display:flex; flex-direction:column; gap:14px; margin-top:14px; padding:16px 18px;
  background:var(--warm-ivory); border:1px solid var(--line-strong); border-radius:12px;
  box-shadow:0 24px 48px -32px var(--shade);
}
.findings.hidden { display:none; }

/* The verdict, in the vendor's own vocabulary. Colour AND the word, never
   colour alone: "Not accepted" has to survive a monochrome screen. */
.verdictbox { border-radius:12px; padding:14px 16px; border:1px solid; display:flex; flex-direction:column; gap:5px; }
.verdictbox.hidden { display:none; }
.verdictbox.accept { background:var(--pass-tint); border-color:var(--pass); color:var(--pass); }
.verdictbox.revise { background:var(--warn-tint); border-color:var(--warn); color:var(--warn); }
.verdictbox.reject { background:var(--fail-tint); border-color:var(--fail); color:var(--fail); }
.verdictbox .vtitle { font-family:var(--font-sans); font-size:18px; font-weight:700; }
.verdictbox .vbody { font-size:14px; line-height:1.55; }

/* The thread. Ours is the plain ground and theirs is tinted — the reverse of
   the internal screen, where OUR messages are the plain ones. Each surface
   tints the other side, so "the coloured one is not mine" holds on both. */
.thread { display:flex; flex-direction:column; gap:10px; }
.msg { display:flex; gap:10px; font-size:14px; line-height:1.6; }
.msg .who {
  font-family:var(--font-mono); font-size:12px; letter-spacing:0.05em;
  color:var(--umber-tan); flex:none; width:74px; padding-top:4px;
}
.msg .bubble { flex:1; min-width:0; background:var(--almond); border:1px solid var(--line); border-radius:12px; padding:10px 13px; }
.msg.mine .bubble { background:var(--oat); border-color:var(--line-strong); }
/* The message is set with textContent, so this is the only thing keeping a
   reply written as three numbered points shaped like three numbered points. */
.msg .text { white-space:pre-wrap; overflow-wrap:anywhere; }
.msg .meta { font-family:var(--font-mono); font-size:12px; color:var(--umber-tan); margin-top:6px; }
.msg .vchip { display:inline-block; margin-bottom:7px; }

/* WARM IVORY, NOT ALMOND — the one ground on this surface chosen for something
   other than the canvas rule. The report assumes a white page (it is the same
   document the customer receives), so tinting it with the canvas would show
   this vendor something different from what was written. Warm Ivory is the
   lightest of the nine and the closest the palette gets to leaving it alone. */
#findFrame {
  width:100%; height:min(60vh,520px); border:1px solid var(--line);
  border-radius:12px; background:var(--warm-ivory);
}

/* The corrected delivery. On `revise` this is the whole point of the screen,
   so it is the one thing here that draws the eye. */
.callout {
  display:flex; flex-wrap:wrap; align-items:center; gap:12px;
  background:var(--oat); border:1px solid var(--line-strong);
  border-radius:12px; padding:13px 15px; font-size:14px; line-height:1.55;
}
.callout.hidden { display:none; }
.callout > div { flex:1 1 320px; min-width:0; }
.callout .mono { font-family:var(--font-mono); font-weight:400; }

.replyblock { display:flex; flex-direction:column; gap:8px; }
.replyblock textarea {
  width:100%; font-family:inherit; font-size:16px; line-height:1.6; color:var(--umber);
  background:var(--almond); border:1px solid var(--line-strong); border-radius:12px;
  padding:10px 12px; resize:vertical;
}
.replyblock textarea::placeholder { color:var(--umber-tan); opacity:1; }
.replyblock textarea:focus-visible { outline:2px solid var(--focus); outline-offset:1px; border-color:var(--umber); }
.replyfoot { display:flex; align-items:center; gap:10px; }
.replyfoot .rowbtn { padding:7px 15px; font-size:14px; }
.findings .formerr { margin-bottom:0; }

@media (max-width:900px) {
  .view { padding:20px 12px 60px; }
  .card { padding:22px 18px 26px; }
  nav { padding:0 16px; }
  .sect { display:none; }
}

/* ---------- the command-line lane (issue #78) ----------
 *
 * The key card is the vendor's whole interaction with this lane, so it is
 * loud where the form is quiet: the receipt is display-sized, the command is
 * the only monospace block on the page, and the live listing sits under it so
 * "is this working?" is answered without scrolling or clicking.
 *
 * Every value below is an existing token. A new palette here would make the
 * one screen a vendor sees least often look like a different product. */

.card.cli { border-color: var(--line-strong); }

.clihead {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; margin-bottom: 18px;
}
/* The KPI rung again, and the same mono exception, for the same reason: on the
   CLI card this receipt is the page's one number. The operator card's twin of
   this class sits at 14px instead, because there the receipt is one field among
   many rather than the whole point of the screen. */
.clireceipt {
  font-family: var(--font-mono); font-size: 28px; font-weight: 700;
  color: var(--umber); letter-spacing: 0.02em;
}
.clisub { font-size: 13px; color: var(--umber-tan); margin-top: 2px; }

/* The command block. `pre-wrap` and not `pre`: a long access token would
 * otherwise scroll sideways off a card the vendor is trying to read, and the
 * Copy button means nobody has to select it by hand anyway. `user-select`
 * stays on regardless — the clipboard fallback selects this node. */
.clicmd {
  font-family: var(--font-mono); font-size: 13px; line-height: 1.7;
  background: var(--sand); border: 1px solid var(--wash);
  border-radius: 12px; padding: 14px 16px; margin: 0 0 10px;
  white-space: pre-wrap; word-break: break-all; color: var(--umber);
  max-height: 240px; overflow-y: auto;
}

.clirow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }

.clilive {
  margin-top: 22px; padding-top: 18px;
  border-top: 1px solid var(--wash);
}
/* The answer to "is it working?", and the reason there is no done button. */
.clistat {
  font-family: var(--font-mono); font-size: 14px; font-weight: 400;
  color: var(--pass); background: var(--pass-tint);
  border-radius: 12px; padding: 8px 12px; display: inline-block;
}
.clinames {
  font-family: var(--font-mono); font-size: 12px; color: var(--umber-tan);
  margin: 8px 0 10px; word-break: break-all;
}

#cliCloseNow { margin-top: 10px; }

/* WHAT THE VENDOR HAS TO INSTALL (issue #246).
 *
 * The first anchor and the first <details> on either portal, so both get their
 * look defined here rather than inheriting a browser default that matches
 * nothing else on the page. The disclosure is shut by default: the two lines
 * above it answer the question for anyone who already has the CLI, and only a
 * reader who does not needs the commands. */
.prereq { font-size: 13px; font-weight: 400; color: var(--umber-tan); line-height: 1.6; }
.prereq p { margin: 0 0 6px; }
.prereq b { color: var(--umber); font-weight: 700; }
.prereq code, .prereqcard code {
  font-family: var(--font-mono); font-size: 12px;
  background: var(--wash); border-radius: 12px;
  /* 3px, not 5: the chip is regularly followed by a comma or a full stop, and
     wider padding puts a visible gap in front of the punctuation. */
  padding: 1px 3px; color: var(--umber); white-space: nowrap;
}

.prereqmore { margin-top: 8px; }
.prereqmore > summary {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: 6px;
  font-weight: 700; color: var(--umber);
  border-bottom: 1px dashed var(--line-strong); padding-bottom: 1px;
}
.prereqmore > summary::-webkit-details-marker { display: none; }
/* Drawn from borders rather than typed as a character: ▸ is absent from the
   page's own font stack in both languages and falls back to a glyph that reads
   as a bullet, which is not a control anyone would think to click. */
.prereqmore > summary::before {
  content: ''; width: 0; height: 0; flex: none;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent; border-bottom: 4px solid transparent;
  transition: transform 0.12s ease;
}
.prereqmore[open] > summary::before { transform: rotate(90deg); }
.prereqmore > summary:hover { color: var(--umber); }
.prereqmore > summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; border-radius: 12px; }

.prereqbody {
  margin-top: 10px; padding: 12px 14px;
  background: var(--oat); border: 1px solid var(--wash); border-radius: 12px;
}
.prereqrow { margin-bottom: 12px; }
.prereqrow:last-of-type { margin-bottom: 10px; }
.prereqcase {
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--line); margin-bottom: 4px;
}
.prereqcmd {
  font-family: var(--font-mono); font-size: 12px; line-height: 1.65;
  background: var(--sand); border: 1px solid var(--wash); border-radius: 12px;
  padding: 8px 10px; margin: 0; color: var(--umber);
  /* A vendor selects these to copy them, so they wrap rather than scroll and
     the long tarball URL stays whole. */
  white-space: pre-wrap; word-break: break-all; user-select: text;
}
.prereqnote { margin-top: 4px; }
.prereqcmd + .prereqnote { margin-top: 5px; }

.prereqlink {
  display: inline-block; color: var(--umber); font-weight: 700;
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: var(--line-strong);
}
.prereqlink:hover { color: var(--umber); text-decoration-color: var(--umber); }
.prereqlink:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; border-radius: 12px; }
/* On the key card the line and its link read as one sentence. */
.prereqcard { margin-bottom: 8px; }
.prereqcard .prereqlink { margin-left: 6px; font-size: 13px; }
