/* THE CLIENT HUB — the studio's frame from the studio's hue (the sitting of
   Sep 2; prototypes/pay.html was the spec, transcribed here). The app's
   construction — one bordered box on --outside, a light L of chrome on
   --tint, the room on --panel, the paper white — fed the STUDIO's brand
   (BrandStyle::hubCss sets --brand and --highlight; every tint in
   tokens.css derives), minus our rail. The paper is paper.css's, verbatim.
   The client's column sits to the RIGHT of the paper and OUTSIDE the grid
   math, the way the rail sits outside it on the left: --side wide, cells
   made of the line, edge to edge. Chrome is namespaced .hub-* / .side so
   these rules and studio.css can share the styleguide without colliding.
   Reads tokens.css only — never a literal color in a component.
   THE CHROME PASS, client host (Sep 3): the bar and the footer are the
   app masthead's height — one token, --bar (72), read here; the bar is
   its cell PLUS its line, the footer its cell over its line — 74 and 74.
   The column's cells stay 48 (--fh). */

/* ── the frame ── */
html{background:var(--outside);scrollbar-gutter:stable}   /* scrollbar-gutter, same reason as the app's (Sep 3): the frame sat 7px further right on a page that didn't scroll than on one that did; the gutter is reserved either way so the frame's left never jumps between pages */
body.hub{margin:0;font:var(--paragraph-font);color:var(--ink);background:var(--panel);min-height:100vh;display:grid;grid-template-rows:calc(var(--bar) + var(--stroke)) auto 1fr auto;   /* the bar · the flash slot (always present, empty = 0 tall) · the room · the foot — a flash used to be an unplanned fourth child that took the room's 1fr (seen in the Kickoff 18 shoot) */
b,strong{font-weight:500}   /* the heaviest weight the faces ship: nothing synthesized */
  max-width:calc(var(--content) + 2*var(--content-edge) + var(--side) + .25rem);margin:0 auto;border-left:var(--line);border-right:var(--line)}
body.hub,body.hub *{box-sizing:border-box}
body.hub a{color:inherit;text-decoration:none}
/* ONE LINK STYLE IN CONTENT (ruled Sep 29, issue #78 §1 — studio.css says it first): every bare <a> in the room wears the fill (the studio's Buttons choice, on this origin as on the app), no underline at rest, an underline on hover and on focus-visible. The bar, the flashes and the foot are the chrome and keep their cells; a link with a class (.go, .cell, .back) has its own rule. */
body.hub a:not([class]):where(:not(.hub-bar *,.hub-flashes *,.hub-foot *)){color:var(--fill)}
body.hub a:not([class]):where(:not(.hub-bar *,.hub-flashes *,.hub-foot *)):hover,body.hub a:not([class]):where(:not(.hub-bar *,.hub-flashes *,.hub-foot *)):focus-visible{text-decoration:underline}
body.hub button{font:inherit;color:inherit;border-radius:0}
body.hub.bare{display:block;max-width:calc(var(--content) + 2*var(--content-edge));border:0;background:#fff}
body.hub.bare main.bare .paper{min-height:100vh}

/* ── the bar: three cells — the studio's (its logo, or its name), the crumb, the session. The frame is the letterhead. (The evening diff of Sep 2.) A 72 cell plus its line (the chrome pass, Sep 3 — the app masthead's rule): the cells centre their contents; the studio's cell is THE LOCKUP BOX (Sep 16, below); the crumb and the session cell keep the 24 inset. ── */
.hub-bar{display:grid;grid-template-columns:auto 1fr var(--side);align-items:center;height:calc(var(--bar) + var(--stroke));position:sticky;top:0;z-index:5;background:var(--tint);border-bottom:var(--line)}
.hub-bar .studio{display:flex;align-items:center;height:100%;padding:0 var(--space-xl);font:500 var(--text-md)/1 var(--ui);color:var(--ink);white-space:nowrap}   /* THE LOCKUP BOX (Sep 16): the cell is mail's box — Email\Lockup's 48 × 240, which the template writes as --lockup-h / --lockup-w from the class's constants, one rule for the bar and the letter — plus 24 each side; its width follows the art, never a fixed 200 (the old 36 × 152 box put a 5:1 logo at ~31 tall in the 72 cell) */
.hub-bar .studio img{height:var(--lockup-h);width:auto;max-width:var(--lockup-w);object-fit:contain;display:block}   /* the art fits to 48 tall, capped at 240 wide, anchored left: width:auto is the art's own width at 48, so the box IS the art up to the cap; past it, contain keeps the shape and the art fills the width, centred on the height (left and centre coincide, as in mail) */
.hub-bar .studio span{display:block;max-width:var(--lockup-w);overflow:hidden;text-overflow:ellipsis}   /* no logo: the name plate in the same box — the name at the bar's 15, never wider than the box, an ellipsis past it (the letter shrinks it first; CSS can't measure) */
.crumb{display:flex;align-items:center;height:100%;gap:var(--space-md);padding-left:var(--space-xl);border-left:var(--line);white-space:nowrap;min-width:0;font:var(--action-font);letter-spacing:var(--action-tracking);text-transform:var(--action-casing);color:var(--ink2)}   /* the chrome inset, like any cell — it follows a cell now, not the paper's edge; it draws its LEFT (the border rule) */
.crumb i{font-style:normal;color:var(--field)}   /* the separators are the line's color: chrome, not words */
.crumb{overflow:hidden}.crumb a,.crumb span{min-width:0;overflow:hidden;text-overflow:ellipsis}.crumb .here{flex-shrink:0}   /* a long trail on a narrow bar: the earlier crumbs give way, where you are keeps its word (Client ▸ Payments overran the session cell at 600 — the Kickoff 18 shoot) */
.crumb .here{color:var(--ink)}
.crumb a:hover{color:var(--ink)}
.crumb .back{display:none}   /* THE WAY BACK (Kickoff 24, ruled Sep 10): one up — shown only on the narrow bar, below */
.crumb .back i{color:var(--acting);margin-right:var(--space-xxs)}
.hub-bar.narrow .crumb>:not(.back){display:none}.hub-bar.narrow .crumb .back{display:block}   /* the styleguide's forced narrow state — the same rules as the breakpoint's */
.hub-bar .who{display:grid;grid-template-columns:minmax(0,1fr) max-content;align-items:center;height:100%;column-gap:var(--space-lg);padding:0 var(--space-xl);border-left:var(--line);font:var(--action-font);letter-spacing:var(--action-tracking);text-transform:var(--action-casing);color:var(--ink2);white-space:nowrap;min-width:0}   /* exactly the column's width: the bar's rule and the column's rule are one line */
.hub-bar .who span{overflow:hidden;text-overflow:ellipsis;text-transform:none;letter-spacing:0;min-width:0}   /* A CELL NEVER WRAPS AND NEVER TRUNCATES ITS WORD (Sep 17), by construction: the cell is a grid — the email on minmax(0,1fr), the word on max-content — so the cell's min-content IS its word (LOG OUT and the 24s), and the bar's session column is floored on min-content below: the email gives way to an ellipsis, down to nothing; the word cannot */
.hub-bar .who form{margin:0}
.hub-bar .who button{font:inherit;letter-spacing:inherit;text-transform:inherit;color:var(--acting);background:none;border:0;padding:0;cursor:pointer}
.hub-bar .who button:hover{color:var(--ink)}
/* THE PROFILE'S BAR sits HERE, with the base, ahead of every breakpoint (Sep 17): written after the phone block at equal specificity, these
   rules beat it — on the profile the bar never took its two rows at 528 and LOG IN was cut to "LO" at 375. Base first, breakpoints after. */
body.hub.profile{grid-template-rows:calc(var(--bar) + var(--stroke)) auto 1fr auto}
body.profile .hub-bar{grid-template-columns:auto 1fr auto}   /* the third cell is the session cell, as wide as its word — not the column's width: there is no column */
.hub-bar .session{display:flex;align-items:center;height:100%;padding:0 var(--space-xl)}   /* the session cell's place, NO rule to its left (ruled Sep 7). Not .door — client.css's .door is the sign-in page and margins its .go */
.hub-bar .session .go{width:auto;height:var(--fh);margin:0}   /* the hub's .go — the acting button everywhere on the hub; the bar's 48 in its 72 */
.hub-bar .session .go:hover{background:var(--ink)}
.hub-bar.lockup{grid-template-columns:auto 1fr}   /* THE UNKNOWN VIEWER (ruled Sep 23, issue #11): a grant, no session, on a leaf — the lockup cell and an empty band; no crumb, no way back, no LOG IN (crumb-bar.php). Two classes: it beats the breakpoints' one-class column maps below */

/* ── the room: the paper on the grid, the column beside it ── */
.room{display:grid;grid-template-columns:minmax(0,1fr) var(--side);align-items:stretch}
/* THE BORDER RULE (ruled Sep 2, evening, both axes; the vertical axis re-ruled Sep 3): a rule belongs to the thing AFTER it on the horizontal axis — a column draws its LEFT, the footer its TOP; a table's outer box draws the outside. On the vertical axis A RUN CLOSES WITH A RULE: the head draws the line under itself and every cell draws its own BOTTOM, so DOWNLOAD INVOICE ends on a line whichever cells a state shows (a :last-child can't see hidden siblings — no :last-child border rule anywhere here). Nothing is drawn twice, so nothing doubles or misaligns (the 4px line where DOWNLOAD INVOICE met the footer was the test). The paper draws no right edge: the column beside it draws its left. */
.room.one{grid-template-columns:minmax(0,1fr)}   /* the door, the stubs: one column (FLAGGED — undesigned) */
.door{padding:var(--space-xxxl) var(--content-edge);max-width:calc(var(--side) + 2*var(--content-edge))}
.door h1{font:var(--title-font);margin:0 0 var(--space-md)}
.door p{margin:0 0 var(--space-lg-xl);color:var(--ink2)}
:is(.door,.door form,.status,.wide,.how,.how form,.side .run)>:last-child{margin-bottom:0}   /* BOTTOM-ONLY (Sep 28, Jessica's law): a flow element owns the space under it; the last one in its container gives it up, so nothing stacks on the container's own padding */
.door ul.plain{list-style:none;margin:0;padding:0}
.door ul.plain li{padding:var(--space-md) 0;border-top:var(--line)}
.link-landing{padding:var(--space-xl) var(--content-edge);font-size:var(--text-md)}

/* ── the column: the studio's chrome on the client's side. --panel (the form ground), deeper than the frame's L, so the white fields are windows cut into it. ── */
.side{background:var(--panel);border-left:var(--line);padding-bottom:var(--space-xxxxl)}   /* the column's rule is ITS left border — the same side the bar's session cell draws it, so the two are one line. The column ends on the same air as the paper (Sep 3): 8u below its last cell — the paper's own bottom padding — so both columns end 64 above the footer; before, a tall column's last line sat ON the footer's (measured 0). A short column already has the air (the room stretches it to the paper); the padding changes nothing there */
.status{padding:var(--space-xxxxl) var(--space-xl) var(--space-xxxxl)}   /* the paper's own inset below the bar (one token, ruled Sep 22), 8u before PAY BY — the column's one rhythm: eight units between every section (the after-line's 4u plus the next head's 4u make the other eight) */
.status .sum{font:var(--sum-font-lg);color:var(--ink);margin-bottom:var(--space-sm-md)}   /* the money leads; numbers are MD IO by rule */
.status .when{font-size:var(--text-md);color:var(--ink);margin-bottom:var(--space-sm-md)}
.status .when b{font-weight:400;color:var(--ink2);margin-right:var(--space-sm)}   /* the label of the line, not a heading over the money */
.status .sub{font-size:var(--text-sm);color:var(--ink2);margin-bottom:var(--space-md-lg);line-height:1.6}   /* BOTTOM-ONLY: the sub-line owns the 14 to the facts under it, on either status */
.status dl{margin:0;display:grid;grid-template-columns:auto 1fr;column-gap:var(--space-lg);row-gap:var(--space-xs);font-size:var(--text-sm)}
.status dt{color:var(--ink2)}.status dd{margin:0;font-family:var(--mono);overflow-wrap:anywhere}
.status[data-state=paid] .when b{color:var(--acting)}

.side .head{padding:0 var(--space-xl) var(--space-sm-md);border-bottom:var(--line);font:var(--eyebrow-font);letter-spacing:var(--eyebrow-tracking);text-transform:var(--eyebrow-casing);color:var(--ink3)}   /* a row header for a run of cells: it draws the line under itself; every cell below draws its own bottom — so the run is closed by a rule whichever cells a state shows */
.side .run{padding-bottom:var(--space-xxxxl)}   /* BOTTOM-ONLY: a run owns the column's rhythm under it — eight units between sections */
.side .run:has(>.note:last-child,>.facts:last-child){padding-bottom:var(--space-xxl)}   /* an after-line or a facts block already carries four of them */
.door .run .head{padding:var(--space-xxl) var(--space-xl) var(--space-sm-md);border-bottom:var(--line);font:var(--eyebrow-font);letter-spacing:var(--eyebrow-tracking);text-transform:var(--eyebrow-casing);color:var(--ink3)}   /* the hub root's card-on-file row (Kickoff 8): the column's row header, under the invoices. FLAGGED: undesigned */
.side .note{padding:0 var(--space-xl) var(--space-xxl);font-size:var(--text-sm);line-height:1.6;color:var(--ink2)}
.side .note.after{font-size:var(--text-sm)}   /* the panel before it owns the 12 */

/* ── the picker: a standard radio group — native radios in a list of rows — and ONE panel beneath it that shows what the chosen
   method means (the details, then the action). The panel never moves; only its contents change. Platform only: :has(), by the
   chosen row's POSITION (twelve static rules — no per-id CSS, no script). ── */
.pick .opt{display:flex;align-items:center;gap:var(--space-md);height:var(--fh);padding:0 var(--space-xl);border-bottom:var(--line);font-size:var(--text-md);color:var(--ink);cursor:pointer}
/* the panel draws no top: the line over it is the last row's bottom (the run rule) */
.pick .opt:hover{background:var(--tint)}   /* hover swaps to the other ground — two tints, never a third */
.pick .opt:has(input:checked){background:#fff;font-weight:500}   /* the chosen thing is white, like Fixed | Per unit; medium is the one heavier weight the face has */
.pick .opt input{margin:0;width:1rem;height:1rem;accent-color:var(--acting);cursor:pointer}
.pick .opt:has(input:focus-visible){outline:var(--stroke) solid var(--ink);outline-offset:-.25rem}
.pick .opt .pref{margin-left:auto;font:400 var(--text-xs)/1 var(--ui);color:var(--ink3)}
.how{display:none;padding:0 var(--space-xl);margin-bottom:var(--space-md);font-size:var(--text-sm);line-height:1.6;color:var(--ink2)}
.how.open{display:block}   /* the styleguide's state */
/* EVERY WAY, SHOWN (issue #75, the payment center): no picker — each way a head and its lines, the next head drawing its rule; the lines end
   20 above it, as they start 20 under their own */
.ways.every .how{padding-bottom:var(--space-lg-xl)}
.ways.every .how+.head{border-top:var(--line)}
.pick:has(.opt:nth-child(1) input:checked)~.how[data-n="1"],.pick:has(.opt:nth-child(2) input:checked)~.how[data-n="2"],
.pick:has(.opt:nth-child(3) input:checked)~.how[data-n="3"],.pick:has(.opt:nth-child(4) input:checked)~.how[data-n="4"],
.pick:has(.opt:nth-child(5) input:checked)~.how[data-n="5"],.pick:has(.opt:nth-child(6) input:checked)~.how[data-n="6"],
.pick:has(.opt:nth-child(7) input:checked)~.how[data-n="7"],.pick:has(.opt:nth-child(8) input:checked)~.how[data-n="8"],
.pick:has(.opt:nth-child(9) input:checked)~.how[data-n="9"],.pick:has(.opt:nth-child(10) input:checked)~.how[data-n="10"],
.pick:has(.opt:nth-child(11) input:checked)~.how[data-n="11"],.pick:has(.opt:nth-child(12) input:checked)~.how[data-n="12"]{display:block}
.pick{margin-bottom:var(--space-lg-xl)}   /* BOTTOM-ONLY: the picker owns the 20 to its panel */
.pick:has(+.head){margin-bottom:var(--space-xxl)}   /* and a break to a head that follows it directly */
.how b{color:var(--ink);font-weight:500}
.how p{margin:0 0 var(--space-md)}
.how p:last-child{margin-bottom:0}
.how form{margin:0}

/* ── label and value as a pair: the line-height is the only space between them, like the studio's name over its bank ── */
.kv{margin:0 0 var(--space-lg-xl)}   /* BOTTOM-ONLY: a pair owns the 20 to the next */
.kv:has(+form){margin-bottom:0}   /* the form directly under its pair: the line-height is the only space */
.kv:has(+p){margin-bottom:var(--space-lg)}
.kv dt{font:500 var(--text-sm)/1.6 var(--ui);color:var(--ink)}
.kv dd{margin:0 0 var(--space-lg);overflow-wrap:anywhere}
.kv dd:last-child{margin-bottom:0}
.kv dd.c{display:flex;align-items:center;gap:var(--space-md);font-family:var(--mono);color:var(--ink);white-space:nowrap}
.kv dd.c button{margin-left:auto;padding:0;border:0;background:none;cursor:pointer;font:var(--action-font);letter-spacing:var(--action-tracking);text-transform:var(--action-casing);color:var(--acting)}
.kv dd.c button:hover{color:var(--ink)}
.kv dd.c .show{margin-left:auto;font:var(--action-font);letter-spacing:var(--action-tracking);text-transform:var(--action-casing);color:var(--acting);cursor:pointer;position:relative}
.kv dd.c .show:hover{color:var(--ink)}
.kv dd.c .show input{position:absolute;opacity:0;width:.0625rem;height:.0625rem;margin:0}
.kv dd.c .show+button{margin-left:0}
.kv dd.c .full,.kv dd.c:has(.show input:checked) .part,.kv dd.c:has(.show input:checked) .show .on{display:none}
.kv dd.c:has(.show input:checked) .full,.kv dd.c:has(.show input:checked) .show .off{display:inline}
.kv dd.c .show .off{display:none}

/* ── the Payment Element's place: Stripe's iframe renders its own labeled fields here, styled through the Appearance API into the
   app's field voice — label above, white field, THE line, --fh, no radius (pay.js reads the resolved tokens off this page). ── */
.pe{margin:0 0 var(--space-xl)}
.how p:has(+.pe){margin-bottom:var(--space-xl)}   /* the line before the Payment Element owns the 24 */
.pe:empty{display:none}
.pe .pe-noscript{margin:0;color:var(--ink2)}
.pe-error{margin:0 0 var(--space-md);color:var(--late);font-size:var(--text-sm)}

/* ── a KNOWN instrument as a field (pay.html: the saved bank account; slice 9b: the attached card too): label above, the line,
   the name in ink, CHANGE at the right in --acting — a form, since forgetting it is a POST. ── */
.pe-known{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-xl) var(--space-lg)}
.pe-known .f{margin:0}
.pe-known .f.w{grid-column:1 / -1}
.pe-known .f>span{display:block;font:var(--label-font);color:var(--ink);margin-bottom:var(--space-xs-sm)}
.pe-known .f>i{display:flex;align-items:center;justify-content:space-between;height:var(--fh);padding:0 var(--space-md);font:var(--row-font);font-style:normal;color:var(--ink);background:#fff;border:var(--line)}
.pe-known .f>i form{margin:0}
.pe-known .f>i button{padding:0;border:0;background:none;cursor:pointer;font:var(--action-font);letter-spacing:var(--action-tracking);text-transform:var(--action-casing);color:var(--acting)}
.pe-known .f>i button:hover{color:var(--ink)}

/* ── the fee disclosure (pay.html): what this card adds, before the client confirms. Rows in the paper's totals voice. ── */
.fee{margin:0 0 var(--space-xl);display:grid;grid-template-columns:1fr auto;row-gap:var(--space-xs-sm);font-size:var(--text-sm)}
.fee .k{color:var(--ink2)}.fee .v{text-align:right;font-family:var(--mono);color:var(--ink)}
.fee .t{padding-top:var(--space-sm);border-top:var(--line);font-weight:500}
.fee .t.v{font-size:var(--text-md)}
.how .nofee{margin:0 0 var(--space-xl);color:var(--ink)}
.pick .opt .add{margin-left:auto;font-family:var(--mono);font-size:var(--text-sm);color:var(--ink2)}   /* a fee knowable before the choice, on the row ("+ $5.00") */
.pick .opt .add+.pref{margin-left:var(--space-md)}

/* ── CONFIRM YOUR BANK (Kickoff 44, pay.html ▸ Verify): the run's ONE panel — no picker above it, so it shows by construction;
   the code (or the two amounts) as a field in the panel's own voice, CONFIRM as the acting cell; the run ends with the break. ── */
.run.verify .how{display:block;padding-bottom:var(--space-xxl)}
.run.verify .two{display:grid;grid-template-columns:1fr 1fr;gap:0 var(--space-lg)}
.run.verify .two .f{min-width:0}

/* ── the field voice inside the panel: Note (two rows at rest), the door's Email ── */
.hub .f{display:block;margin:0 0 var(--space-md)}   /* BOTTOM-ONLY: the field owns the 12 to what follows; the line before it owns the gap above */
.hub .f>span{display:block;font:var(--label-font);color:var(--ink);margin-bottom:var(--space-xs-sm)}
.hub .f>span em{font-style:normal;font-weight:400;color:var(--ink3);margin-left:var(--space-xs)}
.hub .f input{width:100%;height:var(--fh);padding:0 var(--space-md);font:var(--row-font);color:var(--ink);background:#fff;border:var(--line);border-radius:0}
.hub .f input:focus{outline:0;border-color:var(--ink);background:var(--highlight)}
.hub .f textarea{width:100%;min-height:calc(2 * 1.5 * .9375rem + 1.5rem + .25rem);padding:var(--space-md);font:var(--paragraph-font);color:var(--ink);background:#fff;border:var(--line);border-radius:0;resize:none;display:block;field-sizing:content}   /* two rows at rest (the min-height is two lines + padding + the line); grows with what's typed where the platform supports it */
.hub .f textarea:focus{outline:0;border-color:var(--ink);background:var(--highlight)}
.hub .f.err>span{color:var(--late)}
.hub .f.err input{border-color:var(--late)}
.hub .f .msg{display:block;margin-top:var(--space-xs-sm);font-size:var(--text-sm);color:var(--late)}
.hub .go{display:flex;align-items:center;width:100%;height:var(--fh);padding:0 var(--space-xl);border:0;background:var(--acting);color:#fff;cursor:pointer;
  font:var(--action-font);letter-spacing:var(--action-tracking);text-transform:var(--action-casing);white-space:nowrap}
.hub .go:focus-visible{outline:var(--stroke) solid var(--ink);outline-offset:.125rem}
.hub .go[disabled]{opacity:.6;cursor:progress}

/* ── the column of a DOCUMENT page (the proposal sitting, Sep 3; prototypes/proposal.html): the client's derived status + the dated
   facts — a headline (display 28) · one quiet line · a dl of mono date · fact (the client's slice of Activity, not a stream). The
   DEPOSIT run, only when pay-before-signing is on: one acting cell — Pay $2,500 · sign later — a submit of the sign form (its
   package rides along) whose amount derives from the choice like the paper's total (the generated rules); the quiet line under.
   Executed: PAY $n DEPOSIT leads the run into the invoice's ordinary pay page. THE SIGN BLOCK SIGNS; THE COLUMN PAYS. ── */
.status .big{font:400 var(--text-xxl)/1.1 var(--display);color:var(--ink);margin-bottom:var(--space-sm)}
.status .facts,.run .facts{display:grid;grid-template-columns:auto 1fr;column-gap:var(--space-lg);row-gap:var(--space-xs-sm);font-size:var(--text-sm);line-height:1.5}
.status .facts{margin:0}
.run .facts{margin:0;padding:var(--space-md) var(--space-xl) var(--space-xxl)}   /* the same grammar under a run's head — the deposit as a fact for the studio's own browser (staff mode, ruled Sep 23: a control exists only for the party who can act on it) */
.status .facts dt,.run .facts dt{font-family:var(--mono);color:var(--ink3);white-space:nowrap}
.status .facts dd,.run .facts dd{margin:0;color:var(--ink);font-family:var(--ui)}
.cell .n{font-family:var(--mono);letter-spacing:0;text-transform:none;color:var(--ink)}
.cell.go .n{color:#fff}
.cell .n span{display:none}   /* the deposit's amount: the generated rules show the chosen package's; "50%" until one is chosen */
main:has(.pk:has(input[type=radio]):not(:has(input:checked))) .side .cell.go[data-derived]{opacity:.35;pointer-events:none}   /* a derived deposit needs the choice first (ruled): shown by construction */
button.cell{font-family:var(--ui)}

/* ── a chrome cell: the document cells ── */
.cell{display:flex;align-items:center;width:100%;height:var(--fh);padding:0 var(--space-xl);border:0;border-bottom:var(--line);background:transparent;text-align:left;cursor:pointer;
  font:var(--action-font);letter-spacing:var(--action-tracking);text-transform:var(--action-casing);color:var(--ink2);list-style:none;transition:background-color .15s ease,color .15s ease}
.cell:hover{background:var(--tint);color:var(--ink)}   /* chrome cells take the frame's L and ink on hover */
.cell:focus-visible{outline:var(--stroke) solid var(--ink);outline-offset:-.25rem}
.cell.go{background:var(--acting);color:#fff}   /* the acting cell (proposal.html: APPROVE) — the one cell that is an action */
.cell.go:hover{background:var(--ink);color:#fff}   /* the acting cell's hover: ink, like the chrome's primary */
.cell.card{justify-content:space-between;cursor:default}   /* the card on file (Kickoff 8): a fact and its one action — the row is not a link; REMOVE is the Keep pattern, a POST cell. FLAGGED: undesigned */
.cell.card:hover{background:transparent;color:var(--ink2)}
.cell.card form{display:contents}
.cell.card button{font:inherit;letter-spacing:inherit;text-transform:inherit;background:none;border:0;padding:0;margin:0;color:var(--acting);cursor:pointer}
.cell.card button:hover{color:var(--ink)}

/* ── the flash, in the hub's voice (undesigned — FLAGGED) ── */
.hub .flash{padding:var(--space-md) var(--content-edge);background:var(--highlight);color:var(--ink);font-size:var(--text-sm)}
.hub .flash--error{background:var(--late);color:#fff}   /* ONE RED (Sep 8) */

/* ── the foot: the bar's twin — the same height (--bar over its line: 74, the bar's 74), the same voice, the same last cell. The studio's facts as equal centred cells with rules between; we are the last, quietest thing. ── */
.hub-foot{display:grid;grid-template-columns:minmax(0,1fr) var(--side);min-height:var(--bar);border-top:var(--line);background:var(--tint);font:var(--action-font);letter-spacing:var(--action-tracking);text-transform:var(--action-casing);color:var(--ink2)}
.hub-foot .cells{display:flex;min-width:0}
.hub-foot .cells span{flex:1 1 0;min-width:0;display:block;padding:0 var(--space-lg);min-height:var(--bar);line-height:var(--bar);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}   /* equal cells that ELLIPSIZE (a flex container never draws the ellipsis, so these are blocks with the row's line-height — the app footer's rule) */
.hub-foot .cells span+span,.hub-foot .cells .reach{border-left:var(--line)}   /* the cell after draws the rule between (the border rule) */
/* THE REACH IS ICONS, AND ONE CELL (ruled Sep 18): one 48-tall box, each icon a 48-wide link inside it, NO line between them — the lines are the foot's cells, and the socials are one fact. The network's mark is INLINE SVG from the one catalogue (Studio\Icons, Simple Icons, Sep 22) in the link's own color — the studio's tone, ink on hover; the handle is the link's name and title, never text */
.hub-foot .reach{display:flex;align-items:center;flex:none}
.hub-foot .ico{display:grid;place-items:center;flex:0 0 var(--fh);width:var(--fh);height:var(--fh)}
.hub-foot .ico svg{display:block;width:1.25rem;height:1.25rem}   /* the set's 24-unit box at 20 — every mark the same box, the set's own weights */
.hub-foot b{font-weight:500;color:var(--ink)}
.hub-foot b a:hover,.hub-foot .us a:hover{color:var(--ink)}
.hub-foot .us{display:flex;align-items:center;justify-content:center;gap:0 .5ch;min-height:var(--bar);border-left:var(--line);color:var(--ink3)}   /* exactly the column's width, centred; its rule is ITS left border, like the column's, so it's one line */
.hub-foot .us a{color:var(--acting)}

/* ── framed: on screen the paper sits inside the studio's frame, and the frame IS the letterhead — the logo slot is suppressed here and
   returns in print and in the studio's Preview, where the document stands alone. One document, one stylesheet per medium. ── */
.room .paper .p-logo{display:none}   /* every theme: the frame carries the mark */
.room .paper:is(.invoice.structure,.invoice.classic,.document) .p-from{border-left:0;padding-left:0}   /* the two themes with Structure's head, and the document that borrows it: no rule with nothing beside it */
.room .sheet{min-width:0}   /* the paper's column — and the paper's query container (paper.css: `:has(> .paper)`), so the paper answers to its own width, never the room's */
/* Minimalist here: the address alone on the head row — its studio block is the flex row with the logo hidden (paper.css carries the
   address in the head for every theme since Sep 16; the pay page's own Minimalist map of the morning is retired, it said the same). */

@media print{
  html,body.hub{background:#fff}   /* a printed page past the content is paper, never the frame's ground (12c) */
  .room .paper .p-logo{display:block}
  .room .paper:is(.invoice.structure,.invoice.classic,.document) .p-from{border-left:var(--pl);padding-left:var(--space-lg)}
}

/* ── the one breakpoint: the column goes above the paper (the client came here to pay) ── */
@media (max-width:53.4375rem){
  .hub-bar{grid-template-columns:auto minmax(max-content,1fr) minmax(min-content,auto)}   /* the crumb keeps its word; the session cell's email gives way (ellipsis) — the Kickoff 18 shoot at 600 clipped PAYMENTS; the cell's floor is its WORD (min-content — the .who grid above; Sep 17), never 0 */
  .crumb>:not(.back){display:none}.crumb .back{display:block}   /* stacked, the crumb is THE WAY BACK, one up — "‹ Invoices" on an invoice, "‹ Big Medium" on a list, NOTHING on the home or on a lapsed link (no parent to go up to) — Kickoff 24, ruled Sep 10 */
  .hub-foot{grid-template-columns:1fr}
  .hub-foot .cells{display:block}
  .hub-foot .cells span{display:flex;align-items:center;justify-content:center;line-height:inherit;white-space:normal}
  .hub-foot .cells span+span{border-left:0;border-top:var(--line);white-space:normal}
  .hub-foot .cells .reach{justify-content:center;border-left:0;border-top:var(--line)}   /* stacked, the icons stay ONE cell at 48, centred like the words */
  .hub-foot .us{border-left:0;border-top:var(--line)}
  .hub-foot .cells span,.hub-foot .us{min-height:var(--fh)}   /* stacked, the cells keep 48 */
  .room{grid-template-columns:minmax(0,1fr)}
  .side{order:-1;border-left:0;padding-bottom:0}   /* stacked, the column comes first; its last cell draws the line before the paper or the wide (the run rule — a bottom here would double it, and did, under the document page's DOWNLOAD PDF: issue #10); no air below either — it ends on the paper, not the footer */
  .room .paper{min-height:0}
}
/* ── THE PHONE (Sep 16 — the app masthead's rule of Sep 15, studio.css, at the hub's OWN number): the bar folds at 528, the widest one-row sum — the lockup box 288 (240 + its 24s) + the way back 116 ("‹ Big Medium" + its 24) + LOG OUT 124 (the word, its 24s, the gap) = 528; a cell change moves the fold with it. Narrower, the row cannot hold the three (at 375 a wide logo pushed LOG OUT off the frame), so THE LOCKUP TAKES ITS OWN ROW, centred as the app's is, and the crumb and the session cell are the row below; the frame's first row follows the bar's height. The app's 480 is the app's. ── */
@media (max-width:33rem){
  body.hub,body.hub.profile{grid-template-rows:auto auto 1fr auto}
  .hub-bar,body.profile .hub-bar{grid-template-columns:max-content minmax(min-content,1fr);grid-template-rows:var(--bar) calc(var(--bar) + var(--stroke));height:auto;top:calc(-1 * (var(--bar) + var(--stroke)))}   /* ONLY THE BAR STICKS (ruled Sep 22): the lockup row scrolls with the page — the sticky top sits one row (72 plus the line the cells draw over themselves) above the viewport, so what pins is the cells' row over the bar's line: 74, the wide bar's own height, never 148 */   /* Sep 17: the session cell is floored on its word and takes the rest; the way back keeps its word while it fits beside that word and gives way (its ellipsis) only past it — nothing on this row can push a cell's word off the frame */
  .hub-bar .session{justify-content:flex-end}   /* LOG IN at the frame's edge, where LOG OUT is */
  /* the map, two rows: the lockup's (72), then the cells' — 72 plus the line they draw above themselves */
  .hub-bar .studio{grid-column:1 / -1;justify-content:center;contain:inline-size}   /* inline-size containment: the row is sized by the two cells under it, never by the lockup spanning them — a spanning item's width (up to 288) fed the crumb's max-content column and pushed LOG OUT off the frame (measured at 375) */
  .crumb,.hub-bar .who,.hub-bar .session{border-top:var(--line)}   /* the second row draws the line over itself (the run rule: the lockup's row closes with a rule) */
  .crumb{border-left:0;padding:0 var(--space-xl)}   /* first on its row: the frame's edge is its line. THE BACK CELL (issue #79, Sep 29 — ruled on the worst case, ‹ PROPOSALS beside a 32-character address): it takes the bar's inset, 24 each side (it was sized to its text with none — "‹ INVOICES" ran to its line), and NEVER truncates — its column is max-content above; the email gives way (the ellipsis, as built) and LOG OUT is fixed */
  /* THE ROW HAS TWO SHAPES, DECIDED BY THE MARKUP (issue #79): with a .back link, back cell · who cell; without one (the hub root, whose crumb holds only the client's name — an empty 24px cell with a line at 375), the crumb is gone and the session cell spans the row — the email on the content edge, LOG OUT (or LOG IN) right, no line to its left: the frame's edge is its line */
  .crumb:not(:has(>.back)){display:none}
  .crumb:not(:has(>.back))+.who,.crumb:not(:has(>.back))+.session{grid-column:1 / -1;border-left:0}
  .hub-bar.lockup{grid-template-rows:var(--bar)}   /* THE UNKNOWN VIEWER (Sep 23): the lockup row alone, no second row — the sticky top above stays one row up, so with nothing under it the bar scrolls away whole: nothing to stick */
}

/* ── the styleguide: the hub's components render in the studio shell; give them the ground they expect ── */
.styleguide-sample .side,.styleguide-sample .status,.styleguide-sample .how,.styleguide-sample .pick,.styleguide-sample .cell:not(.go),.styleguide-sample .kv{background:var(--panel)}   /* the acting cell keeps its fill */
.styleguide-sample .how.open{padding-bottom:var(--space-lg-xl)}

/* ═══ THE PROFILE — `/` on a studio host, the public face of the hub (Kickoff 17, Sep 10; prototypes/profile.html, ruled Sep 6–7).
   Built on the hub's chrome: the bar, the foot, the tokens. Sections appear as the studio fills them; nothing is a mode.
   (The profile's frame rows and its bar — the session cell, LOG IN — sit with the bar's base rules above, AHEAD of the breakpoints: see there.) ═══ */

/* ── the hero: who · what, beside the cover. Two columns on the grid; the cover is the studio's, the pattern is its zero state. ── */
.hero{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);border-bottom:var(--line);background:var(--tint)}
.hero .who{padding:calc(10*var(--space-sm)) var(--content-edge) var(--space-xxxxl);display:flex;flex-direction:column;justify-content:center;min-width:0}
.hero h1{font:400 clamp(2.75rem,6vw,4.5rem)/1 var(--display);margin:0 0 var(--space-lg);letter-spacing:-.01em;overflow-wrap:anywhere}
.hero .line{font:500 var(--text-lg)/1.4 var(--ui);color:var(--ink2);margin:0}
.hero .cover{border-left:var(--line);min-height:26.25rem;background:var(--panel);position:relative;overflow:hidden}
.hero .cover img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
/* THE ZERO STATE: the legacy icon pattern (the old app's cover-pattern.svg — its `div.cover` masked the brand color through the same file), in the ACTING color on the panel ground. The SVG's shapes are 50% white, so the mask paints the pattern at half strength — the legacy look, by the same construction. */
.hero .cover.zero::before{content:"";position:absolute;inset:0;background:var(--acting);-webkit-mask:url(/images/cover-pattern.svg) center/cover no-repeat;mask:url(/images/cover-pattern.svg) center/cover no-repeat}

/* ── the card: the facts as a LIST in two columns — the app's list grammar (48 rows; caps label at a fixed inset, value beside it; every row draws
   its top, the second column its left — lines connect by construction, labels align by construction). What made the card tidy was sameness:
   every row the same shape (ruled Sep 7). `.fact`, not `.f` — .f is the form field, with its own margins; `.profile-card`, not `.card` — studio.css owns .card and the styleguide loads both sheets. ── */
.profile-card{border-bottom:var(--line);background:var(--tint)}
.profile-card .cols{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}
.profile-card .fact{display:grid;grid-template-columns:6.5rem 1fr;align-items:center;height:var(--fh);padding:0 var(--space-xl);border-top:var(--line);min-width:0}
.profile-card .fact:nth-child(-n+2){border-top:0}
.profile-card .fact:nth-child(2n){border-left:var(--line)}
.profile-card .fact:last-child:nth-child(odd){grid-column:1/-1}   /* an odd count: the last fact runs full width (ruled Sep 7) */
.profile-card .fact:hover{background:var(--panel)}   /* the chrome's ease: a row you can rest on */
.profile-card .l{font:var(--eyebrow-font);letter-spacing:var(--eyebrow-tracking);text-transform:var(--eyebrow-casing);color:var(--ink3)}
.profile-card .v{font-size:var(--text-md);color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.profile-card .v.mono{font-family:var(--mono);font-size:var(--text-sm)}
@media (max-width:47.5rem){.profile-card .cols{grid-template-columns:minmax(0,1fr)}.profile-card .fact:nth-child(-n+2){border-top:var(--line)}.profile-card .fact:first-child{border-top:0}.profile-card .fact:nth-child(2n){border-left:0}}

/* ── about: the paragraph in the UI face (Chris, Sep 10); the drop cap by the PLATFORM — `initial-letter: 3` sinks the first letter three lines, its
   top on the first line's cap height, its bottom on the third baseline, the browser doing the metrics (no hand-tuned size/line-height/margin) — in
   the display face and the LINE's color. Where unsupported, the letter simply stays in the run (graceful). Ascender alignment would want
   `initial-letter-align` (Safari only) — not worth the fragility (ruled Sep 7). ── */
.about{padding:var(--space-xxxxl) var(--content-edge);max-width:calc(var(--content) * .72 + 2*var(--content-edge));font:400 var(--text-md)/1.6 var(--ui);color:var(--ink)}
.about p{margin:0 0 1em}
.about p:last-child{margin-bottom:0}
/* the drop cap is all-or-nothing: a browser without initial-letter (Firefox) gets the plain paragraph, never a small muted F — the whole rule sits behind the @supports; the -webkit- prefix is Safari's */
@supports (initial-letter:2) or (-webkit-initial-letter:2){
.about p:first-child::first-letter{initial-letter:3;-webkit-initial-letter:3;font-family:var(--display);color:var(--field);margin-right:var(--space-md-lg)}
}

/* ── work: images only, on the lines; NO heading — the work speaks (ruled Sep 7; the line above is the section's). one = a wide cover; many = a grid.
   The 2px between images IS the line: one color, one width — each figure's 1px edge, meeting. ── */
.work{border-top:var(--line)}
.work .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(20rem,1fr))}
.work .grid figure{margin:0;background:var(--panel);aspect-ratio:4/3;overflow:hidden;border:var(--hairline) solid var(--field)}   /* each figure draws a 1px edge: neighbours meet at 2px — the line — and a partial last row leaves the ground, not a slab of line color (the prototype's grid background bled across empty tracks; seen in the shoot with four images)*/
.work .grid figure img{width:100%;height:100%;object-fit:cover;display:block}
.work .grid figure.wide{grid-column:1/-1;aspect-ratio:21/9}
.hero+.work,.profile-card+.work{border-top:0}   /* the hero and the card draw their own bottom: nothing is drawn twice */

/* the foot is the bar's twin (client.css's .hub-foot); the mailto there is the reach — the studio's facts are links in the acting color */
.hub-foot .cells a{color:var(--acting)}
.hub-foot .cells a:hover{color:var(--ink)}

@media (max-width:56.25rem){.hero{grid-template-columns:1fr}.hero .cover{border-left:0;border-top:var(--line);min-height:16.25rem;order:-1}}

/* ── THE DOOR's two states (Kickoff 17, Sep 10 — prototypes/hub.html ▸ the doors): the domain hint as <code> on the line; the wrong-person sentence in ink ── */
.door code{font:var(--figure-font);line-height:1;padding:var(--space-xs) var(--space-xs-sm);border:var(--line);color:var(--ink)}
.door .whoami{margin:0 0 var(--space-lg-xl);color:var(--ink)}
.door .whoami b{font-weight:500}
.door p b{font-weight:500;color:var(--ink)}
/* THE WHICH-ONE PAGE: the client names as room cells — the home's .room-cell (below), on the line. Plain, FLAGGED: not drawn. */
.door .rooms{border-top:var(--line)}   /* the line before it owns the 20 above (.door p) */

/* ═══ THE CLIENT'S HOME — `/{client}` (Kickoff 18, Sep 10; prototypes/hub.html, ruled Sep 10, its #proto layer promoted here). The pay
   page's two regions with the home's content: THE WIDE on the lighter ground, THE COLUMN on the deeper — tables in the wide, runs of
   rooms in the column. ═══ */
.wide{padding:var(--space-xxxxl) var(--content-edge);background:var(--tint);min-width:0}   /* the wide region: the lighter ground, the column the deeper — the pay page's pair */
.wide h1{margin:0 0 var(--space-sm-md);font:var(--title-font);color:var(--ink)}
.wide h1:has(+.hub-table){margin-bottom:var(--space-xxl)}
.wide .sub{margin-bottom:var(--space-xxl);color:var(--ink2)}
.wide .sub p{margin:0 0 var(--space-md)}   /* the welcome note's paragraphs — the door's block's own markup and rule (Kickoff 46: one renderer, no pre-line) */
.wide .sub p:last-child{margin-bottom:0}
.hub-table{margin-bottom:var(--space-xxl);border:var(--line)}   /* a list is a table: the box draws the outer rule, each row its own top (hub.html's `.wide section`, named so the styleguide can show it alone) */
.hub-table .head{display:flex;align-items:center;justify-content:space-between;min-height:var(--fh);padding:0 var(--space-xl);background:var(--panel);font:var(--eyebrow-font);letter-spacing:var(--eyebrow-tracking);text-transform:var(--eyebrow-casing);color:var(--ink3)}
.hub-table .head a{color:var(--acting)}
.hub-table .head a::after{content:"\a0▸"}
.hub-table .head a:hover{color:var(--ink)}
/* a row: the thing · its room · the amount (right) · the status — minmax(0,…) so every row resolves the same tracks; NOT client.css's .cell (the pay picker's button) */
.hrow{display:grid;grid-template-columns:minmax(0,4fr) minmax(0,3fr) minmax(0,2fr) minmax(0,3fr);column-gap:var(--space-xxl);align-items:center;min-height:var(--fh);padding:0 var(--space-xl);border-top:var(--line);color:var(--ink)}
a.hrow:hover{background:var(--panel)}   /* a row is a link: hover swaps to the other ground — two tints, never a third */
.hrow .n{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.hrow .tag{justify-self:start;display:block;height:1.5rem;padding:0 var(--space-sm);border:var(--line);font:var(--eyebrow-font);line-height:1.375rem;letter-spacing:var(--eyebrow-tracking);text-transform:var(--eyebrow-casing);color:var(--ink2);white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}   /* the room as a small SQUARE cell of the line — the system has one shape. A BLOCK, its line the box less the rule (ruled Sep 23, issue #3): text-overflow never applies to a flex container's anonymous item, so a long project name clipped mid-word; as a block it ellipsizes and the row holds at 48 */
.hrow .amt{justify-self:end;font-family:var(--mono);font-size:var(--text-sm);color:var(--ink)}
.hrow .st{font-size:var(--text-sm);color:var(--ink3);white-space:nowrap}   /* the status: quiet when it's history, ink when it's now */
.hrow.due .st{color:var(--ink);justify-self:start;background:var(--highlight);padding:var(--space-xs) var(--space-xs-sm);margin-left:calc(-1 * var(--space-xs-sm));box-decoration-break:clone;-webkit-box-decoration-break:clone}   /* the "now" states as a mark in the studio's highlight — under the words only */
.hrow.late .st{color:var(--late)}   /* overdue: the one color that isn't the brand's */
/* AN ASK ENDS IN ITS VERB (issue #76, ruled Sep 29): a Needs row's last cell is the verb with the arrow in the caps voice the studio's
   SEND → uses — the word, then the arrow — as the row's own words inside its link. TEXT, NEVER A BOX (Chris, Sep 29): the row is the one
   door; a bordered box inside it reads as a second control (the studio's SEND → is a cell in a list of cells). One width for every verb,
   so the track is the same on every row. THE OFFSET IS ACCEPTED: Needs is a list of asks and earns the column; Invoices and Documents are
   records in their own boxes and carry no empty column to line up with it. The state stays where it was; the verb is added. */
.needs .hrow:has(.verb){grid-template-columns:minmax(0,4fr) minmax(0,3fr) minmax(0,2fr) minmax(0,3fr) calc(5*var(--u))}
.hrow .verb{justify-self:end;font:var(--eyebrow-font);letter-spacing:var(--eyebrow-tracking);text-transform:var(--eyebrow-casing);color:var(--ink2);white-space:nowrap}
.hrow .verb::after{content:"\a0\2192";letter-spacing:0;color:var(--ink)}
a.hrow:hover .verb{color:var(--ink)}
.hub-table .empty{padding:var(--space-lg) var(--space-xl);border-top:var(--line);color:var(--ink3)}
/* the payment center's rows in a table box (Kickoff 18): the picker's head in the table's voice, the panel closed by its own air */
.hub-table .pick{border-top:0}
.hub-table .how{padding-bottom:var(--space-lg-xl)}
.hub-table .how.open{display:block}
/* the column: the sum, then runs of rooms — client.css's .status and .side .head; the room cell (name over what's inside) */
.room-cell{display:block;padding:var(--space-sm-md) var(--space-xl);border-bottom:var(--line);color:var(--ink)}
a.room-cell:hover{background:var(--tint)}   /* a cell with a page is a link and hovers; a room with no page yet (the projects — RAISED) sits still */
.room-cell b{font-weight:500}
.room-cell span{display:block;font-size:var(--text-sm);color:var(--ink3);margin-top:var(--space-xxs)}
.side>.head:first-child{padding-top:var(--space-xxxxl)}   /* a column that opens on a run (no sum to see — a by-project member): the same 8u the status would have given it */
.side .status+.head{border-top:0}
.side .room-cell:has(+.head){margin-bottom:var(--space-xxxxl)}   /* two breaks between runs — the column's one rhythm (the card on file's run counts as one) */
@media (max-width:53.4375rem){
  .room{grid-template-rows:auto 1fr}   /* stacked: the column's row is its content, the wide takes the frame's rest — auto rows had been sharing the free space, leaving air under the column's last cell (measured 17px in the shoot) */
  .hrow{grid-template-columns:1fr;row-gap:var(--space-xs);padding:var(--space-md) var(--space-xl)}
  .hrow .amt{justify-self:start}
  .needs .hrow:has(.verb){grid-template-columns:1fr}
  .hrow .verb{justify-self:end}   /* the verb is the row's last line, on its right edge (issue #76) */
  /* the column draws NO bottom here either (issue #10, Sep 24): the frame's rule above holds — its last cell draws the line before the wide (a room cell has one; a border on the column doubled it under DOWNLOAD PDF and the pay cells) */
}
/* THE DOOR'S WELCOME (hub.html ▸ the doors): the studio's note to this client, when one is set — the light ground, not white (white would
   read as a field). Promoted with the layer; nothing renders it yet: where the note lives is RAISED (Kickoff 18). */
.door .welcome{padding:var(--space-xl);border:var(--line);background:var(--tint);margin-bottom:var(--space-xxl)}
.door .welcome p{color:var(--ink);margin:0 0 var(--space-md)}
.door .welcome p:last-child{margin-bottom:0}
