/* ============================================================================
   editorial-frame.css — bylineAL tool-shell + DeskCopy Pro proof skin
   The corporate.css editorial language (cream paper, rule-lines, typeset CTA,
   nib masthead) re-expressed as a themeable shell (Day / Night / Press) plus
   every DeskCopy Pro view class re-housed in that language.

   VIEW ONLY: no JavaScript was touched. The external JS renders output using
   the original DeskCopy Pro class names (.copy-headline, .verdict-bar,
   .chk-item, .calibration-flag, .editors-note-row, .diag-points …) — so every
   one of those is restyled here under its original name.

   British spelling. No em-dashes in source comments or UI copy.
   ============================================================================ */

/* Font request. Three rules, learned the hard way (2026-07-27):
   1. CSS2 requires axis tags in SORTED order. "opsz,wght,ital" is unsorted, so
      the family 400s on its own and is SILENTLY DROPPED from a combined 200
      response. Newsreader never loaded on any page from any sheet until now.
   2. Schibsted must be requested as a RANGE (400..800), not a semicolon list.
      A list serves discrete static instances, of which only 400 and 700 ever
      download, and every intermediate weight in the ramp renders as one of them.
   3. Fraunces is gone (de-tell 2026-07-14). It was still fetched render-blocking
      on every page and referenced by nothing.
   Verify with: curl the URL and grep for font-family: 'Newsreader'. Do not
   check by eye — the Iowan Old Style fallback passes a glance.
   Guarded by tests/test_font_requests.py. */
@import url('https://fonts.googleapis.com/css2?family=Schibsted+Grotesk:wght@400..800&family=Newsreader:ital,opsz,wght@0,6..72,200..800;1,6..72,200..800&family=JetBrains+Mono:wght@400;500;700&display=swap');

/* ============================================================
   THEME TOKEN SETS — corporate vocabulary
   :root = Day. [data-theme="night"] / [data-theme="press"] override.
   All colour flows through these tokens; no hard-coded colours below.
   ============================================================ */

:root {
  /* NINE TEXT SIZES for the whole product, replacing fifteen. Eight of them
     applied 2026-08-10 from the styling preview; the ninth added 2026-08-12 on
     Ritu's ruling. This is the LAST step to touch, because it is the only one
     that can change where a word breaks.

     The fifteen fractional sizes had one cause and it was not a second scale:
     the root font is 18px and sizes were written in rem, so 0.7rem is 12.6px.

     THE NINTH SIZE CLOSES THE ONE BROKEN STEP. The scale used to run 40px
     straight to 68px, a jump of 1.7 where every other step is 1.2 to 1.45.
     40 to 48 is 1.20 and 48 to 68 is 1.42. It cost no renaming: --t-8 held 68
     and not one declaration in the product used it, so 48 took that name. 48 is
     not an invented number either. It is where the front door's headline stops
     growing, 2.6rem or 46.8px, which was the one headline end that landed on
     nothing.

     A HEADLINE IS NOT A SIZE HERE. It is a ramp that grows with the window, and
     it stays one: h1 keeps its clamp, and only the two ENDS of that clamp read
     tokens. Freezing a headline onto a fixed step is what turned eight of them
     from one line into two when this was measured on 2026-08-10. See
     --track-hero below, and the hero step in preview/build-precision.mjs. */
  --t-1:         12px;
  --t-2:         14px;
  --t-3:         16px;
  --t-4:         19px;
  --t-5:         23px;
  --t-6:         28px;
  --t-7:         40px;
  --t-8:         48px;
  --t-9:         68px;
  /* SEVEN SPACING STEPS for the whole product, replacing thirteen. Applied
     2026-08-10 from the styling preview.

     Values under 4px are left alone: below the base step there is no step to
     round to, and a 1px nudge is usually doing optical work a scale should not
     overrule.

     INLINE padding and margin are only ever REDUCED, never raised. Raising them
     narrows the column the text is set in, and a narrower column is how a
     paragraph gains a line. Measured: rounding to nearest added a line to five
     paragraphs on corporate and social at 390. See inlineAt() in
     preview/build-precision.mjs for which positions of a shorthand count as
     inline. */
  --s-1:         4px;
  --s-2:         8px;
  --s-3:         16px;
  --s-4:         24px;
  --s-5:         40px;
  --s-6:         64px;
  --s-7:         104px;
  /* The x-height match for the mono-to-sans move. MEASURED, not guessed:
     JetBrains Mono renders an x-height of 0.55 em and Schibsted Grotesk 0.527,
     so giving the sans the mono's ratio scales it up about 4.3% and a 12px
     label renders at 12.52px with the two faces sitting on one x-height. See
     preview/x-height.json.

     A number and not the from-font keyword: the number is written down, it can
     be checked against that file, and it does not change silently when a font
     file is updated. */
  --x-adjust:    0.55;
  /* TWO TRACKING VALUES for the whole product, replacing ten. Applied
     2026-08-10 from the styling preview.

     --track-caps is for text the sheet also sets in uppercase. Capitals need
     the air; a sentence does not, which is why positive tracking on mixed-case
     text goes to plain normal here rather than to 0.16em. That would be grotesque
     on a sentence, and 26 selectors were carrying it.

     --track-tight is the only negative value. h1 keeps its own: loosening
     DeskTriage's and FieldReportage's headline from -0.03em to -0.01em wrapped
     both onto two lines, and the line count wins.

     NOT DONE HERE: the trailing letter-space. CSS puts the tracking after the
     LAST character too, so a centred or right-aligned capped label sits half a
     tracking left of where it should. Cancelling it means adding a negative
     margin to 160 rules, which is a second decision and gets measured on its
     own. */
  --track-tight: -0.01em;
  --track-caps:  0.16em;
  /* ONE TRACKING VALUE FOR EVERY HEADLINE. Applied 2026-08-12.

     The product carried four: -0.03em on the tool pages, -0.03em on editorial
     and corporate, -0.025em on the front door, -0.02em on social. This is the
     TIGHTEST of them, and tightest is the safe direction: loosening
     DeskTriage's and FieldReportage's headline from -0.03em to -0.01em wrapped
     both onto two lines when the tracking step was measured on 2026-08-10.
     Tightening cannot add a line.

     It is separate from --track-tight because that is the body's value,
     -0.01em, and a 68px headline and a 16px label do not want the same one. */
  --track-hero:  -0.03em;
  /* ONE RULE WEIGHT for the whole product: every divider in the five sheets
     reads this token instead of carrying its own number.

     THE VALUE IS 1px, AND 0.5px WAS TRIED FIRST AND MEASURED. In Chrome a
     0.5px border occupies exactly the same space as a 1px one and reports as
     1px, at BOTH display densities: a 40px-tall box with 0.5px borders and one
     with 1px borders both measure 42px. Ritu's ruling of 2026-08-10 is that
     Chrome is the only engine in scope, so a half-pixel here is a number that
     looks like a decision and paints like nothing. It would also halve every
     divider on the day some future Chrome starts honouring it, with nobody
     deciding that.

     The point of the token is that the next person changes ONE number, not 141.
     Guard: tests/test_css_architecture.py::HairlineTokenTest. */
  --hairline:    1px;
  /* Day — WARM PAPER, 2026-08-10. Ritu's pick from the styling preview, and DAY
     ONLY: "warm is messing up Night and Press". Night keeps its near-black
     ground and Press keeps #F4EFE5, which is already warmer than this, so
     overriding Press would have cooled the one theme that was already warm.
     This moves Day towards Press rather than away from it.
     The three move together. Changing only --paper leaves every panel and
     inset box cool grey on a warm page. Prior values, teal-biased grey from the
     de-tell of 2026-07-14: #E9EDED / #E4E9E9 / #EDF0F0. */
  --paper:       #F2F0EA;
  --paper-deep:  #EBE8E0;
  --paper-warm:  #F7F5F0;
  --ink:         #1A1717;
  --ink-soft:    #2F2A26;
  --quiet:       #5C544C;
  --rule:        #C9C3B7;
  --rule-soft:   #E2DCCF;
  --accent:      #156169;
  --accent-deep: #082A4C;
  --mark:        #76510F;
  --good:        #465F36;
  --warn:        #8B4517;
  --brand-rule:  #082A4C;
  --cta-fill:    #082A4C;
  --cta-ink:     #FBFAF6;
  /* Reading weight: Newsreader's 400 reads faded on the light canvases over
     long text (Ritu, 2026-07-20). Light themes carry a heavier book weight;
     night stays 400 (light-on-dark already blooms). */
  --body-wght:   450;
  --btn-ink:     var(--accent-deep);   /* quiet-button text; night overrides brighter */          /* text on the CTA fill */
  --overlay-bg:  rgba(20,17,17,.5);
  --value-mark:  #FBF1CC;          /* value-add highlight wash */

  --font-display: "Schibsted Grotesk", system-ui, -apple-system, sans-serif;
  --font-body:    "Newsreader", "Iowan Old Style", Georgia, serif;
  --font-mono:    "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --measure: 62ch;
  /* Side margin. Was clamp(1.25rem, 3.5vw, 2.5rem), which capped at 45px and so
     stopped growing at 1286px. Above the frame's cap the page is centred and
     looks framed; below it this margin is all there is, and at 1280-1366 it
     collapsed to 44px and the page sat against the glass. Ritu, 2026-08-03: "on
     my narrower office laptop they don't look as good... it should be ideally
     aesthetic on all kinds of laptops." Measured, wordmark inset: 1512 -> 100,
     1440 -> 64, 1366 -> 44, 1280 -> 44. The cliff is at 1386, where the cap
     stops applying. A 14in Windows laptop at 150% scaling reports 1280, which is
     the commonest non-Mac desktop width there is. Now proportional at 5vw, so
     1366 gets 68 and 1280 gets 63. Above the frame's cap NOTHING MOVES: at 1512
     and wider the inset is set by the cap, not by this. */
  --gutter:  clamp(1.5rem, 5vw, 5rem);
  /* One frame for the whole product (spec 2026-08-03 §5A). Ritu's ruling: the
     front door is the standard, so this is the front door's column, and every
     masthead inset and every primary content column derives from it. Declared
     identically in suite.css and social-dark.css because no stylesheet is
     loaded by every page; tests/frame-token.test.js keeps the three in step.
     NOTE the real size: the root font is 18px, so 72rem is 1296px, not the
     1152px a 16px root would give. The plan and the first mockup both carried
     the 16px numbers and they were wrong. --shell was 1476px, not 1312px. */
  --frame:   min(72rem, 100% - var(--gutter) * 2);
  /* Prose cap inside the wide frame (spec §5A). Deliberately NOT --measure.
     --measure is 62ch, measures 572px in the real face, caps the rewrite OUTPUT
     column, is documented in docs/DESIGN-SYSTEM.md and is read by six rules
     including .alt-body. The plan proposed redefining it to 38rem, which
     measures 684px, and that would have widened the column Ritu reads most by
     112px as an unintended side effect. New name, no side effect. */
  --prose:   min(38rem, 100%);
  /* RETIRED 2026-08-03: --shell was this sheet's own 82rem column and is now the
     product frame. Kept as an alias so anything the sweep missed lands on the
     one frame rather than a stale width. */
  --shell:   var(--frame);
  /* The config panel's width. A TOKEN, not a number, because a client
     environment rebrands by changing tokens (spec R7). 18rem = 324px at this
     product's 18px root, and it is constant from 950px upward on every tool:
     below that the structure changes rather than the width shrinking. It is a
     fixed width on purpose, so the widest option is measured once instead of
     depending on how many controls a tool happens to carry. */
  --rail:    18rem;
  --col:     min(58rem, 100%);
  --touch-min: 44px;
  /* Ritu's ruling, 2026-07-28: 44px is the floor for CONTROLS; inline and footer
     text links take the AA target size (2.5.8), which also exempts inline links
     in a sentence outright. Declared here as well as in suite.css and
     social-dark.css — the tool pages load none of those, so the first attempt at
     the footer-link floor referenced an undefined var and silently did nothing
     while looking correct in the stylesheet. */
  --touch-link: 24px;
  /* The one edge every phone rule reads instead of inventing its own. 699.98px
     rather than 700px so a max-width query and a min-width query at 700px
     never both match the same width. See THE PHONE LAYER at the foot of this
     file. */
  --phone-max: 699.98px;

  /* Shape — buttons are capsules (2026-07-12 pill pivot). Reached only via
     var(--pill). See DESIGN-SYSTEM.md. */
  --pill:    999px;
  /* Ritu's ruling, 2026-08-07: "I want all boxes across the project to be
     curved at the edges", scoped by his follow-up to "ALL INPUT AND OUTPUT
     BOXES", with the Claude composer as the reference. This supersedes the
     radius-0 half of the 2026-07-12 shape law for those two families ONLY —
     panels, cards, popovers and tags are untouched and still sharp. A token,
     never a literal, for the same reason --pill is one: the shape stays
     centrally owned, and the drift gate only ever flags raw pixel radii. */
  --box:     16px;

  /* THE PRESS. What a main button does the moment you click it.

     Ritu, 2026-08-17: "Across the project, the one problem that hasn't been
     solved is the buttons, the main buttons, whether it is hook me or rewrite.
     The moment I click, the buttons should feel alive, but that is not the
     case right now." Then, ruling OUT turning the button into a progress
     indicator: "What I wanted was not for the rewrite button itself to get
     activated, but at least feel alive when I click it. Just for a split
     second, almost like blinking." Then: "Do it for the entire project."

     The product already pressed. Six rules in four sheets each carried
     `:active { transform: scale(0.98) }` on a flat 120ms ease-out, typed out by
     hand six times, and he could not feel any of them. Two things were wrong.

     DEPTH. 0.98 is two per cent, the weakest end of the 0.95-0.98 a press is
     drawn from. --press-deep is 0.955.

     RELEASE. A flat 120ms falls and returns at the same rate, which is not
     what a key does under a finger: it goes down fast and comes back carrying.
     85ms down, 200ms back, on an ease-out strong enough to read at that
     length. Never ease-in, which delays the first movement, the exact moment
     the eye is on the button.

     WHY NOT A BLINK, which is his own word, and which he liked. Both were
     built and measured on 2026-08-17 (the bench published that day carries
     them side by side). An opacity dip to 0.4 takes the Rewrite button's ink
     from 24 to 154 and its contrast against the paper from 16.66:1 to 2.44:1.
     Decisively: OPACITY IS ALREADY TAKEN IN THIS PRODUCT AND IT MEANS DEAD.
     .hook-go:disabled is 0.5, .hist-btn:disabled is 0.35, .fidelity-btn is
     0.6. A blink at 0.4 lands inside that band, on the one click that is
     immediately followed by the button really going disabled, so the two would
     run together and read as a single fade into dead. Nothing in this product
     uses scale to mean anything, so the dip collides with nothing.

     TWO DEPTHS, ONE CURVE. A main action takes --press-deep. Secondary
     controls (Clear, the door tabs, the fact bar) keep the depth they had in
     --press-light and take the same curve, so the whole product releases alike
     and only the loudest control got louder.

     DECLARED HERE AND IN suite.css, not once in icons.css, even though
     icons.css is the only sheet every surface loads. A sheet that USES a token
     DECLARES it, because an unresolvable var() does not fall back to the old
     value, it voids the whole declaration and the press disappears silently.
     scroll-desk.css reads these because it always loads after this sheet;
     corporate.css reads suite.css's copy for the same reason. Both orderings
     are proven, not assumed, by tests/test_css_architecture.py::SharedPressTest. */
  --press-deep:  0.955;
  --press-light: 0.98;
  --press-in:    85ms;
  --press-out:   200ms;
  --press-ease:  cubic-bezier(0.23, 1, 0.32, 1);
}
/* The product's own ruling on reduced motion, first written at
   scroll-desk.css:251 and now said once for every sheet that presses: "The
   press state itself stays. It is a state, not an animation: with the
   transition gone it snaps, which is what this setting asks for." A press must
   still answer; it just stops travelling. */
@media (prefers-reduced-motion: reduce) {
  :root { --press-in: 0s; --press-out: 0s; }
}

[data-theme="night"] {
  /* Night — warm dark; primary action leads with teal */
  --paper:       #16171A;
  --paper-deep:  #1F2024;
  --paper-warm:  #1F2024;
  --ink:         #ECE7DC;
  --ink-soft:    #C3BDB0;
  --quiet:       #989185;
  --rule:        #34353A;
  --rule-soft:   #2A2B2F;
  --accent:      #56B6BF;
  --accent-deep: #1E4F80;
  --mark:        #CBA15A;
  --good:        #86A86E;
  --warn:        #D98C53;
  --brand-rule:  #3576B5;
  --cta-fill:    #56B6BF;
  --cta-ink:     #16171A;
  --overlay-bg:  rgba(0,0,0,.62);
  --value-mark:  rgba(203,161,90,.20);
  --body-wght:   400;
  --btn-ink:     var(--accent);        /* accent-deep is near-invisible on the dark canvas (Ritu, MacBook night check) */
}

[data-theme="press"] {
  /* Press — the warm cream, kept distinct from grey Day; the warm reading mode */
  --paper:       #F4EFE5;
  --paper-deep:  #ECE5D6;
  --paper-warm:  #FAF6EC;
  --ink:         #211C17;
  --ink-soft:    #36302A;
  --quiet:       #5F574E;
  --rule:        #C9C3B7;
  --rule-soft:   #E2DCCF;
  --accent:      #156169;
  --accent-deep: #082A4C;
  --mark:        #76510F;
  --good:        #465F36;
  --warn:        #8B4517;
  --brand-rule:  #082A4C;
  --cta-fill:    #082A4C;
  --cta-ink:     #F4EFE5;
  --body-wght:   435;
  --btn-ink:     var(--accent-deep);
  --overlay-bg:  rgba(33,28,23,.52);
  --value-mark:  #FBF1CC;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* THE BROWSER'S OWN FLASH IS SILENCED, EVERYWHERE, 2026-09-10. On a tap the
   browser paints its own grey or blue box over the element, independent of
   any CSS the page authors, and measured on this product's own controls it
   is rgba(0, 0, 0, 0.18): a control that already carries the house press
   answered a single tap with two things at once, the product's own dip and a
   flash that belongs to nobody. `touch-action: manipulation` drops the
   engine's wait for a possible second tap, which otherwise held every click
   here for up to 300ms. Applied by element and role rather than by class, so
   a control built later inherits it without needing to remember to ask. */
button, select, summary, [role="button"], [role="tab"], a {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* This sheet had NO global focus ring — only per-component :focus-visible rules,
   so anything they did not name fell through to nothing. The dropdowns were the
   visible casualty: .toggle-row select set `outline: 0` on :hover AND :focus
   together, so the keyboard style WAS the hover style and a keyboard user could
   not see where they were. Same defect as the landing pages' finding 4, which
   suite.css:83 fixed on 2026-07-27; the tool sheet was never touched because the
   review did not cover these pages.

   Negative offset so the ring sits inside the element and cannot be clipped by
   the overflow-x:hidden on body. --btn-ink resolves per theme (accent-deep on
   Day/Press, accent on Night) so the ring stays visible on all three. Component
   rules that set their own outline still win on specificity. 2026-07-28. */
:focus-visible {
  outline: 2px solid var(--btn-ink);
  outline-offset: -2px;
}

/* Every page's first tab stop. Off-screen until focused. Ported verbatim in
   behaviour from suite.css:89, which the four landing pages have carried since
   2026-07-27; none of the ten tool pages had one (design review, finding 26).
   The navy fill is --cta-fill rather than suite.css's --accent-deep because this
   sheet themes: on Night, navy on navy would be invisible. 2026-07-28. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--cta-fill);
  color: var(--cta-ink);
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-2);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  text-decoration: none;
  padding: var(--s-3) var(--s-3);
  border-radius: var(--pill);
}
.skip-link:focus {
  left: var(--gutter);
  top: 0.6rem;
}

/* The scrollbar is part of the frame. A page long enough to scroll loses ~15px
   of viewport to the scrollbar; a short one does not. Since every masthead inset
   is derived from the viewport, that difference lands half on each side and the
   wordmark shifts 7.5px between a long page and a short one. Measured
   2026-08-03: HookCraft (short, no scrollbar) sat 7.5px off every other surface
   at 1920, 1600 and 1440 while its CSS was byte-identical. Ritu ruled it holds
   the space everywhere. Reserving it costs an empty strip on short pages, which
   is page background, not a visible bar. */
html { scrollbar-gutter: stable; }

html, body {
  min-height: 100vh;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  /* Was a fixed 16px while all four landing pages ran
     clamp(16px, 0.6vw + 14px, 18px), so every rem-based size on a tool page
     rendered 12.5% smaller than the page that links to it: click Editorial ->
     DeskCopy on a desktop and the type shrank. Measured 2026-07-28, root size:

        landing pages   16.34px @390   18px @768   18px @1440
        tool pages      16.00px @390   16px @768   16px @1440

     Ritu's ruling: "Maintain text size symmetry (should look natural on
     mobile)". The same ramp does both — at 390 it moves 16.00 -> 16.34px, a 2%
     change nobody will see, and only on wide screens does it open up to meet
     the landing pages. 2026-07-28. */
  font-size: clamp(16px, 0.6vw + 14px, 18px);
  line-height: 1.6;
  font-feature-settings: "ss01", "onum";
  /* Reading weight is set as font-weight, NOT as a variation axis.
     font-variation-settings INHERITS, and on a variable font its weight axis
     beats font-weight on every descendant. Declared as an axis here, it made
     all 65 rules in this sheet that set font-weight >= 600 without an axis of
     their own render at body weight instead. Measured 2026-07-27 on DeskCopy at
     1440px: "Handgloves in the newsroom" at 24px ran 287.86px under
     font-weight:700 and 287.86px under axis 450 — identical — against 304.69px
     for a real 700. font-weight cascades normally, so children override it.
     Rules that need a specific axis still set font-variation-settings and still
     win; see h1.display. Banked as open-findings-2026-07-26.md 1. */
  font-weight: var(--body-wght);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background 240ms ease, color 240ms ease;
}

a { color: inherit; text-decoration: none; }

/* Subtle paper texture — barely there. Suppressed in Night (dark grain reads as noise). */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background-image:
    radial-gradient(rgba(26,23,23,0.018) 1px, transparent 1px),
    radial-gradient(rgba(26,23,23,0.012) 1px, transparent 1px);
  background-size: 7px 7px, 13px 13px;
  background-position: 0 0, 3px 4px;
  pointer-events: none;
  z-index: -1;
}
[data-theme="night"] body::before { display: none; }

/* ============================================================
   CHROME — masthead (liftable block)
   The masthead surface is ALWAYS the brand cream (#FBFAF6) regardless
   of the active theme. Only the working body below themes. The logo
   renders identically across Day, Night, and Press — no invert filters.
   ============================================================ */

/* Full-bleed masthead strip — brand cream, always, across all themes. */
/* align-items was `baseline` until 2026-07-28, with the note "wordmark + marks
   share a baseline". They never did. The wordmark is an image, whose baseline is
   its bottom edge, and the controls beside it are flex capsules whose baseline
   is the text inside 44px of padding — so the row was pinned on two measures
   that do not correspond. Measured on DeskCopyPro at 390px, the masthead stood
   159px tall with the controls hanging below the mark. Same defect and the same
   fix as suite.css:136 on the landing pages, found the same day. */
.tool-masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
  width: 100%;
  max-width: 100%;
  /* The masthead stays full-bleed (its background and its navy rule run edge to
     edge) but its CONTENTS sit on the content column.

     This line read `padding: 1.6rem var(--gutter) 1.1rem` with the comment
     "side padding mirrors --gutter so content aligns with the main column". That
     was true only while --shell was still gutter-bound. --shell caps at 82rem
     and then centres, so above ~1360px the column moves inward while a fixed
     40px gutter does not. Measured on DeskCopyPro, wordmark vs h1:

        768-1360   aligned
        1440       wordmark 40, content 57    17px out
        1600       wordmark 40, content 137   97px out
        1800       wordmark 40, content 237  197px out

     1440 is the commonest laptop width and 1600/1800 are ordinary external
     monitors, so the mark hung outside the page's own column on most desktops.
     Deriving the inline padding from --shell itself keeps the two locked
     together at every width, and collapses to exactly --gutter below the cap,
     which is what the old comment believed was happening. Ritu asked what about
     the desktop tools, 2026-07-28; this is what.

     2026-08-03: --shell became --frame here. The masthead is brand chrome and
     its inset is now a product-wide constant (spec §5A), not this sheet's own
     column. Measured before the change: the wordmark sat 216px further in on
     editorial.html than on a tool at 1600. */
  padding: 1.6rem calc((100% - var(--frame)) / 2) 1.1rem;
  background: var(--paper);      /* follow the page so masthead and body match (was fixed cream) */
  border-bottom: 2px solid #082A4C; /* navy brand rule — does NOT theme */
}

/* Only DAY is uniform (masthead follows the grey body). PRESS and NIGHT keep the cream
   chrome masthead, distinct from the body — exactly as the original tool behaved. */
[data-theme="night"] .tool-masthead,
[data-theme="press"] .tool-masthead { background: #FBFAF6; }

/* ...and NIGHT must take the daylight tokens with it.
   The rule above flipped the GROUND to cream and flipped nothing else, so on
   night the whole masthead kept light-on-dark values and painted them on a light
   strip. Measured on the rendered night masthead, 1440x900, 2026-08-05, by
   scripts/mark_contrast_check.mjs:
       "Settings" label            1.91:1   (needs 4.5)
       theme toggle Night / Press  1.18:1   (needs 4.5)
       the Settings gear mark      2.27:1   (needs 3.0)
   Ritu saw the gear go missing; the words beside it were worse.
   PRESS needs nothing and is deliberately left out: its tokens are already
   daylight values, and it measures 12.46:1 and 6.79:1 on the same strip.

   This is the idiom social-hook.css:86-100 already established for HookCraft's
   masthead, in its own words: "The strip is a daylight island, so it takes
   daylight values." Same problem, same shape, rather than a fourth naming
   scheme for it. Only the strip is rescoped; the page below is untouched. */
[data-theme="night"] .tool-masthead {
  --paper:       #FBFAF6;
  --paper-deep:  #ECE5D6;
  --ink:         #1A1717;
  --ink-soft:    #2F2A26;
  --quiet:       #5C544C;
  --rule:        #C9C3B7;
  --accent:      #156169;   /* the DAY teal: #56B6BF reads 2.27:1 on this cream */
  --accent-deep: #082A4C;
  --cta-fill:    #082A4C;
  --cta-ink:     #FBFAF6;
  --btn-ink:     #082A4C;
  color: var(--ink);
}

.masthead-brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-width: 0;
}

/* Logo anchor — left cluster, matching landing's .brandcluster */
.masthead-brand .wordmark {
  display: inline-flex;
  align-items: center;
  line-height: 1;
}

/* Logo size: identical to landing (.masthead .logo img) */
/* Optical alignment, 2026-07-28. The nib artwork carries ~1px of internal
   padding at phone scale, so although the image BOX starts on the gutter like
   everything else, the mark's first ink landed 1px inside it. Measured off the
   rendered pixels on DeskCopyPro at 360px: nib 21.0px, while the theme capsule,
   the eyebrow and the h1 all reached 20.0px. The delta held at every sampling
   threshold from 40 down to 2, so it is the artwork, not anti-aliasing. Ritu:
   "the wordmark logo sticks out a tad". Corrected here; the SVG is a fixed asset
   and is never edited. */
.masthead-brand .wordmark img {
  margin-left: -1px;
  display: block;
  height: clamp(36px, 4vw, 48px);
  width: auto;
  /* NO filter — logo renders in its own dark ink across all themes.
     The masthead stays on brand cream so the dark wordmark is always legible. */
}

/* Suite-mark — now on the FAR RIGHT (DeskBrief signature), no divider. Hidden on mobile.

   THE NAME KEEPS ITS OWN CAPITALS. Ritu, 2026-09-22: "Do the other nine labels
   too." His ruling of 2026-09-19, written when six help pages rendered the
   brand as BYLINEAL: "Written bylineAL for a reason." The same rule was doing
   the same thing to every tool's name, in the one place on the page that names
   the tool. MEASURED before this change, at 1440:

     DESKCOPY PRO · EDITORIAL          216px    source: DeskCopy Pro
     DESKCOPY · EDITORIAL              180      DeskCopy
     DESKPOP · EDITORIAL               170      DeskPop
     INTRODOCTOR · EDITORIAL           212      IntroDoctor
     FIELDREPORTAGE · EDITORIAL        233      FieldReportage
     REPORTERALLY · EDITORIAL          215      ReporterAlly
     DESKTRIAGE · EDITORIAL            196      DeskTriage
     THE TEACHING ROOM · EDITORIAL     266      The Teaching Room
     HOOKCRAFT · THE SCROLL DESK       250      HookCraft

   Nine labels, seven of them flattening a capital the source file put there on
   purpose. FrameWork opted out page-locally on 2026-09-15 and he chose that
   treatment for it again on 2026-09-22, from three rendered versions; this is
   the same treatment applied to the rest, so the modifier it needed is no
   longer a special case.

   THE TRACKING STAYS. --track-caps is what makes this read as a label rather
   than as a line of body text, and it is the difference he saw on FrameWork
   before the tracking was restored: 198px close-set against 250 beside it.

   THE THREE SUITE HOMES ARE NOT IN THIS. editorial.html, social.html and
   corporate/index.html carry their own .suite-mark from their own sheets and
   name a SUITE, not a tool: EDITORIAL INTELLIGENCE, SOCIAL, CORPORATE SUITE
   flatten nothing. Changing them is a separate decision and was not asked. */
.masthead-utility .suite-mark {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: none;
  color: #6B6259;           /* always quiet ink — does not theme */
  white-space: nowrap;
}

/* THE NAME IS NOT TRANSFORMED, ANYWHERE. Ritu, 2026-09-19, writing the About
   page's own explanation: "Written bylineAL for a reason." MEASURED the same
   day: the rule directly above sets this label in uppercase with caps tracking,
   so all six help pages rendered the brand as BYLINEAL. That is not a near
   miss. BYLINEAL is the PRONUNCIATION, and help/about.html exists to tell a
   reader the spelling is bylineAL: the page contradicted itself in its own
   masthead, one line above the paragraph explaining it.

   Declared here as well as in suite.css because no stylesheet in this product
   is loaded by every page, and these six load editorial-frame.css and not that
   one. The tracking goes with the transform: caps tracking on mixed-case text
   opens gaps the letterforms were never drawn for.
   scripts/build-help.mjs wraps every occurrence of the name in this class. */
.brand { text-transform: none; letter-spacing: 0; }
/* THE WHOLE LABEL DROPS TO SENTENCE CASE ON THE HELP PAGES, his ruling of
   2026-09-19 between two rendered versions. Keeping the caps and letting the
   name alone hold its case fixed the spelling and left "WHAT bylineAL DOES", an
   island of lowercase in a row of capitals that reads as a mistake even though
   it is correct. A label that must carry a name whose case means something is
   not a label to set in capitals. The tool mastheads keep theirs: those carry
   tool names, not the brand, and help is a reading surface where the tools are
   a working one. Tracking goes with the transform, at the generator's own
   value. */
body[data-tool="help"] .masthead-utility .suite-mark { text-transform: none; letter-spacing: normal; }

/* Right-side utility cluster — Teach · toggle · Settings · suite-mark, spread with air.
   Wraps rather than clips on narrow phones (adding Teach pushed the row past ~420px). */
.masthead-utility {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s-4);
  row-gap: var(--s-2);
}
.masthead-utility .theme-mount { display: inline-flex; align-items: baseline; }

/* Settings button — quiet mono control. */
.settings-toggle {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  padding: var(--s-2) var(--s-3);
  min-height: var(--touch-min);
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--ink-soft);
  cursor: pointer;
  transition: border-color 0.18s ease, color 0.18s ease,
              transform var(--press-out) var(--press-ease);
}
/* :hover and .active (the panel-open state) shared one rule; a tap on a
   touch screen fires :hover and it sticks until the next tap elsewhere
   (social-dark.css:183 first found and named this), so the two are split:
   .active must still answer when the panel is genuinely open, with no
   pointer involved at all. */
@media (hover: hover) { .settings-toggle:hover { border-color: var(--accent); color: var(--accent); } }
.settings-toggle.active { border-color: var(--accent); color: var(--accent); }
.settings-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Settings carried no press at all before this (measured 2026-09-10: six
   control families pressed, this was not one). --press-light: a secondary
   control, never the one loud action on the page. */
.settings-toggle:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: border-color 0.18s ease, color 0.18s ease,
              transform var(--press-in) var(--press-ease);
}

/* Theme toggle — segmented Day / Night / Press, injected by byline-theme.js */
/* The capsule and the Settings pill sit side by side and were 2px apart in
   height, so neither their tops nor their bottoms lined up — Ritu, 2026-07-28,
   from a phone photo: "the day/night press, along with settings right below it,
   are misaligned".

   Both were asking for the same 44px floor and getting different answers.
   .settings-toggle sets min-height on ITSELF, and with this sheet's
   box-sizing:border-box its 1px border sits inside that 44px. The capsule set no
   height of its own: it took the height of its buttons (min-height 44px) and
   then added its own 1px border outside them, standing at 46px.

   The floor now lives on the capsule, where it belongs, and the buttons stretch
   into it. Still 44px of tap target; simply 44px the same way its neighbour is.
   This was wrong at every width, desktop included, not only on a phone. */
.theme-toggle {
  display: inline-flex;
  align-items: stretch;
  min-height: var(--touch-min);
  border: 1px solid var(--rule);
}
.theme-toggle button {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  background: transparent;
  color: var(--quiet);
  border: 0;
  border-left: 1px solid var(--rule);
  padding: 0 var(--s-2);
  /* height comes from the capsule now, so its border does not stack on top of
     a 44px child and push the control to 46px. See the note on .theme-toggle. */
  min-height: 0;
  min-width: var(--touch-min);
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease,
              transform var(--press-out) var(--press-ease);
}
.theme-toggle button:first-child { border-left: 0; }
@media (hover: hover) {
.theme-toggle button:hover { color: var(--ink); }
}
/* Day / Night / Press carried no press of its own before this (measured
   2026-09-10). --press-light, same depth as the Settings pill beside it. */
.theme-toggle button:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: background 0.18s ease, color 0.18s ease,
              transform var(--press-in) var(--press-ease);
}
.theme-toggle button[aria-pressed="true"] { background: var(--accent); color: var(--cta-ink); --ico-color:currentColor; }
.theme-toggle button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.theme-toggle--floating { position: fixed; bottom: 1rem; right: 1rem; background: var(--paper); z-index: 50; }

/* ============================================================
   PAGE CONTAINER + OPENING
   ============================================================ */

.main {
  width: var(--frame);
  margin: 0 auto;
  padding: clamp(2rem, 5vw, 3.5rem) 0 var(--s-7);
}

.display-eyebrow {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 var(--s-4);
  font-weight: 500;
}

h1.display {
  font-family: var(--font-display);
  font-variation-settings: "wght" 780;          /* bold, matching the landing hero */
  /* ONE HERO SIZE FOR EVERY TOOL. Ritu, 2026-09-06: "every tool across the
     project must have a hero of the same size. UNIFORMITY PRINCIPLE", and then
     "I mean ONLY the tools. Not the landing pages."

     The cap was --t-9, so the fluid step ran to 65.9px at 1512 and 62.7px at
     1440. On 2026-09-05 ReporterAlly alone was capped at --t-8 to win back 90px
     of height and get its Refine button on screen; that fixed the button and
     made this the only tool off the shared rule, which he caught the same day.
     Capping the shared rule at --t-8 holds the principle AND keeps that fix:
     every tool now sets at 48px from 1091px up, and the fluid step still takes
     over below that, so nothing regresses on a phone.

     The landing pages are untouched by this and cannot be reached from here:
     index.html and editorial.html load suite.css, which carries its own
     h1.display. tests/test_design_system_drift.py holds that separation. */
  font-size: clamp(var(--t-7), 4.4vw, var(--t-8));
  line-height: 1.05;
  letter-spacing: var(--track-hero);
  margin: 0 0 var(--s-4);
  max-width: 30em;
  color: var(--ink);
  text-wrap: balance;                            /* even 2-line breaks, no ragged orphan line (suite-wide hero law) */
}
/* Teal accent words in a hero never split across a line break. */
h1.display .accent,
h1.display .tw,
h1.display .you { white-space: nowrap; }

/* ---- Hero responsive ramp (suite-wide, uniform across every tool) ----
   The desktop clamp floor (2.2rem) is too large for phones and pushes the
   longest heroes to 3-4 lines. Step the size down on tablet/phone so every
   hero holds two lines at most; balance + kept <br> breaks keep them aligned. */
@media (max-width: 900px) {
  h1.display { font-size: clamp(var(--t-5), 3.4vw, var(--t-7)); }
}
@media (max-width: 600px) {
  /* Ritu, 2026-09-11: "Hero on every tool for mobile can't be 2 lines", and on
     his phone 2026-09-12: "The heroes look horrible... IntroDoctor's hero is
     the only one that makes sense. One line."

     The floor was --t-4, 19px, and 5vw only reaches 19px at 380. Below that the
     clamp held every hero at 19px whatever the width, so the two longest wrapped.
     MEASURED, the single-line width each hero needs against the column it has:
     ReporterAlly 18.2px at 360, 19.5 at 390, 20.6 at 412; DeskCopy Pro 18.9 /
     19.5 / 20.6; FieldReportage, IntroDoctor and the Teaching Room 19 / 19.5 /
     20.6. The binding case is ReporterAlly at 375, a width this project had
     never measured and one of the commonest phones there is: it needs 18.8px.

     --t-3 as the floor and 4.8vw as the slope clears all of them with room:
     17.3px at 360, 18.0 at 375, 18.7 at 390, 19.8 at 412, against needs of
     18.2, 18.8, 19.5 and 20.6. Margin rather than an exact fit, because a hero
     that fits to the pixel wraps the moment the webfont falls back and the
     text sets wider, which is a trap this project has already been caught by.
     Both bounds stay on the type scale, so the scale-drift test still holds.

     FrameWork is NOT answered by this rule and is deliberately left: its hero
     runs at 32.68px and would need 17.7px to hold one line, which is body size
     and would gut the page's hierarchy. That one wants shorter copy, and copy
     is his. */
  /* 5.9vw and a --t-4 floor since 2026-09-12, up from 4.8vw and --t-3.
     Ritu shortened six of the seven heroes that afternoon for exactly this:
     the size is a CEILING set by the longest line, and his rewrite moved that
     ceiling from 18.7px to 23.1px. MEASURED at 390, the largest one-line size
     each can now hold: ReporterAlly 23.1 at 31 characters, the Teaching Room
     24 at 29, DeskCopy Pro 24.3 at 31, FieldReportage 25.7 at 27, IntroDoctor
     26.4 at 29, FrameWork 30.1 at 26, the Scroll Desk 47.2 at 15.

     THE BINDING WIDTH IS 360, NOT 390, and the first pass got that wrong: set
     from the 390 ceiling, ReporterAlly wrapped at 360 and the render said so.
     Measured again at 360, where the column is 311px rather than 341: the
     ceiling is 21.1px, again ReporterAlly's. 5.8vw gives 20.9 / 21.8 / 22.6 /
     23.9 at 360 / 375 / 390 / 412, under it at every width. A hero ceiling is
     always read at the NARROWEST screen the product supports. */
  h1.display { font-size: clamp(var(--t-4), 5.8vw, var(--t-6)); }
}

.section-rule {
  border: 0;
  border-top: 1px solid var(--ink);
  width: 3.5rem;
  margin: 0 0 var(--s-4);
  opacity: 1;
}

.lede {
  font-family: var(--font-body);
  font-variation-settings: "opsz" 18, "wght" var(--body-wght);
  font-size: var(--t-5);
  line-height: calc(var(--s-1) * 9);
  color: var(--ink-soft);
  max-width: 40em;
  margin: 0 0 var(--s-5);
}

/* THE LEADING FOLLOWS THE TYPE DOWN. Added 2026-09-16.
   The owner, on teach.html on his phone: "The text below the hero has too much
   of a gap between sentences." He is reading the LEADING, and it was a fixed
   36px while the font dropped two steps on the way to a phone. Measured at 358:
   16px type under 36px leading, a ratio of 2.25, against 1.565 on the desktop.
   The paragraph was drawing 144px for six lines.

   THE NUMBERS ARE NOT NEW ONES. Every other surface in the suite already sits
   between 1.47 and 1.50 on a phone, and the two bands below land on values
   those pages already use: 24px under 16px type is editorial.html exactly and
   FrameWork to within half a point, and 28px under 19px type is what index.html
   and social.html draw. Only teach.html and DeskTriage.html ever missed the
   override, because they are the two pages carrying a bare `.lede` that nothing
   else re-specifies. Desktop is untouched at 36px, which every page shares.

   The bands match the font-size bands appended by build-precision.mjs
   --apply-type further down this file, so the two move together. */
@media (max-width: 620px) {
  .lede { line-height: calc(var(--s-1) * 7); }
}
@media (max-width: 430px) {
  .lede { line-height: calc(var(--s-1) * 6); }

  /* AND THE SAME 32 THE SOCIAL DESKS CLOSE ON. The owner, 2026-09-16, after
     choosing 32 of three rendered gaps for the social pair: "Either way,
     maintain symmetry with the other tools."

     This lands on exactly two pages and that is deliberate. teach.html and
     DeskTriage.html are the only ones carrying a BARE `.lede`; index, social
     and editorial re-specify it from their own sheets, and FrameWork pays its
     closing air with the section's padding instead. The rooms on teach.html
     take the same step so the page is even with itself down its whole length,
     rather than tight at the top and loose below. */
  .lede { margin-bottom: calc(var(--s-1) * 8); }
}

/* Width variant for the one-line tool description above the input box. Same
   voice as .lede in every other respect — family, size, weight, colour — this
   only lifts the measure. 40em is the reading measure, right for teach.html's
   paragraph and wrong for a single-line descriptor, which it forced into two or
   three lines inside a column narrower than the page needed (measured 2026-07-26:
   the box was capped at 781px while the container offered 1312px). A one-line
   descriptor carries no reading cost at a wider measure, so the copy stays whole
   and the line stops breaking. */
.lede.tool-desc { max-width: 50em; }

/* ============================================================
   CONFIG — rule-lined selector grid (.selectors language)
   Houses Mode / Options (toggle-pills) / Add context / Genre.
   ============================================================ */

.toggle-row {
  display: grid;
  /* 13rem let five columns squeeze onto the 1366 band, where the ADD CONTEXT
     cell gave its select ~197px of box for a label that needs ~202px before the
     dropdown arrow starts. The arrow then painted on top of the last two
     characters of "Off (strict source-fidelity)": a closed <select> does not
     honour text-overflow on its selected-option text, so it does not ellipsis,
     it collides. Measured 2026-08-03 at 1366, where Ritu saw it on a 14in
     laptop. 14rem fixed 1366 but the collision survived at 1512 and at the
     frame's 1296 cap, where five columns leave the cell ~259px: still too
     little. Do not trust a JS width probe here, it under-reads. `font`
     shorthand does not carry font-variation-settings, so a probe copying
     cs.font measures this select's text at weight 400 when it renders at 500,
     reports 15px of clearance, and the arrow still lands on the "y)". The
     picture was right and the number was wrong.

     15rem was then tried and REVERTED the same day. It did stop the collision,
     by giving four columns at every desktop width, but Ritu's verdict on the
     result was "looks HORRIBLE": five cells wrapped to four plus a lone GENRE
     on a second row with a dangling cell border and an empty half-row beside
     it. That is a worse defect than the one it cured, and it was a bad trade to
     have made unprompted. THE COLUMN COUNT IS NOT THE PLACE TO FIX A FEW PIXELS
     OF TEXT OVERLAP. Back to 13rem, which is what shipped for months, and the
     overlap is cured below by giving the select more room inside its cell. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  gap: 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  margin: var(--s-5) 0 0;
  align-items: stretch;
}


/* ============================================================
   THE CONFIG PANEL (2026-08-05)
   Ritu's ruling: a vertical panel on the left, on every tool that has config.

   WHY A FIXED WIDTH. The horizontal bar was a row of EQUAL columns, so a
   control's width depended on how many controls the tool had and how wide the
   window was. That is why the same defect kept coming back at a different
   width on a different tool: changing the column count or the padding was
   fixing a number when the SHAPE was the defect. With a fixed panel the widest
   option is measured ONCE and clears everywhere. Controls stack; six make the
   panel taller, never narrower. There is no column count left to get wrong,
   which is also why DeskPop's [data-tool] special case could be deleted rather
   than extended.

   THE PANEL FLANKS THE INPUT ONLY. The output stays a sibling below at full
   frame width: on a 14in laptop the frame gives the output 1137px, which
   clears the 988px its two-pane layout needs, while the work column would give
   it 811 and collapse the diagnostics under the copy on all three tools.
   ============================================================ */
.tool-config {
  display: grid;
  grid-template-columns: var(--rail) 1fr;
  border-top: var(--hairline) solid var(--rule-soft);
  border-bottom: var(--hairline) solid var(--rule-soft);
  margin: var(--s-5) 0 0;
  align-items: stretch;
}
.tool-panel {
  background: var(--paper-deep);
  border-right: var(--hairline) solid var(--rule-soft);
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* The work column opens on the SAME LINE as the panel beside it. Ritu,
   2026-08-05: "Shouldn't the input box and tool description be aligned with
   the vertical features box on the left?"

   MEASURED, not reasoned: the offset was a constant 47.68px on all five
   panel-bearing tools at every width from 950 to 1920. The padding difference
   is only 7.2px of that; the other 40.5px is .input-label's own
   `margin: 2.25rem 0 0.6rem`, which is right for the SECOND and third labels
   down the column (they need air after a textarea) and wrong for the first,
   which has the container's own edge above it.

   So both halves are fixed: the column's top padding matches .panel-cell's
   1rem, and the first label drops the margin it does not need. The two labels
   are the same type (mono 0.7rem / 0.18em / weight 500), so equal box tops
   put them on one baseline.
   Guard: scripts/panel_fit_check.mjs measures the rendered first baseline. */
.tool-work {
  min-width: 0;
  padding: var(--s-3) 0 var(--s-4) var(--s-4);
}
.tool-work > .input-label:first-child { margin-top: 0; }
.panel-cell {
  padding: var(--s-3);
  border-bottom: var(--hairline) solid var(--rule-soft);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  min-width: 0;
}
.panel-cell:last-child { border-bottom: 0; }
.panel-cell.is-hidden { display: none; }
.panel-cell--apart { margin-top: auto; border-top: var(--hairline) solid var(--rule-soft); }
/* A cell set APART from the run-time controls is apart from nothing when it is
   the only cell: the auto margin then strands it at the foot of an empty
   column, and its label no longer opens on the same line as the work column
   beside it. IntroDoctor became that case on 2026-09-05, when Genre left the
   panel for the Settings drawer and Stylesheet was all that remained. Measured
   by scripts/panel_fit_check.mjs, which reported the label 343px adrift at
   every width from 950 to 1920. Same cancel the 900px block already applies. */
.panel-cell--apart:only-child { margin-top: 0; border-top: 0; }

/* THE RAIL NEVER STOPS SHORT OF ITS BAND. There is no rule here on purpose,
   and this note is the guard's reason.

   REVERSED 2026-09-07. A rule used to stop a single-cell rail at its content
   (align-self: start), chosen on 2026-09-05 to stop one dropdown painting
   above 320px of empty ground. Ritu on the live site: "IntroDoctor's layout
   has become skewed." He was right, and the 09-05 diagnosis was wrong.

   MEASURED, which is what settled it: Headliner already ships a 238px empty
   tinted gap inside its rail and DeskCopy a 202px one. Both run the full
   height of their band, both close flush, and neither reads as skewed. So the
   emptiness was never the fault. A rail that STOPS is, because it turns a
   column into a fragment sitting above open ground.

   IntroDoctor now carries a second cell (Checks) rather than a shorter rail,
   and the stopping rule is culled. tests/rail-never-stops.test.js keeps it
   culled, and scripts/panel_fit_check.mjs measures the rendered gap between
   every rail's foot and its band's foot on every tool at every width. */
/* THE FIDELITY BAR, INSIDE A CELL. The element keeps its class because
   assets/js/style-pass.js repairs a restored snapshot by walking `.fidelity-bar`
   and rebuilding a missing #stylePassBtn inside it; a rename would stop that
   repair without failing anything. What changes is the axis: a 324px rail is a
   column, so the two pills stack and each status sits under the button that
   writes it. The bar's own page margins go, because .panel-cell already owns
   the padding inside a cell. */
.panel-cell > .fidelity-bar {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-2);
  margin: 0;
}
/* A status can run to a sentence ("3 possible issues"). In a 324px column it
   wraps rather than pushing its own cell wide, which overflow_check would
   otherwise report against the rail. */
.panel-cell > .fidelity-bar .fidelity-status {
  max-width: 100%;
  overflow-wrap: anywhere;
}

.panel-hint { font-size: var(--t-1); line-height: calc(var(--s-1) * 4); color: var(--quiet); margin: 0; }

/* Mode is a two-state switch, not a dropdown (Ritu, 2026-08-04). Both states
   are visible at once, so the editor sees what the alternative IS rather than
   having to open a menu to find out. */
.mode-switch {
  display: grid;
  /* ONE column, not the two the plan specified. At 324px a half is 143px and
     "Feature (long-form)" needs ~163px, so side by side it broke mid-word as
     "FEATURE (LONG-" / "FORM)". The label is Ritu's copy and the two-column
     arrangement was a layout preference, so the layout gave way. Stacked, both
     states are still visible at once, which is the whole point of a switch. */
  grid-template-columns: 1fr;
  border: 1px solid var(--accent);
  border-radius: var(--pill);
  overflow: hidden;
}
.mode-switch button + button { border-top: 1px solid var(--accent); }
.mode-switch button {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  font-weight: 500;
  padding: var(--s-2);
  min-height: var(--touch-min);
  background: transparent;
  color: var(--accent);
  border: 0;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease,
              transform var(--press-out) var(--press-ease);
}
.mode-switch button[aria-pressed="true"] { background: var(--accent); color: var(--cta-ink); --ico-color:currentColor; }
@media (hover: hover) {
.mode-switch button[aria-pressed="false"]:hover {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
}
.mode-switch button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; z-index: 1; }
/* Feature / News carried no press before this (measured 2026-09-10).
   --press-light: the switch states an existing choice, it is not the run. */
.mode-switch button:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: background 0.18s ease, color 0.18s ease,
              transform var(--press-in) var(--press-ease);
}

/* The phone tab bar itself is a separate task's build (docs/superpowers/plans/
   2026-09-10-phone-places-stage-one.md, Task 6) and its markup does not exist
   yet. Written here so the control is born pressing rather than retrofitted,
   the exact trap this task exists to close: six families pressed and
   everything built after them did not. --press-light: a tab states which
   place you are in, it is not the run. Placed beside the other :active rules
   on Ritu's ruling that the press belongs at every width, not only on a
   phone; Task 6's own CSS block (phone-only, keyed off --phone-max) covers
   the tab bar's layout, not this. */
.phone-tabs button {
  transition: transform var(--press-out) var(--press-ease);
}
.phone-tabs button:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: transform var(--press-in) var(--press-ease);
}

/* The option pills go teal: outline when off, solid when on (his ruling), with
   a state mark so the state is not carried by colour alone. */
.panel-cell .toggle-btn {
  border-color: var(--accent);
  color: var(--accent);
  border-radius: var(--pill);
  display: flex;
  align-items: center;
  gap: var(--s-2);
  text-align: left;
}
.panel-cell .toggle-btn.active { background: var(--accent); color: var(--cta-ink); --ico-color:currentColor; }
.panel-cell .toggle-btn .mark { width: 1.1em; height: 1.1em; flex: none; display: block; }
.panel-cell .toggle-btn .mark svg { width: 100%; height: 100%; display: block; }
.panel-cell .toggle-btn .mark .on { display: none; }
.panel-cell .toggle-btn.active .mark .on { display: block; }
.panel-cell .toggle-btn.active .mark .off { display: none; }
@media (hover: hover) {
.panel-cell .toggle-btn:hover:not(.active) {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
}
.panel-cell .toggle-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Button marks moved to assets/css/icons.css on 2026-08-05, so every suite
   can load them. See that file. */


/* One control, one look. The stylesheet picker was styled for the fidelity
   bar, where it sat among small mono buttons; in the panel it is a select like
   Genre and Add context and must read as one. Overriding it HERE, scoped to
   the panel, leaves the fidelity-bar styling intact for any surface that still
   uses it rather than forking a second class. */
.panel-cell select {
  width: 100%;
  min-width: 0;
}
.panel-cell .style-sheet-picker {
  font-family: inherit;
  font-size: var(--t-4);
  letter-spacing: normal;
  text-transform: none;
  color: inherit;
  padding: 0;
  border: 0;
  border-bottom: var(--hairline) solid var(--rule-soft);
  /* revert, not a value. FINDING 2026-08-05: no panel select declares a
     border-radius at all, so all of them take the BROWSER'S default (9px in
     Chrome). Only .style-sheet-picker declared one (0), which is why it read
     as a different control. Declaring var(--pill) here made it worse (999px).
     revert puts it back on the same UA default its siblings use, in whatever
     browser. Recorded: the panel selects being UA-dependent is a real if minor
     inconsistency across browsers, and worth a deliberate token later. */
  border-radius: revert;
  /* A rule inside THE PHONE LAYER media block (line 5662, keyed off
     --phone-max) depends on this min-height: auto staying here. It
     restores the touch floor for phone at the same specificity, winning
     only on source order because it comes later in the file. Reordering
     these two rules, or moving this one, breaks that. */
  min-height: auto;
}

@media (max-width: 900px) {
  .tool-config { grid-template-columns: 1fr; }
  .tool-work { padding: var(--s-4) 0 0; }
  .tool-panel {
    border-right: 0;
    border-bottom: var(--hairline) solid var(--rule-soft);
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
  /* --apart sets the stylesheet at the FOOT of the flex column above 900. In
     the stacked grid that margin-top:auto pushes it onto a row of its own and
     strands whatever came before it: two orphans instead of none. Caught by
     scripts/panel_fit_check.mjs, which is the whole reason that gate exists:
     the ad-hoc check I ran by hand had been run BEFORE --apart was added. */
  .panel-cell--apart { margin-top: 0; border-top: 0; }
  /* An odd count leaves the last cell beside an empty half: the orphan again,
     at a different breakpoint. Self-correcting for 4 and 6 controls. */
  .panel-cell:last-child:nth-child(odd) { grid-column: 1 / -1; }
  /* ...but that arithmetic counts DOM children, and DeskCopyPro always hides
     exactly one of its two mode-scoped cells (Voice XOR Way in), so its DOM
     count and its visible count differ by one and the parity is INVERTED:
     there, the last cell must span when its DOM position is EVEN. Both halves
     are needed. The cancel alone was enough while DCP carried an odd number of
     children, because "don't span" happened to be right for that count; adding
     the Merge cell on 2026-08-07 made it even, the cancel silently became the
     wrong half of the rule, and the orphan came back at 900 and 768. Stated as
     the inversion it actually is, it no longer depends on how many cells the
     tool happens to have. scripts/panel_fit_check.mjs asserts no orphan at any
     width on any tool, which is what caught this, and catches it whatever
     mechanism a future cell uses to hide. */
  .tool-panel:has(> .panel-cell.is-hidden) .panel-cell:last-child:nth-child(odd) {
    grid-column: auto;
  }
  .tool-panel:has(> .panel-cell.is-hidden) .panel-cell:last-child:nth-child(even) {
    grid-column: 1 / -1;
  }
  .panel-cell .toggle-options { flex-direction: row; flex-wrap: wrap; }
}
@media (max-width: 620px) { .tool-panel { grid-template-columns: 1fr; } }

.toggle-cell {
  /* Inline padding trimmed 1.25rem -> 0.9rem on 2026-08-03. The frame narrowed
     the bar from 1476 to 1296, which took ~36px off each of the five cells, and
     the ADD CONTEXT select ran ~12px under its own dropdown arrow. This buys the
     room back inside the cell. It is the right place for it: the earlier attempt
     bought the room by dropping to four columns, which wrapped GENRE onto a
     second row and looked far worse than the defect it fixed. */
  /* Asymmetric on purpose. The LEFT padding sets where every label and control
     in the bar begins, so it stays at 0.9rem; the RIGHT is only the gap before
     the cell rule, and the select already carries its own arrow gutter, so it
     can give up room without anything looking tighter. This is the last 10px
     that "Open against the numbers" needed at 1366, and taking it here means the
     dropdowns keep the same 1rem type as the rest of the product. */
  padding: var(--s-3) var(--s-1) var(--s-3) var(--s-3);
  border-right: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  min-width: 0;
}
.toggle-cell:last-child { border-right: 0; }

.toggle-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--quiet);
  font-weight: 500;
}
.toggle-label .opt-quiet,
.source-disclosure-trigger .opt-quiet { color: var(--quiet); font-style: italic; text-transform: none; letter-spacing: 0; }

/* Selects in the config grid + Mode / Add context / Genre */
/* Every select in the panel, styled by the CELL it sits in and not by a list
   of ids. The id list this replaced (#modeSelect, #valueAdd, #genreOverride)
   was the drift pattern itself: #wayIn, #styleProfile, #subheads and
   #featureVoice were never on it, so when the cells moved into the panel those
   four silently fell back to the browser's default control while the three
   listed ids kept the house look. One kind of control, one look, one rule. Way in on the two reportage rows
   joined this list on 2026-09-04, after shipping without it. */
.panel-cell select,
.toggle-row select,
.desk-inline select {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  /* The chevron is named, not written twice. A select that needs a third
     background layer (the reportage row draws its own rule as one) composes
     with these three properties instead of restating the gradients, so the
     arrow keeps one definition across the product. */
  --select-chevron:
    linear-gradient(45deg, transparent 50%, var(--quiet) 50%),
    linear-gradient(135deg, var(--quiet) 50%, transparent 50%);
  --select-chevron-position:
    calc(100% - 12px) calc(50% + 1px),
    calc(100% - 7px) calc(50% + 1px);
  --select-chevron-size: 5px 5px, 5px 5px;
  background-image: var(--select-chevron);
  background-position: var(--select-chevron-position);
  background-size: var(--select-chevron-size);
  background-repeat: no-repeat;
  border: 0;
  border-bottom: 1px solid var(--rule-soft);
  font-family: var(--font-body);
  font-variation-settings: "opsz" 16, "wght" 500;
  /* Briefly 0.95rem on 2026-08-03, to buy pixels for an option label that ran
     under its own dropdown arrow. Ritu shortened the label instead ("Open with
     how they cracked it" -> Open with "how"), which is the real fix, so the type
     goes back to 1rem and every dropdown in the product is one size again. A
     shrunken control is a workaround, not a design; when the copy fits, the
     workaround comes out. */
  font-size: var(--t-4);
  color: var(--ink);
  /* padding-right holds the arrow gutter. A closed <select> does not honour
     text-overflow on its selected-option text, so the text does not ellipsis
     when it runs long, it simply renders under the arrow. Widening this gutter
     is what pushes the arrow clear of the text. Raised 1.5rem -> 2.1rem with the
     cell trim above; measured clearance after, at every width from 1100 to 1920. */
  padding: var(--s-1) var(--s-4) var(--s-2) 0;
  cursor: pointer;
  min-height: var(--touch-min);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: border-color 0.2s ease, transform var(--press-out) var(--press-ease);
}
/* The pairing of :hover with :focus stays — focus should show the same
   affordance as hover — but it must not SUPPRESS the ring, which is what
   `outline: 0` did here. Same correction suite.css:73 made for finding 4.
   :hover and :focus no longer share one rule, split below: a tap fires
   :hover on a touch screen and it sticks, while :focus must answer with no
   pointer involved at all. */
@media (hover: hover) {
  .panel-cell select:hover,
  .toggle-row select:hover,
  .desk-inline select:hover {
    border-bottom-color: var(--accent);
  }
}
.panel-cell select:focus,
.toggle-row select:focus,
.desk-inline select:focus {
  border-bottom-color: var(--accent);
}
/* Way in and the Style pass sheet carried no press before this (measured
   2026-09-10). --press-light: a select is a secondary control, never the
   one loud action on the page. */
.panel-cell select:active:not(:disabled),
.toggle-row select:active:not(:disabled),
.desk-inline select:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: border-color 0.2s ease, transform var(--press-in) var(--press-ease);
}
.panel-cell select option,
.toggle-row select option,
.desk-inline select option { background: var(--paper); color: var(--ink); }
/* These selects have no left padding (text aligns flush with the label), so
   the global inset ring lands ON the first glyph. Positive offset floats the
   ring clear of the text; accent matches the toggle-pills beside them. The
   resting underline yields while the ring shows, or the two run as doubled
   lines 3px apart (Ritu: shoddy). Declared after the :hover/:focus accent
   group so it wins on order at equal specificity. */
.panel-cell select:focus-visible,
.toggle-row select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-bottom-color: transparent;
  /* Air between the glyphs and the ring without moving the text: the box
     grows left, the negative margin holds the text's page position. */
  padding-left: var(--s-2);
  margin-left: calc(var(--s-2) * -1);
}

/* Standfirst / Subheads as toggle-pills */
.toggle-options { display: flex; gap: var(--s-2); flex-wrap: wrap; }
.toggle-btn {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 500;
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  padding: var(--s-2) var(--s-2);
  min-height: var(--touch-min);
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--ink-soft);
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease,
              transform var(--press-out) var(--press-ease);
  user-select: none;
}
@media (hover: hover) {
.toggle-btn:hover:not(.active) { border-color: var(--accent); color: var(--accent); }
}
.toggle-btn.active { background: var(--accent); color: var(--cta-ink); border-color: var(--accent); --ico-color:currentColor; }
.toggle-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Multi-source, Standfirst, Subheads carried no press before this (measured
   2026-09-10, DeskCopy Pro's option pills). --press-light: a toggle states an
   existing choice, it is not the run. Covers `.panel-cell .toggle-btn` too,
   the same element under a more specific selector for its colour only. */
.toggle-btn:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease,
              transform var(--press-in) var(--press-ease);
}

/* ============================================================
   SOURCE BLOCKS — Raw copy + Protect-a-section
   ============================================================ */

.input-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--quiet);
  font-weight: 500;
  margin: var(--s-5) 0 var(--s-2);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-3);
}
.input-label .wc-display {
  font-family: var(--font-mono);
  font-size: var(--t-1);
  letter-spacing: normal;
  color: var(--quiet);
  text-transform: none;
}
/* Quiet right-aligned hint on a primary label (e.g. THE BRIEF) —
   body italic, not the mono/uppercase the label itself carries. */
.input-label .label-hint,
.source-disclosure-trigger .label-hint {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-3);
  letter-spacing: 0;
  color: var(--quiet);
  text-transform: none;
}
/* ── THE CLIP AND THE MIC ───────────────────────────────────────────────
   Ritu, 2026-08-20: "just keep a small CLIP button and a VOICE button.
   That's what modern UIs have. Gmail is a good example. The attach button
   has just a clip. There is no word written there to explain it... It's
   time we took recourse to intuitive user interfaces rather than having
   pills for every new feature that we introduce, unless, of course, they
   need explaining."

   So this is the ONE control class in the suite that is not a pill. No
   border, no ground, no word: a 16px mark on 44px of transparent tap area.
   The pill law in docs/DESIGN-SYSTEM.md governs BOXED buttons and this one
   is deliberately unboxed, which is a new category rather than a breach of
   the old one. HIS 44px FLOOR (2026-07-28, "a button below 44px is drift,
   whatever it looks like") IS UNTOUCHED: the glyph is small, the target is
   not. The word survives only where it costs no pixels, in title and
   aria-label, without which the control is unreachable by keyboard and
   silent to a screen reader.

   THE ROW DOES NOT GROW BY 44px. The buttons carry a negative block margin
   so a label row keeps the height it had; the tap area overhangs into the
   margin above and below, which is empty space in both directions. Measured
   after the change, not assumed. */
/* One class in two situations, which is why it is flex and not inline-flex:
   as a flex ITEM at the end of an .input-label row, margin-left:auto pushes
   it to the right of the label; standing ALONE above a box that has no label
   row, the same auto margin right-aligns the block itself. */
/* A LABEL ROW THAT ALREADY HAD A RIGHT-HAND ITEM NEEDS THE PAIR GROUPED WITH
   IT, NOT COMPETING WITH IT. Every one of these rows is either
   justify-content:space-between or has an item already carrying
   margin-left:auto; a third child with a second auto margin splits the free
   space between the two and strands the first one mid-row. Measured, not
   predicted: ReporterAlly rendered "YOUR MATERIAL0" with the count jammed
   against its label, and FieldReportage's section hints walked from the right
   edge to the middle of the header. So the item that was already on the right
   keeps the auto margin, and the pair follows it at a fixed gap. */
.input-label .wc-display { margin-left: auto; }
[data-tool="reporterally"] .panel-header .char-count { margin-left: auto; }
.wc-display + .box-tools,
.label-hint + .box-tools,
.tz-label .note + .box-tools,
.char-count + .box-tools,
.harvest-hint + .box-tools { margin-left: var(--s-3); }

.box-tools {
  display: flex;
  align-items: center;
  gap: 2px;
  width: fit-content;
  margin-left: auto;
  /* THE MARK LINES UP WITH THE CONTENT EDGE, NOT THE TAP TARGET.
     Ritu, 2026-08-20: "the alignments are awry." They were: a 16px glyph
     centred in a 44px target leaves 14px of empty target outside it, so
     aligning the BUTTON to the right edge left every mark sitting 14px short
     of the box it belongs to, while the invisible half of the target hung
     where the eye reads an edge. The row is pulled back by exactly that 14px,
     which puts the glyph flush with the column and leaves the target where it
     was. */
  margin-right: calc((var(--touch-min) - var(--t-3)) / -2);
  flex: none;
}
.box-tool {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  width: var(--touch-min);
  height: var(--touch-min);
  margin: calc(var(--s-2) * -1) 0;   /* a standalone row keeps 28px, not 44 */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--pill);
  cursor: pointer;
  font-size: var(--t-3);    /* 16px: the mark is sized in em by icons.css */
  line-height: 0;
  flex: none;
  /* Teal at rest is the set's own default (icons.css), and his reason for
     wanting marks at all was that they "lend some colour to the whole UI".
     Hover goes to --ink rather than to a second accent: on Night
     --accent-deep is near-invisible, and --ink inverts with the theme. */
  transition: color 160ms cubic-bezier(0.23, 1, 0.32, 1),
              background-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
              transform var(--press-out) var(--press-ease);
}
@media (hover: hover) {
.box-tool:hover { --ico-color: var(--ink); }
}
/* Attach and Dictate carried no press at all before this (measured
   2026-09-10). --press-light. The click lands on the ::after below, not on
   this element's own painted box, but :active still reads on the host the
   hit resolves to, not on the pseudo-element. */
.box-tool:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: color 160ms cubic-bezier(0.23, 1, 0.32, 1),
              background-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
              transform var(--press-in) var(--press-ease);
}

/* ON A LABEL ROW THE PAIR TAKES NO HEIGHT AT ALL, and this was measured, not
   predicted. A 44px control on a 16px label row is the tallest thing in it, so
   the row grew and pushed the work column's first label 3px below the config
   panel's first label — scripts/panel_fit_check.mjs went from CLEAN to 40
   findings across 55 tool-widths on that 3px alone. The row is zero-height and
   the buttons overflow it equally in both directions, into margins that are
   empty in both directions. The 44px tap area is untouched: an overflowing
   child is still clickable everywhere it is drawn. Rows that stand ALONE above
   a box are excluded, because there the height is the whole point. */
.input-label .box-tools {
  height: 0;
  align-self: center;
}
.input-label .box-tool {
  margin: calc(var(--touch-min) / -2) 0;
}

/* ONE GAP ABOVE THE BOX, EVERYWHERE. Ritu, 2026-08-20: "the gap between the
   boxes and the icons is uniform and symmetrical."
   MEASURED at 1440 across all nine tools, mark bottom edge to box top edge:
   a label row gave 10px, a row standing alone gave 6, a FieldReportage section
   header gave 18, and two rows carrying a rule of their own gave 27 and 47.
   Five gaps for one relationship.
   The two worst are fixed in the markup above, by moving the marker down to
   its box; the rest are nudged here to 8px, the scale's own step. The nudge is
   `top` on a relative box, which moves the marks and moves nothing else: every
   row keeps the height and the label position it was tuned to, and the panel
   gate stays clean. Re-measure with the harness before changing any of these
   three numbers; they are differences, not preferences. */
.box-tools { position: relative; top: -2px; }      /* alone above a box: 6 -> 8 */
.input-label .box-tools { top: 2px; }              /* on a label row:    10 -> 8 */

/* THE TAP TARGET STOPS AT THE BOX'S TOP EDGE, AND THE MARK DOES NOT MOVE.
   The mark sits 8px above its box and carries 14px of empty target below it,
   so the bottom 6px of every button lay over the top of the textarea, and
   .box-tools is position:relative, so it painted over the field there whatever
   the DOM order. A click in a box's top right corner opened the file picker
   instead of putting the caret in the field. Measured on every page and every
   width: 6.0px, and 6.4px on the three boxes whose label row lands on a
   fractional pixel.
   Nothing is drawn differently. The button keeps its 44px box, its glyph, its
   pill radius and its listening disc; only the part of it that hangs over the
   box stops answering the pointer. The pointer-sensitive area is the ::after,
   which ends one pixel short of the surplus, the pixel of sub-pixel drift
   scripts/box_tools_align_check.mjs already tolerates. .box-tools itself takes
   no clicks at all: on a corporate label row the row's OWN box overhangs the
   textarea by the same 6px, and the click listener on it fires by bubbling
   from the button, as it always did. */
.box-tools { pointer-events: none; }
.box-tool {
  position: relative;
  pointer-events: none;
}
.box-tool::after {
  content: '';
  position: absolute;
  inset: 0 0 calc((var(--touch-min) - var(--t-3)) / 2 - var(--s-2) + 1px) 0;
  pointer-events: auto;
}

/* ON A PHONE THE WORDS KEEP THE LINE AND THE CONTROLS TAKE A NEW ONE.
   Two 44px targets are a third of a 390px row, and measured against HEAD they
   cost FieldReportage's own section labels their single line: "Scene &
   Location" wrapped at 430 and 390 where it used to wrap only at 360, and
   "Moments" wrapped at 390 where it never had. Shrinking the targets is not
   available (44px is the floor, and a phone is where it earns its keep) and
   the labels are authored copy, so the row wraps instead. The words are held
   to one line by nowrap, which leaves the pair as the only item that CAN
   move; it drops only where it does not fit, so wider phones keep the single
   row they already had. */
/* FIELDREPORTAGE'S SIX SECTIONS TOOK THE SAME ROW AS EVERY OTHER BOX, AND THIS
   IS THE THIRD PLACEMENT THEY WERE GIVEN. On the section header the pair looked
   tidiest and cost no height, and it could not be made to measure: a header
   wraps when its own words are long, the pair then centres in a taller row, and
   the align gate read 17.2px above the box at 1024 on the two longest section
   names and again on four sections at 430. CSS cannot see a wrap, so one rule
   cannot hold both cases at 8px, and chasing it with a media query moved the
   error rather than removing it. A row of its own above the box has one
   geometry at every width. It costs each section about 28px of height, which is
   the honest price of a number that holds. */
.input-label .wc-display { white-space: nowrap; }

/* LISTENING. The word "Listening... tap to stop" is what Corporate's pill
   says while it records; with the word gone the mark has to say it. It says
   it by filling: a teal disc with the mark cut out of it in paper, which is
   unmistakably ON and needs no colour this product has already spent.
   NOT opacity — opacity means DEAD here (0.35/0.5/0.6) and a breathing
   ghost would read as a control going out, not one that is live. */
.box-tool[aria-pressed="true"] {
  background: var(--accent);
  --ico-color: var(--paper);
  animation: box-tool-breath 1.6s cubic-bezier(0.37, 0, 0.63, 1) infinite;
}
@keyframes box-tool-breath {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.09); }
}
/* Gentler, not gone: the fill still says recording, the movement stops. */
@media (prefers-reduced-motion: reduce) {
  .box-tool[aria-pressed="true"] { animation: none; }
}

/* What prints here: a file that would not read, or a moment of progress
   worth naming (reading a file, transcribing a recording, takes a beat).
   Silence on failure would leave the reporter waiting for text that is
   never coming, so this defaults to the warning voice, --warn, which is
   what a failure has earned. */
.box-tools-msg {
  display: none;
  font-family: var(--font-body);
  font-size: var(--t-2);
  font-style: italic;
  letter-spacing: 0;
  text-transform: none;
  color: var(--warn);
  margin: 0 0 var(--s-2);
}
.box-tools-msg.is-shown { display: block; }
/* Progress is not a failure and should not wear one's colour. --quiet is
   the token the suite already uses for a notice that asks nothing of the
   reporter (.panel-hint, above); this borrows it rather than inventing a
   third voice for the same line. */
.box-tools-msg.is-progress { color: var(--quiet); }

/* THE ONE CONTROL THAT EVER APPEARS IN THIS LINE: the offer to send a stretch
   of recording again. It is not a .box-tool; those two are marks on the label
   row, and this is a word inside a sentence, so it takes the line's own type
   and colour and says it is a control by underlining itself. It appears only
   when there is audio in storage to send, and it goes when the words land. */
.box-tools-again {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin-left: var(--s-2);
  font: inherit;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.box-tools-again:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.box-tools-again:disabled { cursor: default; opacity: 0.5; }

/* .input-hint retired 2026-07-26 (Ritu's design wishlist, item 1). Every tool
   description now wears .lede, DeskCopy Pro's treatment, so the suite reads in
   one voice above the input box: roman not italic, --ink-soft not --quiet, and
   40em rather than the 60ch cap that bunched the text into a narrow left-hand
   column. The .accent child moved to `.lede .accent` below. Guarded culled by
   tests/test_design_system_drift.py so no tool reintroduces the class. */

/* Main raw-copy input. Shared affordance: every tool's primary paste box.
   One class for all of them since 2026-08-05: the #rawInput half of these
   selectors was the last thing making Pro's paste box a special case, and the
   id it hung on no longer exists. Guarded by tests/suite_vocabulary.test.js. */
textarea.raw-input {
  width: 100%;
  min-height: 16rem;
  background: var(--paper-deep);
  border: 1px solid var(--rule);
  border-radius: var(--box);
  font-family: var(--font-body);
  font-variation-settings: "opsz" 16, "wght" var(--body-wght);
  font-size: var(--t-4);
  line-height: calc(var(--s-1) * 7);
  color: var(--ink);
  padding: var(--s-3) var(--s-3);
  resize: vertical;
  transition: border-color 0.24s ease, background 0.24s ease;
}
/* The two hairlines in the bottom-right corner of every box are the browser's
   resize grip. Ritu, 2026-08-07: "I want that small, almost inconspicuous two
   lines in the right-hand bottom corner of each box removed."

   The PAINT goes, the DRAG stays. `resize: none` would have been one word
   shorter and would have taken a real capability off a writing tool: an editor
   pasting 2,000 words should still be able to pull the box taller. Hiding only
   the grip keeps that. It also matters more now than it did yesterday, because
   a square grip sitting inside a 16px rounded corner is the one place the new
   curve would visibly break.

   Chrome/Safari only — Firefox draws no grip on a textarea at all, so there is
   nothing to hide there and nothing to fall back to. */
textarea::-webkit-resizer { display: none; }

textarea.raw-input:focus { border-color: var(--ink); background: var(--paper-warm); }
textarea.raw-input::placeholder { color: var(--quiet); font-style: italic; }

/* Protect-a-section — quieter second source block */
.toggle-row.protect-row {
  display: block;
  border: 0;
  margin: var(--s-4) 0 0;
}
.toggle-row.protect-row .toggle-label { display: block; margin-bottom: var(--s-2); }

#protectedSection {
  width: 100%;
  min-height: 4.5rem;
  background: var(--paper-warm);
  border: var(--hairline) solid var(--rule-soft);
  border-radius: var(--box);
  padding: var(--s-3) var(--s-3);
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--ink);
  resize: vertical;
  transition: border-color 0.24s ease;
}
#protectedSection:focus { border-color: var(--ink); }
#protectedSection::placeholder { color: var(--quiet); font-style: italic; }

/* The brief — a live channel the desk listens to, NOT an afterthought.
   Peer to raw copy (primary label), set apart by its warm fill.
   Taller than protect-a-section: it invites a sentence or two of facts + direction. */
#briefInput {
  width: 100%;
  min-height: 6rem;
  background: var(--paper-warm);
  border: var(--hairline) solid var(--rule-soft);
  border-radius: var(--box);
  padding: var(--s-3) var(--s-3);
  font-family: var(--font-body);
  font-size: var(--t-4);
  line-height: calc(var(--s-1) * 7);
  color: var(--ink);
  resize: vertical;
  transition: border-color 0.24s ease, background 0.24s ease;
}
#briefInput:focus { border-color: var(--ink); background: var(--paper-warm); }
#briefInput::placeholder { color: var(--quiet); font-style: italic; }

/* ============================================================
   ACTION ROW — typeset "Rewrite to the bar" compose + quiet Clear
   ============================================================ */

.action-row {
  display: flex;
  /* CENTRE, not baseline (2026-08-08). Ritu, on the working indicator now
     living in this row: "Make sure they are aligned everywhere." Baseline
     aligns the TEXT inside each child, which is right for two bare text
     buttons and wrong the moment a bordered control joins them: a pill's text
     sits inside its own padding, so baseline hangs the whole capsule low.
     MEASURED against the run button's centre, capsule and Clear both: baseline
     was 1-4px out and worst on DeskTriage (4px, because its Clear is
     display:none rather than visibility:hidden, which changes what the row has
     to align). Centre is 0px on all six tools, for the capsule AND for Clear. */
  align-items: center;
  gap: var(--s-4);
  margin: var(--s-4) 0 0;
  flex-wrap: wrap;
}

.run-btn {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink);
  font-family: var(--font-display);
  font-variation-settings: "wght" 500;
  font-size: var(--t-5);
  line-height: calc(var(--s-1) * 6);
  color: var(--ink);
  padding: 0 0 var(--s-2);
  cursor: pointer;
  letter-spacing: var(--track-tight);
  transition: color 0.24s ease, border-color 0.24s ease,
              transform var(--press-out) var(--press-ease);
}
/* Press feedback (emil law: a button must respond to the press; corporate.css
   carries the same rule for .compose). Feedback on the press only, never on
   the disabled state. Keyboard runs (Cmd+Enter) never pass through :active,
   which is the correct absence: keyboard actions are not animated.
   Depth and curve come from icons.css; read the reasoning there, not here. */
.run-btn:active:not(:disabled) {
  transform: scale(var(--press-deep));
  transition: color 0.24s ease, border-color 0.24s ease,
              transform var(--press-in) var(--press-ease);
}
/* The run button's trailing arrow pseudo-element is CULLED (2026-08-05).
   Ritu's ruling, on the Rewrite button: "REWRITE should also have a logo" and,
   on the icon set as a whole, one mark per button. The run button now carries
   a real <span class="ico"> before its label, so a drawn arrow after it would
   be a second mark on the loudest control on the page. The mark also does what
   the arrow never could: it draws in var(--accent), so the primary action
   lends the page colour instead of more ink.

   ::before is still the 44px touch hit-area (see the touch-floor block below);
   only the reason it could not be the icon has changed.
   Guard: tests/test_css_architecture.py::RetiredArrowTest. */
.run-btn .ico { flex: none; }
/* outline suppression removed 2026-07-28: the affordance (colour / border
   change) stays, the ring is no longer cancelled. Finding 4's rule.
   :hover and :focus-visible no longer share one rule, split below: a tap
   fires :hover on a touch screen and it sticks; keyboard focus must answer
   with no pointer involved at all. */
@media (hover: hover) {
  .run-btn:hover:not(:disabled) {
    color: var(--accent);
    border-bottom-color: var(--accent);
  }
}
.run-btn:focus-visible:not(:disabled) {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
.run-btn:disabled { color: var(--quiet); border-bottom-color: var(--rule); cursor: not-allowed; }

/* Clear is a CONTROL and wears the house pill. Ritu's ruling 2026-08-02, after
   he could not find Clear or Copy on his own tool: "if I, as the builder, can't
   spot them easily, then how can a user do so? It would be a bad idea to have
   the user hunt for buttons", and then, when the first pass reached only
   Corporate: "I said across the project". The hairline-underline treatment was
   the right grammar for a de-emphasised secondary action and the wrong grammar
   for something the reader has to FIND. */
.clear-btn {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--pill);
  color: var(--ink);
  padding: var(--s-2) var(--s-3);
  cursor: pointer;
  min-height: var(--touch-min);
  display: inline-flex;
  align-items: center;
  transition: color 0.2s ease, border-color 0.2s ease,
              transform var(--press-out) var(--press-ease);
}
@media (hover: hover) {
.clear-btn:hover { color: var(--accent); border-color: var(--accent); }
}
.clear-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Clear carried no press before this (measured 2026-09-10). --press-light,
   same idiom as Copy beside it. */
.clear-btn:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: color 0.2s ease, border-color 0.2s ease,
              transform var(--press-in) var(--press-ease);
}

/* ============================================================
   ERROR + STATUS
   ============================================================ */

.error-msg {
  margin-top: var(--s-3);
  padding: var(--s-3) var(--s-3);
  /* Was a 3px warn side-bar — the side-tab tell. The full hairline names
     the state and lets the rule do the structural work (de-tell 2026-07-31,
     Ritu's ruling: "2 and 3, execute"). */
  border: var(--hairline) solid var(--warn);
  background: var(--paper-deep);
  font-family: var(--font-body);
  font-size: var(--t-4);
  line-height: calc(var(--s-1) * 7);
  color: var(--ink);
  display: none;
}
.error-msg.visible { display: block; }

/* The status bar is PINNED TO THE FOOT OF THE WINDOW, and both halves of that
   sentence are the fix (2026-08-07, Ritu: "rewrite button triggers engine but
   doesn't react in any way. Must be the case across the suite").

   It was parked in the normal flow, 63px below the run button on DeskCopy and
   179px below it on DeskPop, on a `--paper-deep` ground. MEASURED, day theme:
   that ground is #E4E9E9 on a #E9EDED page — a contrast ratio of 1.04:1. There
   was no bar. There was a 13px spinner and a line of small grey caps floating
   on unchanged paper, and only when the flow happened to put it on screen: at
   1440x900, sweeping every scroll position where the run button is fully
   visible, the bar was off screen for 21% of them on DeskCopy and 35% on
   DeskPop, and the band it was missing from is precisely the one a user lands
   in — you scroll until the button appears, which puts it at the bottom edge,
   which puts the bar below the fold. A before/after pixel diff of the real
   click measured the whole reaction at 0.08% of the screen, max channel delta
   66: the Rewrite label going from --ink to --quiet. That is what "doesn't
   react in any way" was.

   IT IS A CAPSULE, NOT A BAR, AND THAT IS RITU'S RULING (2026-08-07, from the
   five rendered treatments in mockups/11-status-treatments.html). The first fix
   pinned a full-width --ink slab across the foot, converging with suite.css and
   Corporate. He saw it live: "I don't like the idea of the strip at the bottom
   being black. I just saw it, and well, cringed. Just that strip. Everything
   else is fine." The diagnosis that followed is worth keeping, because it is not
   about the colour: A FULL-WIDTH OVERLAY IS THE ONLY OVERLAY ON THIS PAGE.
   Recolouring the slab to paper makes it quieter without making it belong.
   Roughly a fifth of the mass, in the house pill radius, sitting clear of the
   page edge, does.

   IT SITS BESIDE THE RUN BUTTON, IN THE SAME ROW, ON EVERY TOOL. Ritu,
   2026-08-08: "The display should be next to the REWRITE button across the
   tools. Design aesthetics, don't you think? A user otherwise has to scroll
   away from the masthead and the input box to see that hitting the rewrite
   button did something." Both halves of that are right, and the second is the
   one that closes the last hole in this whole sequence: ON THE SAME LINE AS THE
   BUTTON, IT IS VISIBLE WHENEVER THE BUTTON IS. Nothing to scroll to, and no
   band of scroll positions where the click appears to do nothing.

   HOW THIS THING GOT HERE, BECAUSE THE ROUTE IS THE LESSON. It began pinned to
   the foot of the window (position: fixed), taken on the first fix because in
   the flow it had been scrolled past. Four ships then changed its COLOUR
   (--paper-deep to --ink to paper), its SHAPE (full-width slab to capsule) and
   WHEN IT LEFT (a retirement rule for when it hovered over a finished story) —
   every one of them treating a symptom of the pinning while keeping the pinning.
   Ritu: "It remains in one place while the user can scroll the page", and then
   "this is so frustrating". Unpinning it put it in the flow BELOW the action
   row, which was right in principle and still ~63px from the button. Beside the
   button is the actual answer, and it makes the earlier trade moot: the
   below-the-fold band that unpinning cost simply does not exist here.

   The markup moved with it: #statusBar now lives inside .action-row (or
   .action-left on FieldReportage and ReporterAlly), after the Clear button, on
   all eight tools. It is a member of the row, so the row's flex-wrap carries it
   to its own line when the width runs out.

   It is DELIBERATELY not the sole signal. Ritu reported this same class on
   Corporate on 2026-08-02, so .working-line below carries the wait in the output
   area too — and that line is the part he singled out as the part he likes. */
.status-bar {
  display: none;
  align-items: center;
  gap: var(--s-2);
  width: fit-content;
  /* The row is align-items: baseline, which would hang a bordered pill off the
     Rewrite label's baseline. It centres against the button instead. */
  align-self: center;
  padding: var(--s-3) var(--s-3);
  background: var(--paper-warm, var(--paper));
  border: var(--hairline) solid var(--rule-soft);
  border-radius: var(--pill);
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  /* IT WRAPS RATHER THAN ELLIPSISES, AND THAT WAS MEASURED, NOT ASSUMED. The
     first pass took nowrap + text-overflow: ellipsis, which looks tidier and is
     wrong: the longest step any tool sets is ReporterAlly's "Working through
     the material...", and at 360px it was cut (content 314px into a 310px box)
     and at 320px badly cut (311px into 270px). Losing an authored word to keep
     one tidy line is the wrong trade, so the capsule takes a second line on a
     phone instead. On any normal width the text is far inside the max-width and
     never wraps, so nothing changes there. */
  max-width: 100%;
  line-height: calc(var(--s-1) * 4);
}
/* In the flow, so it takes space only while it is up. It arrives with a short
   rise rather than a slide from the window edge — it is part of the page now,
   not something entering from outside it. Exponential ease-out so the movement
   reads immediately, which is the whole job of a signal that says "heard you". */
.status-bar.visible {
  display: inline-flex;
  animation: statusIn 260ms cubic-bezier(0.22, 0.8, 0.2, 1) both;
}
@keyframes statusIn {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .status-bar.visible { animation: none; }
}
/* UNDER THE NOTES BOX FOR A REVISE RUN (2026-10-04). On a wide screen
   phone-places.js stands the one bar under the notes box while "Edit with
   notes" runs (settleStatus). It keeps the distance it has from Rewrite in
   Draft: the action row's 24px gap. DeskCopy's and DeskPop's .feedback-bar
   gaps 8px, so the bar adds the other 16px. IntroDoctor's Revise shares a row
   with its box, so the bar goes under that row, the same 16px below it as the
   revised intro that follows. */
.feedback-bar > .status-bar { margin-left: var(--s-3); }
.revision-input-row + .status-bar { margin-top: var(--s-3); }
/* BOTH OF THE RULES THAT USED TO LIVE HERE ARE CULLED, because both existed
   only to manage an overlay and there is no overlay any more:

   1. The retirement rule (`body:has(.output-container.visible) .status-bar`),
      which sent the capsule away once output landed. That was the fix for
      "the 'writing' pill is a hovering one" — it hovered because it was fixed.
      In the flow it sits above the fidelity bar and displaces nothing, so it
      can stay up for the senior-editor pass and say what is happening. That
      also closes the gap the retirement rule opened, where Call 2 ran with no
      indicator at all.
   2. `body:has(.status-bar.visible) { padding-bottom: 4.5rem }`, which kept the
      last rows of the page reachable from under a pinned capsule. Nothing is
      pinned, so nothing is covered.

   Guard: tests/test_css_architecture.py::StatusBarInFlowTest. */

.status-bar .spinner {
  display: block;
  flex: none;
  /* Aligned to the FIRST line, not the centre of the box: when the copy wraps
     to two lines on a phone, a vertically centred mark floats between them. */
  align-self: flex-start;
  margin-top: 0.16rem;
  width: 13px; height: 13px;
  /* On paper now, so the ring is the page's own rule and the accent does the
     moving. The one place this control is allowed colour. */
  border: var(--hairline) solid var(--rule-soft);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* Reduced motion is fewer and gentler, not none: the capsule still has to say
     the desk is working, so the slide and the spin go and the capsule stays. */
  .status-bar .spinner { animation: none; opacity: 0.75; }
}

/* THE HELD LINE — the second half of the same fix, and the half that is about
   where the eye actually is.

   Ritu, on Corporate, 2026-08-02, with the fixed bar ALREADY pinned to the
   foot: he ran a draft on prod and reported seeing no activity at all. A fixed
   bar is unmissable in the sense that it is always on screen and entirely
   missable in the sense that it is 51px at the bottom edge of a 900px window
   while you are staring at the button you just pressed and at the empty space
   where the story goes. Corporate's answer was to put one line in the output
   area for the whole wait, and it is his approved pattern; this is that
   component, in the Editorial sheet, on the same dot and the same mono
   register so the two read as one voice rather than two indicators competing.

   MEASURED, and it is why the held line cannot replace the bar here the way it
   nearly could on Corporate: Editorial's output area sits +221px (IntroDoctor,
   HeadlineBench) to +438px (DeskPop) below the run button, because the fidelity
   bar and the action bar sit between them. On its own it would be below the
   fold from the button just as often as the old bar was. Bar AND line.

   It is a placeholder and never the story arriving in pieces: paragraph-by-
   paragraph streaming into the output stays banned. */
.working-line {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-2);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--quiet);
  padding: var(--s-4) 0;
  margin: 0;
}
.working-dot {
  flex: 0 0 auto;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--mark);
  animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { opacity: 0.35; transform: scale(0.8); }
  50%      { opacity: 1;    transform: scale(1.1); }
}
@media (prefers-reduced-motion: reduce) {
  .working-dot { animation: none; opacity: 0.8; }
}

/* quality banner host — neutral; the shared module owns its own styling */
#quality-banner-host:not(:empty) { margin-top: var(--s-4); }

/* ============================================================
   OUTPUT AREA — two columns: the page | the desk
   ============================================================ */

/* Hidden until there is output, exactly as .output-container is on the other
   four tools. Ritu, 2026-08-04: "buttons that become active only after the
   output is generated... I want those to be invisible, just as in Desk Copy."
   DIAGNOSED before fixing, as the plan required, and the difference was NOT
   the bars: DeskCopy renders its fidelity bar pre-output too, disabled, just
   like DeskCopyPro. The difference was that DeskCopyPro rendered a 436px EMPTY
   OUTPUT SCAFFOLD at 1280 where DeskCopy's container was display:none. Its JS
   already added and removed .visible in all the right places and its own
   comment says the scaffold should not show; only this default was missing, so
   the class had nothing to switch. */
.output-area { display: none; }
.output-area.visible {
  display: block;
  margin-top: var(--s-5);
  border-top: var(--hairline) solid var(--rule-soft);
  padding-top: var(--s-5);
}

/* Column labels */
.col-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--quiet);
  font-weight: 500;
  margin-bottom: var(--s-4);
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--rule);
}
.col-label--row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-3); }
.col-label--row .wc-output { letter-spacing: normal; text-transform: none; }
.wc-output {
  font-family: var(--font-mono);
  font-size: var(--t-1);
  color: var(--quiet);
  text-transform: none;
}
.wc-diff-neg { color: var(--good); margin-left: 4px; font-weight: 700; }
.wc-diff-pos { color: var(--warn); margin-left: 4px; font-weight: 700; }

/* The desk (diagnostics aside) */
/* .tp-desk is already sticky from 1001px up (see the min-width rule near
   the tp-grid collapse), so the only thing this modifier still carries is
   DeskCopyPro's boxed treatment of the collapsed desk below 1000px.
   Was .col-eval until 2026-08-05; renamed so one desk has one name. */
.desk-card {
  background: var(--paper-deep);
  border: var(--hairline) solid var(--rule-soft);
  padding: var(--s-4) var(--s-4);
}
.desk-card-label { color: var(--accent); border-bottom-color: var(--rule); }
.tp-desk-summary { display: none; }   /* desktop: the desk-card label is the heading */
.tp-desk .calibration-panel,
.tp-desk .editors-notes-panel { margin-top: var(--s-4); }

/* ── COPY BODY (rewrite output, rendered by JS) ── */
#copyBody { min-width: 0; }
#copyBody[data-draft] { opacity: 0.72; }

.copy-headline {
  font-family: var(--font-display);
  font-variation-settings: "wght" 560;
  font-size: var(--t-7);
  line-height: calc(var(--s-1) * 11);
  letter-spacing: var(--track-tight);
  color: var(--ink);
  margin-bottom: var(--s-3);
  max-width: var(--measure);
}
.copy-standfirst {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-4);
  line-height: calc(var(--s-1) * 7);
  color: var(--ink-soft);
  margin-bottom: var(--s-3);
}
/* The byline shows as written, the way DeskCopy's .rewrite-byline does
   (owner 2026-10-04: DeskCopy Pro's came in capitals). No text-transform
   and no capital tracking; weight and tracking match .rewrite-byline.
   tests/dcp-byline-case.test.js pins this. */
.copy-byline {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 700;
  font-size: var(--t-1);
  letter-spacing: normal;
  color: var(--quiet);
  margin-bottom: var(--s-1);
}
/* Fallback only, when the model returns no body; normally the dateline rides
   at the head of the first body paragraph. Shows as written, not uppercased. */
.copy-dateline {
  font-family: var(--font-mono);
  font-size: var(--t-2);
  letter-spacing: normal;
  color: var(--ink-soft);
  margin-bottom: var(--s-4);
}
.copy-body {
  font-family: var(--font-body);
  font-variation-settings: "opsz" 18, "wght" var(--body-wght);
  font-size: var(--t-4);
  line-height: calc(var(--s-1) * 8);
  color: var(--ink);
  white-space: pre-wrap;
  max-width: var(--measure);
  font-feature-settings: "onum", "tnum";
}
.copy-body p { margin: 0 0 0.85em; }
.copy-subhead {
  font-family: var(--font-display);
  font-variation-settings: "wght" 560;
  font-size: var(--t-5);
  line-height: calc(var(--s-1) * 7);
  letter-spacing: var(--track-tight);
  color: var(--ink);
  margin: var(--s-4) 0 var(--s-2);
  display: block;
}
.copy-notice {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--quiet);
  margin-top: var(--s-3);
}
.copy-block {
  font-family: var(--font-body);
  font-size: var(--t-4);
  line-height: calc(var(--s-1) * 8);
  color: var(--ink);
  white-space: pre-wrap;
}
/* value-add web-sourced markers */
.copy-body .value-add-marker {
  font-weight: 700;
  background: linear-gradient(to bottom, transparent 60%, var(--value-mark) 60%);
  padding: 0 1px;
}

/* ── EVAL COLUMN (diagnostics, rendered by JS) ── */
.eval-genre {
  font-family: var(--font-body);
  font-size: var(--t-3);
  color: var(--ink);
  margin-bottom: var(--s-2);
  padding: var(--s-1) 0;
}
.eval-genre-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--quiet);
  margin-right: var(--s-2);
}
.verdict-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--s-3) var(--s-3);
  margin-bottom: var(--s-3);
  /* Was a 3px status side-bar — the side-tab tell. The verdict-label already
     carries the typographic signal; the full hairline carries the colour
     (de-tell 2026-07-31, Ritu's ruling). */
  border: var(--hairline) solid var(--accent);
  background: var(--paper-warm);
}
.verdict-bar.pass { border-color: var(--good); }
.verdict-bar.warn { border-color: var(--mark); }
.verdict-bar.fail { border-color: var(--warn); }
.verdict-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink);
  font-weight: 700;
}
.verdict-bar.pass .verdict-label { color: var(--good); }
.verdict-bar.warn .verdict-label { color: var(--ink); }
.verdict-bar.fail .verdict-label { color: var(--warn); }
.score-ring {
  font-family: var(--font-display);
  font-variation-settings: "wght" 600;
  font-size: 1rem;
  color: var(--ink);
  letter-spacing: var(--track-tight);
}
.eval-section { margin-bottom: var(--s-4); }
.eval-section-title {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--s-2);
  font-weight: 500;
}
.chk-list { list-style: none; padding: 0; margin: 0; }
.chk-item {
  display: flex;
  gap: var(--s-2);
  align-items: flex-start;
  padding: var(--s-2) 0;
  font-family: var(--font-body);
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--ink);
  border-bottom: var(--hairline) solid var(--rule-soft);
}
.chk-item:last-child { border-bottom: none; }
.chk-icon { flex-shrink: 0; width: 14px; text-align: center; font-weight: 700; }
.chk-ok .chk-icon { color: var(--good); }
.chk-warn .chk-icon { color: var(--mark); }
.chk-err .chk-icon { color: var(--warn); }
.chk-text { flex: 1; color: var(--ink); }
.notes-block {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 7);
  color: var(--ink);
  padding: var(--s-3) var(--s-3);
  background: var(--paper-warm);
  border: var(--hairline) solid var(--rule-soft); border-radius: var(--box);
}
/* Editorial decisions as numbered points */
.diag-points { list-style: none; margin: 0; padding: 0; counter-reset: dp; }
.diag-points li {
  position: relative;
  padding: var(--s-2) 0 var(--s-2) var(--s-4);
  font-family: var(--font-body);
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--ink);
  border-bottom: var(--hairline) solid var(--rule-soft);
}
.diag-points li:last-child { border-bottom: none; }
.diag-points li::before {
  counter-increment: dp;
  content: counter(dp);
  position: absolute;
  left: 0; top: 0.55rem;
  width: 19px; height: 19px;
  border: var(--hairline) solid var(--rule-soft);
  border-radius: 50%;
  background: var(--paper-warm);
  color: var(--ink);
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 700;
  font-size: var(--t-1);
  display: flex; align-items: center; justify-content: center;
}
.diag-points-lead {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--quiet);
  margin: 0 0 var(--s-2);
}
.eval-fallback {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-3);
  color: var(--quiet);
  padding: var(--s-3) 0;
}

/* ── Calibration panel (senior editor flags) ── */
.calibration-panel { margin-top: var(--s-4); border-top: var(--hairline) solid var(--rule-soft); padding-top: var(--s-3); }
.calibration-panel[hidden] { display: none; }
.calibration-panel-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--quiet);
  font-weight: 500;
  margin-bottom: var(--s-2);
}
.calibration-flag {
  display: grid;
  grid-template-columns: 130px 1fr;
  gap: var(--s-2);
  padding: var(--s-2) 0;
  border-bottom: var(--hairline) solid var(--rule-soft);
  font-family: var(--font-body);
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
}
.calibration-flag:last-child { border-bottom: none; }
.calibration-flag-type {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 500;
  font-size: var(--t-1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink);
}
.calibration-flag-detail { color: var(--ink); }
.calibration-flag-detail em { font-style: italic; color: var(--quiet); }

/* ── Editor's notes panel (value-add citations) ── */
.editors-notes-panel { margin-top: var(--s-4); border-top: var(--hairline) solid var(--rule-soft); padding-top: var(--s-3); }
.editors-notes-panel[hidden] { display: none; }
.editors-notes-panel-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--quiet);
  font-weight: 500;
  margin-bottom: var(--s-2);
}
.editors-notes-panel-hint {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--quiet);
  margin-bottom: var(--s-3);
  max-width: 60ch;
}
.editors-notes-panel-hint strong { font-weight: 700; color: var(--ink); font-style: normal; }
.editors-note-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-2) var(--s-3);
  padding: var(--s-2) 0;
  border-bottom: var(--hairline) solid var(--rule-soft);
  font-family: var(--font-body);
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
}
.editors-note-row:last-child { border-bottom: none; }
.editors-note-addition { font-weight: 700; color: var(--ink); grid-column: 1 / -1; }
.editors-note-para {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 400;
  font-size: var(--t-1);
  color: var(--quiet);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  margin-left: var(--s-1);
}
.editors-note-hole { color: var(--quiet); font-style: italic; }
.editors-note-url { font-size: var(--t-2); word-break: break-all; }
.editors-note-url a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--mark); }
.editors-note-empty { color: var(--quiet); font-style: italic; }

/* ============================================================
   ACTION BAR — quiet mono action-links under a rule
   (Copy / Export / Bulletise-injected / Prev / Next)
   ============================================================ */

.action-bar {
  display: flex;
  gap: var(--s-4);
  align-items: center;
  flex-wrap: wrap;
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: var(--hairline) solid var(--rule-soft);
  max-width: calc(100% - 360px - 3rem);
}
/* The `.idle` bar state is CULLED (2026-08-05). It was DeskCopyPro's own way
   of saying what every other desk says with the `disabled` attribute, and two
   mechanisms for one state is what let the restore path leave a whole bar
   unclickable: the slice restore repainted the output and never took the class
   off, while the shared layer, which only knows about `disabled`, had no idea
   the class existed. One mechanism now.
   Guard: tests/test_css_architecture.py::ActionBarConvergenceTest.

   Its other job was making an inert button LOOK inert, which the desks never
   did: there was no `.copy-btn:disabled` rule at all, so a dead Copy button
   was pixel-identical to a live one. That is half of why Ritu could not tell
   what was clickable. It is a real rule now, on both button families. */
.copy-btn:disabled,
.hist-btn:disabled { opacity: 0.35; cursor: not-allowed; }

/* Prev / Next / counter travel together and are hidden until there is a second
   version to move between. Ritu, 2026-08-05: "WHAT ARE THE PREV & NEXT BUTTONS
   FOR? NEVER TRIED THEM." They step through the last three versions of a
   rewrite in this session, so on a fresh page, and after any reload, the stack
   is genuinely empty and the pair sat there greyed and meaningless.

   [hidden] needs the explicit rule: an inline-flex declaration would otherwise
   beat the user agent's display:none and the group would show through. */
.hist-group { display: inline-flex; align-items: center; gap: var(--s-4); }
.hist-group[hidden] { display: none; }

/* Copy is the most-reached-for control on a finished story and it read as a
   caption. Same ruling as .clear-btn above. Outline, never filled: the compose
   action stays the one loud thing on the page. */
.copy-btn,
.hist-btn {
  appearance: none;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--pill);
  font-family: var(--font-mono);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink);
  padding: var(--s-2) var(--s-3);
  cursor: pointer;
  min-height: var(--touch-min);
  display: inline-flex;
  align-items: center;
  transition: color 0.2s ease, border-color 0.2s ease,
              transform var(--press-out) var(--press-ease);
}
@media (hover: hover) {
.copy-btn:hover:not(:disabled),
.hist-btn:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }
}
.copy-btn:focus-visible,
.hist-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.copy-btn.copied { color: var(--good); border-color: var(--good); }
/* Copy and the history pair carried no press before this (measured
   2026-09-10: six control families pressed, and this was not one).
   --press-light, same depth as Fact check beside them. */
.copy-btn:active:not(:disabled),
.hist-btn:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: color 0.2s ease, border-color 0.2s ease,
              transform var(--press-in) var(--press-ease);
}
.hist-count {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  color: var(--quiet);
  min-width: 32px;
  text-align: center;
}

/* ============================================================
   FEEDBACK / REVISION — disclosure accordion
   ============================================================ */

.feedback-section {
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: var(--hairline) solid var(--rule-soft);
  max-width: var(--measure);
}

/* Disclosure trigger: replaces the static .feedback-label */
.feedback-disclosure-trigger {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--touch-min);
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--quiet);
  font-weight: 500;
  width: 100%;
  text-align: left;
  transition: color 0.18s ease;
}
@media (hover: hover) {
.feedback-disclosure-trigger:hover { color: var(--ink); }
}
.feedback-disclosure-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.feedback-disclosure-chevron {
  flex-shrink: 0;
  font-size: 0.75rem;
  color: var(--quiet);
  transition: transform 0.18s ease;
  line-height: 1;
}
.feedback-section.open .feedback-disclosure-chevron { transform: rotate(90deg); }

/* Collapsible panel — hidden by default, shown when .open */
.feedback-disclosure-body {
  display: none;
  margin-top: var(--s-3);
}
.feedback-section.open .feedback-disclosure-body { display: block; }

/* Keep the old .feedback-label class available (unused now but harmless) */
.feedback-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--quiet);
  font-weight: 500;
  margin-bottom: var(--s-2);
}
.feedback-textarea {
  width: 100%;
  min-height: 5rem;
  background: var(--paper-deep);
  border: 1px solid var(--rule);
  border-radius: var(--box);
  padding: var(--s-3) var(--s-3);
  font-family: var(--font-body);
  font-size: var(--t-4);
  line-height: calc(var(--s-1) * 8);
  color: var(--ink);
  resize: vertical;
  transition: border-color 0.2s ease, background 0.2s ease;
}
.feedback-textarea:focus { border-color: var(--ink); background: var(--paper-warm); }
.feedback-textarea::placeholder { color: var(--quiet); font-style: italic; }
.revise-btn {
  margin-top: var(--s-3);
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink);
  /* canonical display-serif action spec — matches .run-btn / .compose so peer
     compose/edit actions read as one family (was opsz 60 / 1.15rem, drifted). */
  font-family: var(--font-display);
  font-variation-settings: "wght" 500;
  font-size: var(--t-5);
  line-height: calc(var(--s-1) * 6);
  letter-spacing: var(--track-tight);
  color: var(--ink);
  padding: 0 0 var(--s-2);
  cursor: pointer;
  /* The RELEASE half of the press. It belongs on the resting rule, because the
     return is what happens when :active is dropped. Without it the button
     springs back instantly and only half the movement is felt. */
  transition: color 0.22s ease, border-color 0.22s ease,
              transform var(--press-out) var(--press-ease);
}
/* :hover and :focus-visible no longer share one rule, split below: a tap
   fires :hover on a touch screen and it sticks; keyboard focus must answer
   with no pointer involved at all. */
@media (hover: hover) { .revise-btn:hover:not(:disabled) { color: var(--accent); border-bottom-color: var(--accent); } }
.revise-btn:focus-visible:not(:disabled) { color: var(--accent); border-bottom-color: var(--accent); }
/* THE PRESS, which this button never had. Ritu, 2026-09-06: the "Edit with
   notes" button "works but appears unresponsive every time you hit them".
   Measured: .revise-btn carried hover, focus-visible and disabled and no
   :active at all, so the one moment the eye is on it, nothing happened.

   This is not a new decision. He settled it on 2026-08-17 ("the main buttons...
   should feel alive... just for a split second, almost like blinking. Do it for
   the entire project") and the depth, curve and reasoning are at the top of
   this sheet. Edit with notes runs a model call, so it is a MAIN action and
   takes --press-deep, exactly as .run-btn beside it does. The August pass built
   the system and applied it to a roster that never included this button;
   tests/test_css_architecture.py::SharedPressTest now fails if a button that
   starts a run is missing from it again. */
.revise-btn:active:not(:disabled) {
  transform: scale(var(--press-deep));
  transition: color 0.22s ease, border-color 0.22s ease,
              transform var(--press-in) var(--press-ease);
}
.revise-btn:disabled { color: var(--quiet); border-bottom-color: var(--rule); cursor: not-allowed; }

/* ============================================================
   SETTINGS MODAL
   ============================================================ */

.settings-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: var(--overlay-bg);
  z-index: 998;
}
.settings-backdrop.visible { display: block; }

.settings-panel {
  display: none;
  position: fixed;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: min(92vw, 820px);
  max-height: 85vh;
  overflow-y: auto;
  z-index: 999;
  background: var(--paper);
  border: var(--hairline) solid var(--rule-soft);
  border-top: 3px solid var(--brand-rule);
  padding: var(--s-5) var(--s-5);
  box-shadow: 0 24px 64px var(--overlay-bg);
}
.settings-panel.visible { display: block; }
.settings-panel h2 {
  font-family: var(--font-display);
  font-variation-settings: "wght" 540;
  font-size: var(--t-6);
  letter-spacing: var(--track-tight);
  color: var(--ink);
  margin-bottom: var(--s-2);
}
.settings-subtitle {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--quiet);
  margin-bottom: var(--s-4);
  max-width: 60ch;
}
.settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3) var(--s-4); }

/* THE PRESET NOTE IS PROSE, SO IT TAKES THE MEASURE PROSE TAKES.
   Ritu on the live drawer, 2026-09-06: "the bunched text". Measured at 1512:
   the sentence under the heading ran 554px on one line while the sentence
   under PRESET was squeezed into the dropdown's own column, 220px on
   FieldReportage and 315px on ReporterAlly, and broke into two short lines
   under a wide empty panel. It was inheriting a width meant for a control.
   The cap now sits on the control. Neither number below is new or measured:
   220px is the cap this group already carried, and the ReporterAlly select
   keeps exactly the width one grid cell gave it, written in the grid's own
   terms so it follows the grid instead of a frozen pixel count. */
.preset-group > .settings-subtitle { max-width: 60ch; }
[data-tool="fieldreportage"] .preset-group > select { max-width: 220px; }
[data-tool="reporterally"] .preset-group > select { max-width: calc((100% - var(--s-4)) / 2); }

/* TWO NAMED GROUPS INSIDE THE DRAWER. Ritu's ruling of 2026-09-05, taken on the
   rendered comparison in mockups/21-panel-and-hero: when Genre and Add context
   move out of the left panel they land beside Numbers and Month abbreviation
   under a heading that reads "Stylesheet settings", where Genre reads as a
   stylesheet dial and Add context is left alone on a row. His Mockup C of 09-03
   had already asked for the split; the drawer's own subtitle draws the line.

   The subgroup spans BOTH columns and re-establishes the two-column run inside
   itself, rather than using the grid's own tracks: a heading that only spanned
   one column would let a group's second control ride up beside its own title.

   THE HEADING IS A SMALL SIBLING OF THE DRAWER'S OWN h2, not a fourth micro-label.
   The first draft gave it the label recipe exactly -- display face, --t-1,
   uppercase, --track-caps, --quiet, weight 500 -- which is byte-for-byte what
   .setting-group label already is, so a group title and the field titles under
   it were indistinguishable and only a hairline separated them. suite.css:487
   records the same lesson from the suite page: "an h2 and its child h3 were
   indistinguishable" until the weight actually differed. So the hierarchy is
   carried four ways at once, none of them decoration: size (19px against 12px),
   case (sentence against uppercase), colour (--ink against --quiet) and weight
   (600 against 500, on the real Schibsted Grotesk 400..800 axis).

   No rule under the title, and none needed: --s-5 above each group after the
   first separates them, which is more space above the heading than the --s-3
   below it. Grouping is done with space, the way the drawer already does it. */
.settings-subgroup { grid-column: 1 / -1; }
.settings-subgroup + .settings-subgroup { margin-top: var(--s-5); }
.settings-subgroup > h3 {
  font-family: var(--font-display);
  font-variation-settings: "wght" 600;
  font-size: var(--t-4);
  line-height: calc(var(--s-1) * 6);
  letter-spacing: var(--track-tight);
  color: var(--ink);
  margin: 0 0 var(--s-3);
}
.settings-subgroup > .rowset { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3) var(--s-4); }

.setting-group { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
.setting-group label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--quiet);
  font-weight: 500;
}
.setting-group select,
.setting-group input[type="text"] {
  width: 100%;
  padding: var(--s-2) var(--s-2);
  font-family: var(--font-body);
  font-size: var(--t-3);
  color: var(--ink);
  background: var(--paper-warm);
  border: 1px solid var(--rule);
  min-height: var(--touch-min);
}
.setting-group select:focus,
.setting-group input[type="text"]:focus { outline: 0; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.setting-group input[type="text"]::placeholder { color: var(--quiet); font-family: var(--font-body); font-style: italic; }
.setting-group select option { background: var(--paper); color: var(--ink); }

.settings-footer {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: var(--hairline) solid var(--rule-soft);
  flex-wrap: wrap;
}
.btn-save-settings {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  padding: var(--s-2) var(--s-3);
  min-height: var(--touch-min);
  color: var(--cta-ink);
  background: var(--cta-fill);
  border: 1px solid var(--cta-fill);
  --ico-color: currentColor;
  cursor: pointer;
  transition: opacity 0.18s ease;
}
@media (hover: hover) {
.btn-save-settings:hover { opacity: 0.88; }
}
.btn-reset-settings {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  padding: var(--s-2) var(--s-3);
  min-height: var(--touch-min);
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--rule);
  cursor: pointer;
  transition: border-color 0.18s ease, color 0.18s ease;
}
@media (hover: hover) {
.btn-reset-settings:hover { border-color: var(--accent); color: var(--accent); }
}
.btn-save-settings:focus-visible,
.btn-reset-settings:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.settings-saved-msg {
  font-family: var(--font-mono);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--good);
  display: none;
}

/* ============================================================
   FOOTER — colophon (liftable block)
   ============================================================ */

.tool-foot {
  width: var(--frame);
  margin: 4rem auto 0;
  padding-top: var(--s-4);
  border-top: var(--hairline) solid var(--rule-soft);
  font-family: var(--font-body);
  font-size: var(--t-3);
  color: var(--quiet);
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--s-3);
}
/* colophon grouped to the right; the "← Editorial suite" switch stays left */
.tool-foot .colophon {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  align-items: flex-end;
  text-align: right;
  margin-left: auto;
}
/* Footer colophon: themes like the rest of the footer (no pill) — text is the
   page ink (dark in Day/Press, light in Night), right-aligned flush with the
   .powered line below it. The copyright mark shows original colours in Day/Press,
   and a muted grey in Night (see below). */
.tool-foot .copy {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  flex-wrap: wrap;
  color: var(--quiet);
  font-size: var(--t-2);
}
.tool-foot .copy img {
  height: 22px;
  width: auto;
  vertical-align: middle;
  position: relative;
  top: -1px;
}
/* Day/Press: original brand colours. Night is near-black, where the navy goes
   illegible — so the copyright mark lightens to the muted grey of the T.U.L.U.
   signature (not stark white). */
[data-theme="night"] .tool-foot .copy img { filter: brightness(0) invert(1); opacity: 0.55; }
.tool-foot .powered {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  font-style: italic;
  letter-spacing: normal;
  color: var(--quiet);
  opacity: 0.78;
}
.tool-foot .back {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
}
.tool-foot a {
  color: var(--ink-soft);
  border-bottom: var(--hairline) dotted var(--quiet);
  transition: color 0.2s ease, border-color 0.2s ease;
}
/* :hover and :focus-visible no longer share one rule, split below: a tap
   fires :hover on a touch screen and it sticks; keyboard focus must answer
   with no pointer involved at all. */
@media (hover: hover) { .tool-foot a:hover { color: var(--accent); border-bottom-color: var(--accent); } }
.tool-foot a:focus-visible { color: var(--accent); border-bottom-color: var(--accent); }

/* Hide the original tool-name footer (kept in DOM for any JS that reads it). */
.legacy-foot { display: none !important; }

/* ============================================================
   RESPONSIVE — two-pane collapses to single column + a collapsible desk
   ============================================================ */

@media (max-width: 1000px) {
  /* .tp-grid/.tp-page collapse at this same breakpoint (see the tp-grid
     rule below); .two-col and .col-rewrite were a second name for exactly
     that and were culled 2026-08-05. The aside's default order 0 already
     puts it before the order-2 page, so no order is needed here either. */
  .tp-desk--boxed { margin-bottom: var(--s-4); border: var(--hairline) solid var(--rule-soft); }
  .tp-desk-summary {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--s-2);
    cursor: pointer;
    padding: var(--s-3) var(--s-3);
    min-height: var(--touch-min);
    background: var(--paper-deep);
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform: uppercase;
    color: var(--ink);
  }
  .tp-desk-summary .tp-chev { font-size: 0.85rem; color: var(--quiet); transition: transform 0.15s ease; }
  .tp-desk:not(.collapsed) .tp-desk-summary .tp-chev { transform: rotate(90deg); }
  .tp-desk.collapsed .tp-desk-body { display: none; }
  .desk-card { margin: 0; border-left: none; padding: var(--s-4) var(--s-3) var(--s-1); }
  .desk-card .desk-card-label { display: none; }
  .tp-desk .calibration-panel,
  .tp-desk .editors-notes-panel { padding: 0 var(--s-3) var(--s-3); }
  .action-bar { max-width: 100%; }
}

@media (max-width: 760px) {
  /* clip, not hidden, since 2026-09-12. Both stop a sideways scrollbar; only
     hidden makes the element a SCROLL CONTAINER, and a scroll container
     between a sticky element and the viewport is what a sticky element sticks
     to. With hidden here, the pinned masthead below --phone-max computed
     position: sticky and still scrolled away: MEASURED at 390, scrolled to
     282px, the masthead's own top read -282px. With clip it holds at 0.
     clip creates no scroll container, so nothing between the masthead and the
     viewport moves, and the sideways scroll this rule was written to stop is
     stopped exactly as before. */
  html, body { overflow-x: clip; }
  .tp-grid, .tp-page, .tp-desk, .copy-body { min-width: 0; }
  .settings-grid { grid-template-columns: 1fr; }
  /* AND THE TOOL'S OWN COPY OF THAT RULE, which beat the line above for months.
     [data-tool="fieldreportage"] .settings-grid is declared at line 4306,
     outside every media query and LATER in this file than this block, so at
     equal specificity it won and the drawer stayed at two columns on a phone.
     Naming the drawer the grid sits in outranks it wherever either sits.

     The first fix for this was written at the FOOT of the sheet instead, to win
     on order, and tests/phone_places.test.js correctly refused it: the phone
     layer is one block. Specificity is the right lever; source order is the one
     this repo has already been bitten by, on 2026-09-13.

     MEASURED at 358 with the drawer open: two columns of 142.8px, giving each
     menu 126.8px of text, and SIX options across four menus needed more:
     "Six or more letters abbreviated" 172.4, "Corporate communications" 152.1,
     "Capitalised in running text" 145.6, "Lowercase in running text" 143.8,
     "Off (strict source-fidelity)" 140.5 and "Thicken (all of the above)"
     139.4. None was ever readable in full on a phone. */
  [data-tool="fieldreportage"] .settings-panel .settings-grid { grid-template-columns: 1fr; }

  /* AND THE PRESET MENU TAKES THE COLUMN, for the same reason one step along.
     Its cap is written as half a cell, calc((100% - var(--s-4)) / 2), which was
     exactly right while the grid had two columns: it kept the control the width
     of the cell beside it. On a phone the grid has ONE column, so half of it is
     half the screen.

     MEASURED at 320, 358 and 412 on ReporterAlly: "AP baseline (universal)"
     needs 155.4px and the box gave it 116, 126.8 and 153.3. That option is the
     DEFAULT, so the fault showed the moment the drawer opened. FieldReportage's
     own cap is a flat 220px and clears its longest preset at every width, so it
     is left alone. */
  [data-tool="reporterally"] .preset-group > select { max-width: 100%; }

  /* AND A DRAWER THAT IS STILL AN OVERLAY OPENS AT THE PAGE'S OWN GUTTER.
     var(--s-5) is 40px each side, a desk's margin, and it is right for a panel
     floating in the middle of a 1440 screen. At 320 the panel is 294.4px wide,
     so 80 of it is margin and the menus inside get 196.4. MEASURED on DeskCopy
     V3: "Six or more letters abbreviated" needs 210.7 and was sliced at that
     width alone.

     :not([data-phone-moved]) SCOPES THIS TO THE DRAWERS THAT STAY OVERLAYS.
     assets/js/phone-places.js stamps that attribute on the drawer it moves into
     the Desk place, where the drawer is not an overlay but ordinary content in
     the page, already reset by this block and already on the page's own column.
     The attribute is the page's own statement of which is which, rather than a
     list of tool names that would go stale. */
  .settings-panel:not([data-phone-moved]) { padding: var(--s-3); }
  /* The named groups collapse WITH the grid they sit in, at the grid's own
     breakpoint. A second, nearer breakpoint of their own would leave the
     groups running two columns inside a drawer already down to one. */
  .settings-subgroup > .rowset { grid-template-columns: 1fr; }
  .masthead-utility { gap: var(--s-3); }
  /* Masthead padding stays IDENTICAL to desktop across breakpoints — the
     brand spacing around the wordmark never shifts (Ritu, 2026-07-02). */
  /* Hide descriptor on mobile — mirrors landing (descriptor hidden ≤760 px) */
  .masthead-utility .suite-mark { display: none; }
}

@media (max-width: 560px) {
  /* The wordmark owns row 1 — sacrosanct, nothing beside it. All controls sit
     on ONE compact row below (toggle · Settings · Teach), left-aligned to the
     gutter. The masthead stays exactly two rows; it never grows into a ladder.
     Controls are tightened just enough to fit a ~360px phone while keeping the
     44px touch height. */
  /* The masthead's 2rem gap is the space BETWEEN the wordmark and the controls
     on a desktop, where they sit side by side. On a phone they are two stacked
     rows, so the same 2rem becomes 32px of dead air inside the chrome. The
     wordmark's own padding is untouched (Ritu, 2026-07-02: the brand spacing
     around the mark never shifts) — only the row gap closes. */
  .tool-masthead { gap: var(--s-2); }
  .masthead-utility {
    width: 100%;
    justify-content: flex-start;
    align-items: center;
    gap: var(--s-1);
  }
  /* Pills keep the 44px touch height but fill the capsule: nudge the mono label
     up ~1-2px so it doesn't float in an empty pill (the "outsized" look). */
  .masthead-utility .theme-toggle button { padding: 0 var(--s-2); min-width: 0; font-size: var(--t-1); }
  .masthead-utility .settings-toggle { padding: var(--s-2) var(--s-2); font-size: var(--t-2); }
  /* Teach leaves the phone masthead (Ritu, 2026-07-17): device font scaling
     can overflow the compact row and strand the navy pill on a row of its
     own. The Room stays reachable via the editorial home; tablet and desktop
     keep the masthead pill. */
  .masthead-utility .mast-nav-link { display: none; }
  /* ...except where the row has nothing else in it, which is the Scroll Desk
     and only the Scroll Desk. Ritu, 2026-09-12, asked for the same symmetry
     rule here that the editorial tools follow.

     His 2026-07-17 reason for the rule above is CROWDING, and this row is
     not crowded: the story control ships empty on this page, the theme toggle
     is hidden by the page's own [data-theme="social"] rule, and the settings
     button was culled on his word in 2026-08-17 with a test holding it culled.
     MEASURED at 360 / 390 / 412 with the row as it stood: the wordmark 23.2 /
     23.5 / 23.7 from the left and the last thing in the masthead 236.5 /
     266.3 / 288.1 from the right, a lopsidedness of 213 to 264px that the
     wordmark gate has reported since 2026-08-20. With the pill back: 24.2 /
     24.5 / 24.7 on the right against 23.2 / 23.5 / 23.7 on the left, a delta
     of 1.0px, which is .wordmark img's documented optical offset and nothing
     else. The masthead still stands at 54px, so the parity his other ruling
     asked for is untouched, and the pill measures 101.4 x 44.

     :not(:has(.settings-toggle)) states the crowding condition rather than a
     page name: scroll-desk.html is the only page in the repo with a nav pill
     and no settings button, and any page that later loses its settings button
     is by that fact a page with room for this one. It also closes a real hole:
     on a phone this desk had NO route to the Teaching Room, and his own words
     on the app are that access to it is imperative. */
  .masthead-utility:not(:has(.settings-toggle)) .mast-nav-link { display: inline-flex; }

  /* ---- a section title and its actions stop breaking mid-word ----
     Ritu, 2026-09-12, on his phone: "in FieldReportage, on the STORY page, why
     should the word FIELD INTELLIGENCE and FACT CHECK be split into two ugly
     lines", and then "it's actually there in ReporterAlly too. I don't know,
     maybe even in the other tools."

     .panel-header is a nowrap flex row, so when its content is wider than the
     column its CHILDREN shrink and their words break instead of the row
     wrapping. MEASURED on FieldReportage's Story at 390: the title took 2
     lines in a 117px box, the Fact check label 2 lines in a 52px box, Clear 2
     lines in a 51px box, inside a header that still reported one row.

     One row cannot hold all three whole and that is arithmetic, not taste:
     FIELD INTELLIGENCE sets 167px, FACT CHECK 132px and CLEAR 103px, which is
     402px of words in a 341px column before a single gap. So the title takes
     the row it needs and the two actions share the next, which is the ordinary
     shape of a section heading with controls under it. Measured after, at 360,
     390 and 412: two rows, every label on one line, header 96px against 65px.
     The 31px is what not breaking words costs.

     Scoped to [data-place] so it reaches the phone layer's panels and leaves
     the desktop and the place-less pages alone. */
  [data-place] .panel-header { flex-wrap: wrap; row-gap: var(--s-2); }
  [data-place] .panel-header > h2 { flex: 1 0 100%; white-space: nowrap; }
  [data-place] .panel-header .btn-label { white-space: nowrap; }

  /* ---- the tool footer stops huddling ----
     Ritu, 2026-07-28: "the footers in all the tools are bunched together like
     awkward toads". The desktop rule is a space-between row with the colophon
     pushed right by margin-left:auto and align-items:flex-end. On a 350px shell
     the back-link and the colophon cannot share a row, so they wrap — and the
     wrapped colophon keeps its right alignment and its flex-end pull, landing
     as a ragged right-aligned block jammed under a left-aligned link. Measured
     121px tall on DeskCopyPro at 390px.

     A phone footer is a stack. One column, honest spacing.

     THE ALIGNMENT HALF OF THAT IS REVERSED, 2026-09-12, on his newer word:
     "Footers should be on the right. The copyright mark ... should always be
     on the right." The 2026-07-28 rule sent the colophon left along with the
     back-link. What made the old right alignment look ragged was flex-end on
     a COLUMN whose two lines are different lengths, under a left-aligned
     link: the block's left edge was jagged and its right edge only sometimes
     flush. Right-aligning the TEXT as well as the box gives both lines the
     same right edge, which is the thing he is asking for and the thing the
     old rule never actually produced. The back-link stays left, so the row
     still reads as two ends of a footer rather than one huddle. */
  .tool-foot { flex-direction: column; align-items: stretch; gap: var(--s-3); }
  .tool-foot .colophon { margin-left: auto; align-items: flex-end; text-align: right; }
  .tool-foot .copy, .tool-foot .powered { text-align: right; }

  /* Ritu, 2026-09-12, on a photograph of his own phone: "The footer doesn't
     need All Tools on mobile. That will help align Teach with the copyright
     information and make it more aesthetic."

     With the colophon already right-aligned, the back row was the one thing
     left on the other side, so the foot read as two edges pulling apart. Teach
     alone, right-aligned with the copyright, gives the footer one column and
     one edge.

     :has(.foot-teach) WAS THE WHOLE RULE, AND IT IS GONE. It hid All tools only
     where Teach could take its place, sparing teach.html and app.html because
     on those two the back link was the only way out of the page. That was true
     when it was written and it stopped being true the same day: the wordmark
     now goes to /app from every surface at phone width (assets/js/app-home.js),
     so every page has a way out that is not a footer link.

     What the exception left behind was worse than untidy. Measured at 390:
     "All tools" was 0x0 on five tools and 102x24 on teach.html and app.html,
     and on both of those it still pointed at editorial.html. So the two pages
     that kept it were the two that could still drop the reader onto the desktop
     landing page, which is the thing he has now asked to stop three times.
     His ruling is plain and has no exception in it: All tools comes off the
     mobile footers.

     SPECIFICITY, and it is the reason the first attempt at this made things
     worse rather than better. `.tool-foot .back a` (line 5107) sets
     display: inline-flex, which is two classes and an element, and it sits two
     thousand lines further down. A plain `.tool-foot .foot-all` is two classes
     and loses on both counts, so writing the obvious rule turned All tools ON
     everywhere instead of off: measured at 390, computed display inline-flex,
     102px wide, on every tool. The old rule only ever won because :has() took
     it to four classes. Naming the anchor takes this one to three classes and
     an element, which is enough. */
  .tool-foot .back a.foot-all,
  .tool-foot .back .foot-sep { display: none; }
  .tool-foot .back { text-align: right; }
  /* "DeskCopy Pro · Editorial · Powered by T.U.L.U. Logic" is 52 characters and
     cannot hold one line in a 350px shell. Balanced so it breaks into two even
     lines instead of stranding "Logic" alone on the second. */
  .tool-foot .powered { text-wrap: balance; }

  /* The hero eyebrow carries 0.22em of tracking, which is right on a desktop and
     is what pushed DeskCopyPro's "EDITORIAL · FULL COPYWRITING SUITE" onto two
     lines at 390px (Ritu, 2026-07-28) — 34 characters needing ~320px inside a
     350px shell, before any device font scaling. The tracking eases on a phone;
     the label stays one line and still reads as a tracked mono eyebrow. */
  .display-eyebrow { letter-spacing: var(--track-caps); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ===== DeskCopy-specific output panels (1st-occurrence; not shared) =====
   Class names are locked by byte-identical JS in DeskCopy_V3_0.html.
   Token mapping (old inline → framework):
     --brand / --focus → --accent   (primary teal / navy)
     --cream           → --paper-deep
     --muted           → --quiet
     --keep            → --good
     --cut             → --warn
     'Fraunces'        → var(--font-display)
     'Lora'            → var(--font-body)
     'Inter'           → var(--font-mono)
   ================================================================ */

/* ── Output container visibility ── */
.output-container { display: none; }
/* Ritu, 2026-08-07, on live prod: "Output box touches Copy, Export and
   Bulletise buttons." Measured: the gap was EXACTLY 0px on DeskCopy and
   DeskPop. `.action-bar` carries margin-top and padding-top — all of its air
   is above itself — and nothing here pushed the output down.

   It was 0px before the corners were curved too. A square panel butted against
   a button row read as attached, so nobody saw it; a rounded box butted against
   the same row reads as collided. The curve did not cause this, it revealed it.

   1.5rem matches the .tp-grid margin-bottom below, so the output block now sits
   with equal air above and below instead of hanging off the buttons.
   DeskCopyPro is untouched: it uses .output-area and already measured 96px. */
.output-container.visible { display: block; margin-top: var(--s-4); }

/* ── Diagnosis panel (restructured 2026-07-12: chips + hairline blocks) ── */
.diagnosis-panel {
  background: var(--paper-deep);
  border: var(--hairline) solid var(--rule-soft);
  border-radius: var(--box);
  padding: var(--s-4) var(--s-4) var(--s-4);
  margin-bottom: var(--s-5);
  box-shadow: none;
}
.diagnosis-panel h3 {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 700;
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--s-4);
  display: flex;
  align-items: center;
  gap: var(--s-2);
}
.diagnosis-panel h3::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

/* glance chips — Genre (outline) + Purpose (solid). Solid uses ink/paper so it
   inverts cleanly and stays AA-legible in Day, Night and Press. */
.diag-chips {
  display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center;
  padding-bottom: var(--s-4); border-bottom: 1px solid var(--rule);
}
.diag-chip {
  display: inline-flex; align-items: center;
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1); letter-spacing: var(--track-caps);
  text-transform: uppercase; padding: var(--s-2) var(--s-2); border-radius: var(--pill);
}
.diag-chip.chip-outline { background: transparent; color: var(--ink-soft); border: 1px solid var(--rule); }
.diag-chip.chip-solid   { background: var(--ink); color: var(--paper); border: 1px solid var(--ink); }
.diag-purpose-note {
  font-family: var(--font-body); font-size: var(--t-3); line-height: calc(var(--s-1) * 6);
  color: var(--quiet); margin: var(--s-3) 0 0;
}

/* analysis blocks — hairline-divided, accent tick on each label */
.diag-block { padding: var(--s-4) 0; border-bottom: var(--hairline) solid var(--rule-soft); }
.diag-block:last-child { border-bottom: 0; padding-bottom: 0; }
.diag-block-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-weight: 700; font-size: var(--t-1);
  letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--accent);
  margin-bottom: var(--s-2); display: flex; align-items: center; gap: var(--s-2);
}
.diag-block-body { font-family: var(--font-body); font-size: var(--t-3); line-height: calc(var(--s-1) * 6); color: var(--ink); }
.note-label { font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-weight: 700; font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--accent-deep); margin-bottom: var(--s-2); font-style: normal; }
.diag-block-body ul { margin: 0; padding-left: var(--s-3); }
.diag-block-body li { margin-bottom: var(--s-2); }

/* structural plan — one labelled line per step */
.diag-plan { display: flex; flex-direction: column; gap: var(--s-2); }
.diag-plan-step {
  font-family: var(--font-body); font-size: var(--t-3); line-height: calc(var(--s-1) * 6);
  color: var(--ink); margin: 0;
}
.diag-plan-k {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1); letter-spacing: var(--track-caps);
  text-transform: uppercase; color: var(--accent); margin-right: var(--s-2);
  padding: 0.12rem var(--s-2); border: var(--hairline) solid var(--rule-soft); border-radius: var(--pill);
  white-space: nowrap;
}

/* ── Two-pane output: LEFT "the page" / RIGHT "the desk" ── */
.tp-grid {
  display: grid;
  grid-template-columns: minmax(0, 600px) minmax(340px, 1fr);
  gap: var(--s-5);
  align-items: start;
  margin-bottom: var(--s-4);
}
.tp-page { min-width: 0; }

/* ── Rewrite panel ── */
.rewrite-panel {
  background: var(--paper);
  border: var(--hairline) solid var(--rule-soft);
  border-radius: var(--box);
  padding: var(--s-5) var(--s-5);
  margin-bottom: var(--s-4);
  box-shadow: none;
}
.rewrite-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: var(--s-4);
  padding-bottom: var(--s-3);
  border-bottom: var(--hairline) solid var(--rule-soft);
}
.rewrite-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 700;
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--quiet);
}
.rewrite-wc {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 500;
  font-size: var(--t-1);
  letter-spacing: normal;
  color: var(--quiet);
}
.rewrite-wc .up { color: var(--good); margin-left: 4px; font-weight: 700; }
.rewrite-wc .dn { color: var(--warn); margin-left: 4px; font-weight: 700; }
.rewrite-headline {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--t-7);
  line-height: calc(var(--s-1) * 12);
  letter-spacing: var(--track-tight);
  color: var(--ink);
  margin-bottom: var(--s-3);
  max-width: var(--measure);
}
.rewrite-byline {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 700;
  font-size: var(--t-1);
  letter-spacing: normal;
  color: var(--quiet);
  margin-bottom: var(--s-2);
}
.rewrite-body {
  font-family: var(--font-body);
  font-size: var(--t-4);
  line-height: calc(var(--s-1) * 8);
  color: var(--ink);
  max-width: var(--measure);
}
.rewrite-body p { margin-bottom: var(--s-3); }
.rewrite-body p:last-child { margin-bottom: 0; }

/* ── Value-add control (Add context dropdown) ── */
.value-add-control {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}
.value-add-control label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 700;
  font-size: var(--t-1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--quiet);
}
.value-add-control select {
  padding: var(--s-2) var(--s-4) var(--s-2) var(--s-2);
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  /* 16px, not 14. See the 16px floor note above. This one was NOT in the
     2026-09-20 render sweep: on every desk measured, a more specific rule won
     and set the size, so the control never painted at 14. The declaration is
     still under the floor and wins wherever that other rule does not reach,
     which is why tests/input-zoom-floor.test.js reads the sheet rather than
     trusting a render to have visited every combination. It found this one. */
  font-size: var(--t-3);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--accent);
  border-radius: 0;
  cursor: pointer;
}
.value-add-control select:not([value=""]) { background: var(--paper-deep); }
.value-add-control select:focus {
  border-color: var(--accent);
  outline: none;
  box-shadow: 0 0 0 2px var(--accent);
}

/* ── Feedback panel ── */
.feedback-panel {
  background: transparent;
  border: none;
  border-top: var(--hairline) solid var(--rule-soft);
  padding: var(--s-5) 0 0;
  margin-top: var(--s-5);
}
/* h2, not h3: on DeskCopy and DeskPop this was the only heading after the
   page h1, so the document ran h1 -> h3 with nothing at h2 (tools brief,
   section 1). The rank changed; the type did not. 2026-07-28. */
.feedback-panel h2 {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 700;
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--quiet);
  margin-bottom: var(--s-2);
}
.feedback-hint {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--quiet);
  margin-bottom: var(--s-3);
  max-width: 60ch;
}
.feedback-bar {
  display: flex;
  gap: var(--s-2);
  margin-top: var(--s-3);
}

/* ── Responsive: collapse tp-grid below 1000 px ── */
@media (max-width: 1000px) {
  .tp-grid { grid-template-columns: 1fr; gap: 0; }
  .tp-page { order: 2; }
  /* diag-grid collapses here too (source used 960px): once tp-grid is
     single-column at 1000px, a two-column diag-grid inside it reads cramped,
     so the two collapses are intentionally consolidated to one breakpoint. */
  .diag-grid { grid-template-columns: 1fr; }
}
/* Desk pins while the article is read (desktop two-pane only). Internal scroll
   keeps it pinned even when the structural plan runs long. */
@media (min-width: 1001px) {
  .tp-desk {
    position: sticky;
    top: 1.5rem;
    align-self: start;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}
@media (max-width: 960px) {
  .rewrite-panel { padding: var(--s-4); }
}
@media (max-width: 520px) {
  .rewrite-headline { font-size: var(--t-6); }
}


/* ============================================================
   DeskCopy / DeskPop shared output bits
   The input layout (toggle-row, input-label, raw-input, action-row, run-btn,
   clear-btn, error-msg) is the shared model library above. The old DeskPop
   single-row controls CSS was removed 2026-06-21 once DeskPop + IntroDoctor
   moved to the model. Only two JS-emitted output bits remain, shared by both.
   ============================================================ */
/* JS-emitted single note block (value-add summary). */
.editors-note { margin-top: var(--s-3); padding: 16px var(--s-3); background: var(--paper-deep); border: var(--hairline) solid var(--rule-soft); border-radius: var(--box); font-family: var(--font-body); font-size: 14px; line-height: calc(var(--s-1) * 6); color: var(--ink); font-style: italic; }

/* Soft-lock: output visible but dimmed while Call 2 finalises */
#rewriteContent[data-draft] { opacity: .72; }

/* ── Brief box show-back panel ── */
.brief-panel { margin-top: var(--s-2); }
.brief-panel-label { font-weight: 600; margin-bottom: 4px; color: var(--ink); }
.brief-panel-hint { font-size: 12px; color: var(--quiet); margin: 0 0 8px; }
.brief-row { display: flex; align-items: baseline; gap: 8px; padding: var(--s-1) 0; border-top: var(--hairline) solid var(--rule-soft); font-size: var(--t-1); }
.brief-tag { text-transform: uppercase; font-size: var(--t-1); letter-spacing: var(--track-caps); padding: 1px var(--s-1); border: var(--hairline) solid var(--rule-soft); border-radius: 3px; color: var(--quiet); flex: none; }
.brief-text { flex: 1; }
.brief-where { color: var(--quiet); font-size: var(--t-1); }
.brief-reclass { flex: none; background: none; border: var(--hairline) solid var(--rule-soft); border-radius: 0; font-size: var(--t-1); padding: 1px var(--s-1); cursor: pointer; color: var(--accent); }
.brief-row--conflict .brief-text em { font-style: italic; }

/* ---- Fidelity assist ---- */
.fidelity-bar { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-2); margin: var(--s-5) 0 var(--s-4); }
/* THE WORKING ROW OF THE THREE DESKS (2026-10-03). Fact check, Style pass,
   Bulletise, Compare, in the order an editor works them; Compare is last
   because it is used only after everything else. Ritu: "Ideally, I would like
   the row to be on one line." The row is named by the Compare it holds, so
   no other tool's bar changes, and in the app, where Compare moves into the
   action bar, the rule stops matching.
   One gap between every pill: the row's own, var(--s-3). The gap used to be
   var(--s-2) and the empty status spans between the buttons each took a gap
   of their own, so the space between two pills was 8, 16 or 24px depending on
   how many spans stood between them. An empty span takes no box here.
   FEEDBACK GOES BELOW (2026-10-03, fix round 2). A status with words in it, or
   the Style pass undo pill, used to sit between the buttons and push the later
   pills onto another line. Now the four pills never split: feedback takes
   order 1, so it follows them, and when any feedback exists an ::after of
   100% basis ends the pills' line, so the feedback starts its own line,
   flush with the left edge of the first pill. The break exists only while
   there is feedback, so an empty row gains no height. The ::after is a line
   of its own with no height, and a flex line cannot have a negative height,
   so it adds two row gaps. While feedback exists the row gap is var(--s-2),
   which makes the space between the pills and the feedback 16px, the pills'
   own gap, and the space between feedback lines 8px.
   The rules are gated to the desktop: not below 700px, not in the installed
   app, not under html.panel. In the app the bar is a grid with a 4px gap and
   these rules, at higher specificity, would otherwise win it while Compare is
   still in the bar (before the output lands). */
@media (min-width: 700px) and (display-mode: browser), (min-width: 700px) and (display-mode: minimal-ui), (min-width: 700px) and (display-mode: fullscreen) {
html:not(.panel) .fidelity-bar:has(> #btnCompare) { gap: var(--s-3); }
html:not(.panel) .fidelity-bar:has(> #btnCompare) > :is(.fidelity-status, .prov-status):empty { display: none; }
html:not(.panel) .fidelity-bar:has(> #btnCompare) > :is(.fidelity-status, .prov-status):not(:empty),
html:not(.panel) .fidelity-bar:has(> #btnCompare) > .style-undo-pill { order: 1; }
html:not(.panel) .fidelity-bar:has(> #btnCompare):has(> :is(.fidelity-status, .prov-status):not(:empty), > .style-undo-pill) { row-gap: var(--s-2); }
html:not(.panel) .fidelity-bar:has(> #btnCompare):has(> :is(.fidelity-status, .prov-status):not(:empty), > .style-undo-pill)::after {
  content: ''; flex: 0 0 100%; height: 0;
}
}
.fidelity-btn {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1); letter-spacing: var(--track-caps);
  text-transform: uppercase; color: var(--btn-ink);
  background: transparent; border: 1px solid var(--rule); border-radius: 0;
  padding: var(--s-2) var(--s-2); cursor: pointer;
  transition: border-color 0.2s ease, transform var(--press-out) var(--press-ease);
}
@media (hover: hover) {
.fidelity-btn:hover { border-color: var(--accent); }
}
/* Fact check and Style pass press too, at the SECONDARY depth. The ruling at
   the top of this sheet names the fact bar among the controls that keep
   --press-light, and corporate.css already gives `.fact-bar button` exactly
   this. Missed here in the August pass, found on 2026-09-06 while fixing the
   Edit with notes button beside it. Never on :disabled: these two spend most of
   their life disabled, and a dead control must not answer a click. */
.fidelity-btn:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: border-color 0.2s ease, transform var(--press-in) var(--press-ease);
}
/* Shape 2 (Ritu's ruling 2026-07-31): the sheet the Style pass applies is
   chosen where it is used. Same idiom as .fidelity-btn. */
.style-sheet-picker {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1); letter-spacing: var(--track-caps);
  text-transform: uppercase; color: var(--btn-ink);
  background: transparent; border: var(--hairline) solid var(--rule-soft); border-radius: 0;
  padding: var(--s-2) var(--s-2); cursor: pointer; min-height: var(--touch-min);
}
.fidelity-btn:disabled { opacity: 0.6; cursor: default; }
.fidelity-status { font-family: var(--font-body); font-size: var(--t-3); color: var(--quiet); }
mark.fidelity-flag { background: transparent; border-bottom: var(--hairline) solid var(--accent); color: inherit; cursor: pointer; padding: 0 1px; }
.fidelity-pop {
  margin: var(--s-2) 0; background: var(--paper-warm);
  border: var(--hairline) solid var(--rule-soft);
  border-radius: 0; padding: var(--s-3) var(--s-3);
  font-family: var(--font-body); font-size: var(--t-3); line-height: calc(var(--s-1) * 6);
}
.fidelity-pop .fidelity-why { color: var(--warn); font-weight: 600; margin-bottom: var(--s-1); }
.fidelity-pop .fidelity-fix { color: var(--ink); margin-bottom: var(--s-1); }
.fidelity-pop .fidelity-src { color: var(--quiet); font-size: var(--t-2); margin-bottom: var(--s-2); }
.fidelity-acts { display: flex; gap: var(--s-2); }
.fidelity-acts button { font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; border-radius: 0; padding: var(--s-1) var(--s-2); cursor: pointer; }
.fidelity-apply { background: var(--cta-fill); color: var(--cta-ink); border: 1px solid var(--cta-fill); --ico-color:currentColor; }
.fidelity-dismiss { background: transparent; color: var(--accent-deep); border: var(--hairline) solid var(--rule-soft); }
.fidelity-list { margin-top: var(--s-2); }
.fidelity-list .fidelity-row { display: flex; align-items: baseline; gap: var(--s-2); padding: var(--s-2) 0; border-top: var(--hairline) solid var(--rule-soft); font-size: var(--t-2); }
.fidelity-tag { text-transform: uppercase; font-size: var(--t-1); letter-spacing: var(--track-caps); padding: 1px var(--s-1); border: var(--hairline) solid var(--rule-soft); border-radius: 3px; color: var(--quiet); flex: none; }
.fidelity-text { flex: 1; color: var(--ink-soft); }

/* Mechanical style pass: same visual family as the fidelity assist. */
mark.style-change { background: transparent; border-bottom: var(--hairline) dotted var(--accent); color: inherit; cursor: pointer; padding: 0 1px; }
mark.style-flag { background: transparent; border-bottom: var(--hairline) dashed var(--warn); color: inherit; cursor: pointer; padding: 0 1px; }
.style-undo-pill { font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; padding: var(--s-1) var(--s-2); border: 1px solid var(--rule); background: transparent; color: var(--accent-deep); cursor: pointer; }
@media (hover: hover) {
.style-undo-pill:hover { border-color: var(--accent); }
}

/* ============================================================
   IntroDoctor — tool-specific (lede alternatives, coherence note, revision)
   Onboarded 2026-06-21. Settings/diagnosis/controls-row/genre-override/input-fields/
   status/spinner reuse the shared library above (incl. DeskPop's controls rules).
   Only IntroDoctor's bespoke alternatives + revision UI live here. Tokens remapped
   from the legacy byline-kit palette; dimensions unchanged.
   ============================================================ */
.coherence-note { margin-top: var(--s-3); padding: var(--s-3) var(--s-3); background: var(--paper-deep); border: var(--hairline) solid var(--rule-soft); border-radius: var(--box); font-family: var(--font-body); font-style: italic; font-size: 14px; line-height: calc(var(--s-1) * 6); color: var(--ink); }
.coherence-note strong { font-style: normal; font-family: var(--font-display); font-weight: 700; letter-spacing: var(--track-tight); color: var(--ink); }

.alternatives-heading { font-family: var(--font-display); font-weight: 400; font-style: italic; font-size: var(--t-6); line-height: calc(var(--s-1) * 8); letter-spacing: -.01em; color: var(--ink); margin: 8px 0 var(--s-3); }

.alt-card { background: var(--paper); border: var(--hairline) solid var(--rule-soft); border-radius: var(--box); padding: 24px var(--s-4); margin-bottom: var(--s-3); transition: border-color .15s ease; }
@media (hover: hover) {
.alt-card:hover { border-color: var(--accent-deep); }
}
.alt-card.lateral { background: var(--paper-deep); border: var(--hairline) solid var(--rule-soft); }
/* Ritu's catch, 2026-08-02: "after each alternative the copy button comes very
   close to the text." Rendering the real card showed why, and it was NOT the
   pill: `.alt-label` was added as a wrapper around the number and strategy and
   never given any CSS, so the header's `gap` stopped separating them (it now
   sees one child, not two) and nothing pushed the button to the card's right
   edge. The header read "Option 1SCENE FIRST (COPY)" with the control jammed
   against the label and the whole right half of the card empty. Giving the
   wrapper the flex behaviour the header used to do restores both: the gap
   between number and strategy, and the button back on the right edge.
   align-items is centre, not baseline: a 44px control has no text baseline
   worth aligning and baseline-hanging pushed it toward the rule below. */
/* 2026-09-16: the strategy sentence moved OUT of this wrapper and became the
   header's own child, so that on a phone it can take a full-width line of its
   own. CSS cannot reparent, and while it sat in here `100%` meant 100% of the
   label, which is what was left after the button, not the card.
   The desktop is unchanged and the `order` below is what keeps it that way:
   label, sentence, button, exactly the order they were written in. */
.alt-label { display: flex; align-items: baseline; gap: var(--s-2); flex: 0 1 auto; min-width: 0; order: 1; }
.alt-card-header .alt-strategy { flex: 1 1 auto; order: 2; }
.alt-card-header .btn-copy { order: 3; flex-shrink: 0; }
.alt-card-header { display: flex; align-items: center; gap: var(--s-2); margin-bottom: var(--s-2); padding-bottom: var(--s-2); border-bottom: var(--hairline) solid var(--rule-soft); }
.alt-number { font-family: var(--font-display); font-weight: 700; font-size: var(--t-5); line-height: calc(var(--s-1) * 6); color: var(--ink); letter-spacing: var(--track-tight); flex-shrink: 0; }
.alt-strategy { font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-weight: 700; font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-soft); flex: 1; }
.alt-card.lateral .alt-number, .alt-card.lateral .alt-strategy { color: var(--ink); }
.alt-body { font-family: var(--font-body); font-size: 16px; line-height: calc(var(--s-1) * 7); color: var(--ink); margin-bottom: var(--s-2); white-space: pre-wrap; max-width: var(--measure); }
.alt-annotation { font-family: var(--font-body); font-style: italic; font-size: 14px; line-height: calc(var(--s-1) * 6); color: var(--ink-soft); }
.alt-card.lateral .alt-annotation { color: var(--ink); }
/* THE OPTION HEADER STACKS ON A PHONE, 2026-09-16.

   Ritu sent IntroDoctor's output from his phone: "Need I say anything?" He did
   not. The strategy line was squeezed into a 126px column, ran to ten lines,
   spilled 34px past the card at 358 and 56px at 320, and the Copy button sat
   on top of the words.

   MEASURED, header height: 53px at 1440, 207px at 320, 358, 390 and 412. One
   row cannot hold a big "Option 4", a bordered tag, a sentence of tracked caps
   and a button inside 310px, and flex answered by starving the only item that
   could shrink.

   So the sentence takes its own line under the number and the tag, and the
   button aligns to the top row rather than floating against a tall block.
   Nothing changes above 700px, where it already fits on one line. */
.lateral-tag { display: inline-block; font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-weight: 700; font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink); padding: 3px 8px; background: var(--paper); border: var(--hairline) solid var(--accent); margin-left: var(--s-1); }

.revision-panel { background: transparent; border: none; border-top: var(--hairline) solid var(--rule-soft); padding: var(--s-4) 0 0; margin-top: var(--s-4); }
.revision-panel h3 { font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-weight: 700; font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-soft); margin-bottom: 8px; }
.revision-hint { font-family: var(--font-body); font-style: italic; font-size: 14px; line-height: calc(var(--s-1) * 5); color: var(--ink-soft); margin-bottom: var(--s-3); max-width: 60ch; }
.revision-input-row { display: flex; gap: var(--s-2); align-items: flex-start; flex-wrap: wrap; }
/* The wrapper the mic and the box share, so the mark has its own box's edge to
   sit on rather than the panel's. It carries the flex behaviour the textarea
   used to carry. */
.revision-box { flex: 1 1 280px; min-width: 280px; display: block; }
.revision-textarea { width: 100%; min-width: 0; min-height: 80px; padding: var(--s-2); font-family: var(--font-body); font-size: var(--t-3); line-height: calc(var(--s-1) * 5); color: var(--ink); background: var(--paper-deep); border: 1px solid var(--rule); border-radius: var(--box); resize: vertical; outline: none; }
.revision-textarea:focus { border-color: var(--accent-deep); box-shadow: 0 0 0 2px var(--accent); }
.revision-textarea::placeholder { color: var(--quiet); font-style: italic; }

.revised-output { display: none; margin-top: var(--s-3); padding: var(--s-3) 24px; background: var(--paper); border: var(--hairline) solid var(--rule-soft); font-family: var(--font-body); font-size: 16px; line-height: calc(var(--s-1) * 7); color: var(--ink);
  border-radius: var(--box);
}
.revised-output.visible { display: block; }
.revised-output strong { font-family: var(--font-display); font-weight: 700; color: var(--ink); font-style: normal; letter-spacing: var(--track-tight); }

@media (max-width: 960px) { .alt-card { padding: var(--s-3) var(--s-3); } }
@media (max-width: 520px) { .alternatives-heading { font-size: var(--t-5); } }

/* IntroDoctor emits per-alternative copy buttons with the legacy class `btn-copy`
   from JS (kept byte-identical); alias it to the shared copy-btn appearance. */
/* Ritu's catch, 2026-08-02: "Be careful about Intro Doctor, where after each
   alternative the copy button comes very close to the text." He was right, and
   the pill made it worse — .alt-body leaves 10px and the button is now a 44px
   object with a visible ring, so it sat almost on the lede it belongs to.
   A bordered control needs its own air or it reads as attached to the sentence
   above it. The margin lives on the button, not on .alt-body, so every context
   that emits a .btn-copy after prose gets the same clearance. */
.btn-copy { appearance: none; background: transparent; border: 1px solid var(--rule); border-radius: var(--pill); font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink); padding: var(--s-2) var(--s-3); cursor: pointer; min-height: var(--touch-min); display: inline-flex; align-items: center; transition: color 0.2s ease, border-color 0.2s ease, transform var(--press-out) var(--press-ease); }
@media (hover: hover) {
.btn-copy:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }
}
.btn-copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn-copy.copied { color: var(--good); border-color: var(--good); }
/* Carried no press before this (measured 2026-09-10, IntroDoctor's own Copy).
   --press-light, same depth as .copy-btn, which this class is an alias of. */
.btn-copy:active:not(:disabled) { transform: scale(var(--press-light)); transition: color 0.2s ease, border-color 0.2s ease, transform var(--press-in) var(--press-ease); }


/* ============================================================
   DeskTriage — diagnostic output, API-key row, signal rows
   Onboarded 2026-06-21. Input area (input-label/raw-input/action-row/run-btn/
   clear-btn/error-msg/status-bar) + btn-copy reuse the shared library. The
   JS-emitted diag-* / sig-* class names are kept verbatim. Tokens remapped from
   the legacy byline-kit palette; dimensions unchanged.
   NOTE: the "risk" signal was true-red (--cut); mapped to --warn here per the
   suite's no-separate-error-red decision. Swap to a dedicated --danger if red is
   wanted back for diagnostic risk.
   ============================================================ */
.api-row { display: flex; align-items: center; gap: var(--s-2); margin-bottom: var(--s-3); padding: var(--s-2) var(--s-2); background: var(--paper-deep); border: var(--hairline) solid var(--rule-soft); }
.api-label { font-family: var(--font-mono); font-weight: 700; font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-soft); white-space: nowrap; flex-shrink: 0; }
.api-input { flex: 1; background: var(--paper); border: 1px solid var(--accent-deep); padding: 8px var(--s-2); font-family: var(--font-mono); font-size: 12px; color: var(--ink); outline: none; border-radius: var(--box); }
.api-input:focus { box-shadow: 0 0 0 2px var(--accent); }
.api-input::placeholder { color: var(--quiet); }
.eye-btn { font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-weight: 700; font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; padding: var(--s-2) var(--s-2); background: transparent; color: var(--accent-deep); border: 1.5px solid var(--accent-deep); border-radius: 0; cursor: pointer; white-space: nowrap; transition: background .15s ease, color .15s ease; }
@media (hover: hover) {
.eye-btn:hover { background: var(--accent-deep); color: var(--paper); --ico-color:currentColor; }
}

.wc-bar { margin-top: 8px; text-align: right; }
.wc-note { font-family: var(--font-mono); font-weight: 500; font-size: var(--t-1); letter-spacing: normal; color: var(--quiet); }
.lede .accent { font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-weight: 700; font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink); font-style: normal; display: block; margin: var(--s-1) 0 0; }

.diag-action-bar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: var(--s-3) 0; border-top: var(--hairline) solid var(--rule-soft); border-bottom: var(--hairline) solid var(--rule-soft); margin-bottom: 24px; }
.output-divider { display: none; border: none; border-top: 3px solid var(--accent-deep); margin: var(--s-4) 0 24px; }
.output-divider.visible { display: block; }

.diag-header { display: flex; align-items: baseline; gap: var(--s-3); margin-bottom: 24px; padding-bottom: var(--s-3); border-bottom: var(--hairline) solid var(--rule-soft); }
.diag-title { font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-weight: 700; font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-soft); }
.diag-status { font-family: var(--font-mono); font-weight: 700; font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--good); }

.diag-row { padding: var(--s-3) 0; border-bottom: var(--hairline) solid var(--rule-soft); opacity: 0; transform: translateY(5px); animation: dt-rowIn .35s ease forwards; }
.diag-row:last-child { border-bottom: none; }
@keyframes dt-rowIn { to { opacity: 1; transform: translateY(0); } }
.diag-row-label { font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-weight: 700; font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-soft); margin-bottom: 8px; display: flex; align-items: center; gap: var(--s-2); }
.diag-row-label::after { content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--rule); margin-left: auto; }
.diag-row.sig-ok .diag-row-label::after { background: var(--good); }
.diag-row.sig-warn .diag-row-label::after { background: var(--mark); }
.diag-row.sig-risk .diag-row-label::after { background: var(--warn); }
.diag-row-value { font-family: var(--font-body); font-size: 16px; line-height: calc(var(--s-1) * 7); color: var(--ink); white-space: pre-line; }
.diag-row-value strong { font-family: var(--font-display); font-weight: 700; letter-spacing: var(--track-tight); }
.sig-ok { color: var(--good); }
.sig-warn { color: var(--ink); }
.sig-risk { color: var(--warn); }

.settings-coming-soon { margin-top: var(--s-3); padding: var(--s-3) var(--s-3); background: var(--paper-deep); border: var(--hairline) solid var(--rule-soft); font-family: var(--font-body); font-style: italic; font-size: 14px; line-height: calc(var(--s-1) * 5); color: var(--ink); }

@media (prefers-reduced-motion: reduce) { .diag-row { animation: none; opacity: 1; transform: none; } }

/* Balanced rag on short prose + display headings — even line lengths so no middle
   line juts out (recurring polish, 2026-06-21). text-wrap:balance is spec-scoped to
   short blocks; long body prose is unaffected. */
h1.display,
.settings-subtitle,
.revision-hint,
.feedback-hint,
.editors-notes-panel-hint { text-wrap: balance; }


/* ============================================================
   FieldReportage + ReporterAlly — exception tools (bespoke layouts kept).
   Integrated 2026-06-21. Each rule SCOPED under [data-tool="<tool>"] so its
   redefinitions (notably .main as a 2-col grid) never touch the model tools.
   ============================================================ */
[data-tool="fieldreportage"] *::before, [data-tool="fieldreportage"] *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* Column: --shell, not a fixed cap. This rule read `max-width:1320px` with
   `padding:28px 40px 32px`, which is the same pattern .tool-masthead was moved
   off on 2026-07-28 and for the same reason. --shell caps at 82rem and then
   centres; a fixed 1320 cap with a fixed 40px inset does not track it, so above
   the cap the body column and the masthead's wordmark drift apart. Measured on
   prod 2026-08-02, wordmark vs hero: 1440 -> 49px out, 1180 -> 0, 820 -> -4,
   390 -> +5. Ritu saw it as the mark hanging off to the left. Deriving the
   column from --shell locks it to the masthead at every width. */
[data-tool="fieldreportage"] .main {
    flex:1;
    width:var(--frame);
    max-width:none;
    margin:0 auto;
    /* Opens on the same expression every other tool opens on; see the note on
       [data-tool="reporterally"] .ra-hero. */
    padding: clamp(2rem, 5vw, 3.5rem) 0 var(--s-4);
    display:grid;
    grid-template-columns:1fr 1fr;
    gap: var(--s-4);
    min-height:0;
  }
[data-tool="fieldreportage"] .panel-input {
    display:flex;
    flex-direction:column;
    min-width:0;
  }
[data-tool="fieldreportage"] .mode-desc {
    font-family:var(--font-body);
    font-style:italic;
    font-size: var(--t-2);
    line-height: calc(var(--s-1) * 6);
    color:var(--ink-soft);
    margin-bottom: var(--s-3);
    max-width:62ch;
  }
[data-tool="fieldreportage"] .context-line {margin-bottom: var(--s-3)}
[data-tool="fieldreportage"] .context-line textarea {
    width:100%;
    padding: var(--s-2) var(--s-2);
    font-family:var(--font-body);
    font-style:italic;
    /* THE HOUSE BOX, 19 on 28, var(--t-4) on the agreed scale. Measured across
       all three suites: every box a person types or pastes into runs it — the
       four rewrite desks, DeskTriage, IntroDoctor, Headliner, Teach and all
       nine Corporate boxes. Ritu ruled this one back and forth on 2026-08-21
       and settled it after seeing both rendered: "Let's go for symmetry and the
       house style." The crowding he was guarding against is in the six field
       boxes below, which are closed at first paint and DELIBERATELY stay at
       14/24 — "the descriptions for scene and location, people, etc., let them
       be." This box is the twin of ReporterAlly's first box and matches it. */
    font-size: var(--t-4);
    line-height: calc(var(--s-1) * 7);
    color:var(--ink);
    background:var(--paper);
    border:1px solid var(--accent-deep);
    border-radius: var(--box);
    outline:none;
    resize:none;               /* no drag-resize; the rows attribute sets the height */
    display:block;
  }
[data-tool="fieldreportage"] .context-line textarea:focus {box-shadow:0 0 0 2px var(--accent)}
[data-tool="fieldreportage"] .context-line textarea::placeholder {color:var(--quiet);font-style:italic}
[data-tool="fieldreportage"] .harvest-scroll {
    flex:1;
    overflow-y:auto;
    padding-right:4px;
    margin-bottom: var(--s-3);
  }
[data-tool="fieldreportage"] .harvest-scroll::-webkit-scrollbar {width:6px}
[data-tool="fieldreportage"] .harvest-scroll::-webkit-scrollbar-thumb {background:var(--rule)}
@media (hover: hover) {
[data-tool="fieldreportage"] .harvest-scroll::-webkit-scrollbar-thumb:hover {background:var(--quiet)}
}
/* THE DISCLOSURE BOX IS NOW BOTH REPORTAGE TOOLS' (Ritu, 2026-08-21):
   "ReporterAlly's input box for anything left over should close just like
   all the boxes in FieldReportage... There are already sub-heads telling
   the user what those boxes are for." One component, one set of rules,
   rather than a second collapsible invented for the other tool. */
[data-tool="fieldreportage"] .harvest-section, [data-tool="reporterally"] .harvest-section {
    /* 8px, not 16: closed by default since 2026-08-21, these read as a
       list of prompts rather than six separate cards. */
    margin-bottom: var(--s-2);
    background:var(--paper-deep);
    border:1px solid var(--rule);
  }
[data-tool="fieldreportage"] .harvest-header, [data-tool="reporterally"] .harvest-header {
    display:flex;
    align-items:center;
    gap: var(--s-2);
    /* 12px vertical, not 16: closed by default since 2026-08-21, six of these
       stack as a list rather than as six cards. The whole row is the click
       target, so the floor is stated rather than left to arithmetic. Padding
       alone rendered 43px, one under his 44px floor of 2026-07-28, which is
       why min-height carries it instead of a padding number chosen to land
       on 44. MEASURED at 44px on both tools. */
    padding: 12px var(--s-3);
    min-height: var(--touch-min);
    cursor:pointer;
    user-select:none;
    transition:background .12s ease, transform var(--press-out) var(--press-ease);
  }
@media (hover: hover) {
[data-tool="fieldreportage"] .harvest-header:hover, [data-tool="reporterally"] .harvest-header:hover {background:var(--rule-soft)}
}
/* A whole section row carried no press before this (measured 2026-09-10).
   --press-light: it discloses a section, it does not run anything. */
[data-tool="fieldreportage"] .harvest-header:active:not(:disabled), [data-tool="reporterally"] .harvest-header:active:not(:disabled) {transform:scale(var(--press-light));transition:background .12s ease, transform var(--press-in) var(--press-ease);}
[data-tool="fieldreportage"] .harvest-toggle, [data-tool="reporterally"] .harvest-toggle {
    display:inline-block;
    font-size: var(--t-1);
    color:var(--ink-soft);
    transition:transform .2s ease;
    flex-shrink:0;
    width:12px;
    text-align:center;
  }
[data-tool="fieldreportage"] .harvest-section.open .harvest-toggle, [data-tool="reporterally"] .harvest-section.open .harvest-toggle {transform:rotate(90deg)}
[data-tool="fieldreportage"] .harvest-label, [data-tool="reporterally"] .harvest-label {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    color:var(--ink);
  }
[data-tool="fieldreportage"] .harvest-hint, [data-tool="reporterally"] .harvest-hint {
    font-family:var(--font-body);
    font-style:italic;
    font-size: var(--t-1);
    color:var(--ink-soft);
    margin-left:auto;
    /* Ritu, 2026-09-12, on the built screens: "In FieldReportage, the
       explanation for SURPRISES & GAPS is misaligned."

       margin-left: auto pushes this box to the right of the header, so a
       hint that fits on one line ends flush on the column edge and looks
       right-aligned. It is not: the text inside is start-aligned, and the
       moment a hint wraps, its SECOND line begins at the box's left edge and
       stops well short of that column.

       MEASURED on FieldReportage's Notes at 390, where the column ends at
       348.5: the four one-line hints all end on 348.5; "What was said?" wraps
       and its second line runs 266.3..293.9; "What caught you off guard?"
       wraps and its second line runs 211.7..248.5, stopping 100px short of
       the edge its neighbours hold. That ragged left-aligned block against
       four flush lines is what he saw.

       text-align: right makes every line end where the box ends, wrapped or
       not, so the hint column reads as one edge at any number of lines. It
       changes nothing where a hint does not wrap, which is every hint at
       desktop width, and that is measured rather than assumed. */
    text-align: right;
  }
[data-tool="fieldreportage"] .harvest-body, [data-tool="reporterally"] .harvest-body {
    display:none;
    padding: 0 var(--s-3) var(--s-3);
  }
[data-tool="fieldreportage"] .harvest-section.open .harvest-body, [data-tool="reporterally"] .harvest-section.open .harvest-body {display:block}
[data-tool="fieldreportage"] .harvest-body textarea, [data-tool="reporterally"] .harvest-body textarea {
    width:100%;
    min-height:90px;
    padding: var(--s-2) var(--s-2);
    font-family:var(--font-body);
    /* 16px, not 14. See the 16px floor note above. These are eight note boxes,
       not the one story surface, so they take the interface size var(--t-3)
       rather than textarea.raw-input's var(--t-4). */
    font-size: var(--t-3);
    line-height: calc(var(--s-1) * 6);
    color:var(--ink);
    background:var(--paper);
    border:1px solid var(--rule);
    border-radius: var(--box);
    resize:vertical;
    outline:none;
    transition:border-color .12s ease;
  }
[data-tool="fieldreportage"] .harvest-body textarea:focus, [data-tool="reporterally"] .harvest-body textarea:focus {border-color:var(--accent-deep);box-shadow:0 0 0 2px var(--accent)}
[data-tool="fieldreportage"] .harvest-body textarea::placeholder, [data-tool="reporterally"] .harvest-body textarea::placeholder {color:var(--quiet);font-style:italic;font-size: var(--t-1)}
[data-tool="fieldreportage"] .action-bar {
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap: var(--s-2);
    /* The shared .action-bar caps itself at calc(100% - 360px - 3rem), which is
       right on the desks that carry a 360px sidebar beside the bar and wrong
       on the two reportage tools, which do not. MEASURED 2026-08-21 at 1280px:
       the panel is 557.3px and the bar was 143.3px, so its own buttons had
       been overflowing it silently at every desktop width. Not a new rule, a
       cap that never applied here. */
    max-width: 100%;
    padding-top:16px;
    padding-bottom:16px;
    border-top:1px solid var(--rule);
    flex-shrink:0;
  }
/* WRAPS, like ReporterAlly's row two hundred lines below, and for a reason
   the gate measured rather than a preference: FieldReportage's bar carries
   three actions since 2026-08-21 (Test the idea, Build Blueprint, Write the
   piece) and the row wanted 389px inside a 360px viewport. One vocabulary
   (protocol 13a): the same row on the sister tool already had this.
   SUPERSEDED above 609px on 2026-09-03 (Ritu: the three actions stay on one
   line); see ONE ROW below the button rules. The wrap holds only under 610. */
[data-tool="fieldreportage"] .action-left {display:flex;align-items:center;gap: var(--s-2);flex-wrap:wrap}

/* The twin-mode switch (Ritu, 2026-08-20). The shared .mode-switch stacks its
   two states in one column, which is right inside DeskCopyPro's 143px panel
   cell. Here it sits in a full-width row under the harvest, and both labels
   are one short word, so it takes two columns at every width this product
   renders at. MEASURED 2026-08-21 at 320px, the narrowest width the overflow
   gate runs: the switch takes 211.3px of a 272px row, each state 106.4px on
   one line, and the labels do not break. */
/* MODE OPENS THE COLUMN (Ritu, 2026-08-21: "I also don't like where mode is
   placed in FieldReportage"). It sat at the FOOT of the left column, directly
   above the three actions, where it read as a seventh step after the six
   prompts. It is not a step: it is the register the whole tool writes in, and
   the suite already has an answer — DeskCopyPro keeps its Mode in the config
   bar ABOVE the work. So it now opens the column: declare the kind of piece,
   say what you need, fill in what you saw, then act. */
[data-tool="fieldreportage"] .fr-mode {
  display: flex; align-items: center; gap: var(--s-3);
  margin: 0 0 var(--s-3);
  padding-bottom: var(--s-3);
  border-bottom: var(--hairline) solid var(--rule-soft);
}
/* The character count rides the MODE row, right-aligned. It is a quiet status,
   and the action row could not hold it: measured at 1512, three actions plus
   the count needed 668px in a 636px row, so the count wrapped onto a line of
   its own and made that block 71px tall. The mode row had the space already. */
[data-tool="fieldreportage"] .fr-mode .char-total { margin-left: auto; }
[data-tool="fieldreportage"] .fr-mode .mode-switch { grid-template-columns: 1fr 1fr; }
[data-tool="fieldreportage"] .fr-mode .mode-switch button + button { border-top: 0; border-left: 1px solid var(--accent); }

/* Way in rides the Mode row, on both reportage desks. Ritu's ruling of
   2026-09-03 on the rendered mockup (mockups/16-desk-row/option-1-beside-mode):
   there is room beside the switch, so the row keeps its height and nothing
   below it moves. MEASURED 2026-09-03 at 1440 and 1280: FieldReportage's row
   63px tall before and after, Build Blueprint's top edge unchanged. The
   select keeps its native arrow: background-color, never background. */
/* flex: 1 1 auto, added 2026-09-15 with the cap above. The group was sized to
   its content and sat in a 309.5px row taking 189.2 of it, so the select was
   squeezed to 125.6 whatever its own max-width said. It takes the row now and
   the label, which is nowrap, keeps what it needs. */
[data-tool="fieldreportage"] .fr-mode .desk-inline, [data-tool="reporterally"] .fr-mode .desk-inline { display: flex; align-items: center; gap: var(--s-2); min-width: 0; flex: 1 1 auto; }
[data-tool="fieldreportage"] .fr-mode .desk-inline .toggle-label, [data-tool="reporterally"] .fr-mode .desk-inline .toggle-label { margin: 0; white-space: nowrap; }
/* The look comes from the shared select rule, which .desk-inline select now
   joins. Only the width belongs here: the row is a line, not a cell, so the
   control takes its text's width and stops at 16ch. The bespoke look this
   replaced (2026-09-03) missed appearance:none and the chevron, so Way in
   rendered as plain underlined text with nothing to say it opens. That is the
   drift the shared rule's own comment warns about. */
[data-tool="fieldreportage"] .fr-mode .desk-inline select, [data-tool="reporterally"] .fr-mode .desk-inline select {
  /* THE CAP WAS 16ch AND EIGHT OF THE TEN OPTIONS ARE LONGER THAN THAT.
     Ritu, 2026-09-15, with a photograph of his phone: "If I choose Open with
     the pattern, 'n' gets visually sliced", then "Open against numbers is
     worse", then, after the words were shortened, "Even then, 'twist' gets cut
     a bit".

     MEASURED against the rendered box, in the select's own font, at 320, 358,
     412, 1024 and 1440: the box gave its text 101.6px on a phone and 120.7 on a
     desktop, and EIGHT of the ten options needed more at every one of those
     widths. Only "Headline news" and "Say it short" ever fitted. The longest
     needed 180 on a phone and 204.2 on a desktop. This was not a phone fault
     and it has been live since 2026-09-03.

     WHY NOTHING CAUGHT IT. The shared select rule declares overflow: hidden
     with text-overflow: ellipsis, and overflow_check reads that pair and files
     the element as "declared truncation, ellipsis shown". It never asked
     whether an ellipsis was actually drawn. Chrome renders this control with
     appearance: base-select, whose selected-value box does not take the
     ellipsis, so the text is cut mid-letter instead. A gate that trusts a
     declaration over the render is the failure this repo keeps paying for.

     A ch CAP WAS THE WRONG KIND OF NUMBER. 16ch is a count of characters in a
     font that may not be the font that loads, which is a trap already on record
     here from 2026-08-25. The control is a menu: its own intrinsic width IS its
     widest option, so width: auto asks for exactly what the content needs, and
     max-width: 100% stops it at the row. The desktop keeps a control sized to
     its text, which is what the row was designed for; the phone gets the same
     thing, bounded by a narrower row. */
  width: auto;
  max-width: 100%;
  /* The shared padding is 4px above and 8px below, built for a panel cell whose
     label sits on top. In a horizontal row it lifts the selected text 2.8px
     above MODE and WAY IN, which is the misalignment Ritu saw on 2026-09-04.
     The fix is the cell padding inverted, which is two exact scale steps and
     no magic number. MEASURED on the rendered pixels, not computed from the
     box: both strings run without descenders, so the baseline is the bottom
     of the ink. At 1440, 9x, the WAY IN label's baseline is row 373 and this
     one lands within 2 rows of it, a fifth of a CSS pixel. The cell's own
     4/8 leaves it 3.8px high, which is what Ritu saw. align-content: center
     was tried first and is worse, 7.8px high: the select's inner text box
     does not take that alignment. */
  padding-top: var(--s-2);
  padding-bottom: var(--s-1);
  /* Ritu's ruling of 2026-09-04, option three of three shown rendered: the rule
     is pulled up under the words. As a border it draws at the bottom of the
     44px tap target, 9.6px below the text and a few pixels above the row's own
     rule, so the row showed two parallel hairlines. As a background layer it
     sits where a rule under text belongs and the tap target is untouched. */
  border-bottom: 0;
  background-image: var(--select-chevron), linear-gradient(var(--rule-soft), var(--rule-soft));
  background-position: var(--select-chevron-position), 0 calc(50% + 16px);
  background-size: var(--select-chevron-size), 100% 1px;
}
/* ReporterAlly's row is FieldReportage's: one line, two-column switch. It has
   no character count to right-align; that count rides its panel header. */
[data-tool="reporterally"] .fr-mode { display: flex; align-items: center; gap: var(--s-3); margin: 0 0 var(--s-3); padding-bottom: var(--s-3); border-bottom: var(--hairline) solid var(--rule-soft); }
[data-tool="reporterally"] .fr-mode .mode-switch { grid-template-columns: 1fr 1fr; }
[data-tool="reporterally"] .fr-mode .mode-switch button + button { border-top: 0; border-left: 1px solid var(--accent); }
/* Below 520px the row wraps: Way in drops under the switch, no horizontal
   overflow. MEASURED on the built page at 390, 2026-09-03: row 111px, scrollWidth 390. */
@media (max-width: 520px) { [data-tool="fieldreportage"] .fr-mode, [data-tool="reporterally"] .fr-mode { flex-wrap: wrap; row-gap: var(--s-2); } }

/* The two quiet actions either side of Build Blueprint. Same treatment as
   ReporterAlly's secondary button, same class name, so a reader who knows one
   desk knows the other. */
[data-tool="fieldreportage"] .btn-submit {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    padding: var(--s-2) var(--s-3);
    background:transparent;
    color:var(--btn-ink);
    border:1.5px solid var(--btn-ink);
    border-radius:0;
    min-height: var(--touch-min);
    cursor:pointer;
    display:inline-flex; align-items:center; gap: var(--s-2);
    transition:background .15s ease,color .15s ease;
}
@media (hover: hover) {
[data-tool="fieldreportage"] .btn-submit:hover {background:var(--cta-fill);color:var(--cta-ink);--ico-color:currentColor;}
}
[data-tool="fieldreportage"] .btn-submit:active:not(:disabled) {transform:scale(var(--press-deep));transition:background .15s ease,color .15s ease,transform var(--press-in) var(--press-ease);}
[data-tool="fieldreportage"] .btn-submit:disabled {background:var(--paper-deep);color:var(--quiet);border-color:var(--rule);cursor:not-allowed}
[data-tool="fieldreportage"] .btn-go {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    padding: var(--s-2) 24px;
    /* The 44px control floor (Ritu, 2026-07-28). MEASURED 2026-08-21 at every
       width from 320 to 1920: this button rendered 31px tall and had done
       since it was written, which nothing caught while it was the only button
       in its row. It sits beside two 44px actions now, so the shortfall is
       both a floor breach and visible. */
    min-height: var(--touch-min);
    background:var(--cta-fill);
    color:var(--cta-ink);
    --ico-color:currentColor;
    border:none;
    border-radius:0;
    cursor:pointer;
    transition:background .15s ease,transform var(--press-out) var(--press-ease);
  }
/* Depth and curve from icons.css; the reasoning lives there. This is also
   the phone action bar's filled button (docs/superpowers/plans/2026-09-10-
   phone-places-stage-one.md Task 8 pins `.action-bar` to the foot on a
   phone): the element is the same button, only its position on screen
   moves, so nothing further is needed here for that case. */
[data-tool="fieldreportage"] .btn-notes:active:not(:disabled),
[data-tool="fieldreportage"] .btn-go:active:not(:disabled) {transform:scale(var(--press-deep));transition:background .15s ease,transform var(--press-in) var(--press-ease);}
@media (hover: hover) {
[data-tool="fieldreportage"] .btn-go:hover {background:var(--cta-fill);--ico-color:currentColor;}
}
[data-tool="fieldreportage"] .btn-go:disabled {background:var(--paper-deep);color:var(--quiet);cursor:not-allowed}
/* ONE ROW. Ritu, 2026-09-03: the three actions (Test the idea, Build
   Blueprint, Write the piece) never wrap to two lines. MEASURED 2026-09-03,
   scrollbars hidden: the row needs 594.3px at today's padding and the column
   is 556.5px at 1280, so the row wrapped below a 1364px viewport. The pills'
   side padding and the gap between them narrow with the viewport, from
   today's values at 1440 (the clamp resolves to 16px / 24px / 8px there, so
   the 1440 render is unchanged) down to a floor of 8px / 12px / 6px. Height
   and copy do not change. Below the floor the page goes single-column (see
   the breakpoint below) and the bar takes the whole frame. */
[data-tool="fieldreportage"] .action-left { flex-wrap: nowrap; gap: clamp(6px, calc(var(--s-2) - (1440px - 100vw) * 0.05), var(--s-2)); }
[data-tool="fieldreportage"] .action-left .btn-label { white-space: nowrap; }
/* Ritu, 2026-09-29: "Build Blueprint" has been tight from the beginning in the
   app and the extension, so there it reads "Blueprint"; the website keeps both
   words. The app is the phone layout (below 700px, the width app-home.js reads
   as the app; that rule lives in the one phone block below) or the installed
   app; the extension's panel sets html.panel. assets/js/btn-label.js puts the
   word back after a run. */
@media (display-mode: standalone) { [data-tool="fieldreportage"] .bp-build { display: none; } }
html.panel [data-tool="fieldreportage"] .bp-build { display: none; }
[data-tool="fieldreportage"] .btn-submit { padding-inline: clamp(8px, calc(var(--s-3) - (1440px - 100vw) * 0.1), var(--s-3)); }
[data-tool="fieldreportage"] .btn-go { padding-inline: clamp(12px, calc(24px - (1440px - 100vw) * 0.15), 24px); }
/* Below 610px even the whole frame is too narrow for the row (bisected
   2026-09-03: the bar is 534px at 610 and 533.1px at 609), so the row may
   stack there, as it did before at 390. */
@media (max-width: 609px) { [data-tool="fieldreportage"] .action-left { flex-wrap: wrap; } }
/* FieldReportage's Clear is byte-identical to ReporterAlly's and carries the
   identical 2.12:1 night defect. The gate reported only ReporterAlly because
   BOTH buttons are visibility:hidden until their input has text, and on the
   run that flagged it only ReporterAlly's box happened to hold any. Fixing the
   one that was reported and leaving its twin is the exact failure this suite
   has hit three times; see the .status-bar note above. Same swap, same reason. */
[data-tool="fieldreportage"] .btn-clear {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    padding: var(--s-2) var(--s-3);
    min-height:var(--touch-min);
    display:inline-flex;
    align-items:center;
    background:transparent;
    border:1.5px solid var(--btn-ink);
    color:var(--btn-ink);
    border-radius:0;
    cursor:pointer;
    transition:background .15s ease,color .15s ease, transform var(--press-out) var(--press-ease);
  }
@media (hover: hover) {
[data-tool="fieldreportage"] .btn-clear:hover {background:var(--btn-ink);color:var(--paper);--ico-color:currentColor;}
}
/* Carried no press before this (measured 2026-09-10). --press-light, same
   idiom as Copy beside it. */
[data-tool="fieldreportage"] .btn-clear:active:not(:disabled) {transform:scale(var(--press-light));transition:background .15s ease,color .15s ease, transform var(--press-in) var(--press-ease);}
[data-tool="fieldreportage"] .char-total {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:500;
    font-size: var(--t-1);
    letter-spacing: normal;
    color:var(--quiet);
  }
[data-tool="fieldreportage"] .panel-output {
    display:flex;
    flex-direction:column;
    min-width:0;
    border-left:1px solid var(--rule);
    padding-left: var(--s-4);
  }
[data-tool="fieldreportage"] .panel-header {
    display:flex;
    align-items:baseline;
    justify-content:space-between;
    padding-bottom: var(--s-3);
    margin-bottom: var(--s-3);
    border-bottom:1px solid var(--rule);
  }
[data-tool="fieldreportage"] .panel-header h2 {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    color:var(--ink-soft);
  }
[data-tool="fieldreportage"] .btn-copy {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    padding: var(--s-1) var(--s-2);
    background:transparent;
    border:1px solid var(--rule);
    color:var(--ink-soft);
    border-radius:0;
    cursor:pointer;
    transition:border-color .12s ease,color .12s ease;
  }
@media (hover: hover) {
[data-tool="fieldreportage"] .btn-copy:hover {color:var(--accent-deep);border-color:var(--accent-deep)}
}
[data-tool="fieldreportage"] .btn-copy.copied {color:var(--good);border-color:var(--good)}
[data-tool="fieldreportage"] .output-scroll {
    flex:1;
    overflow-y:auto;
    padding-right:4px;
}
[data-tool="fieldreportage"] .output-scroll::-webkit-scrollbar {width:6px}
[data-tool="fieldreportage"] .output-scroll::-webkit-scrollbar-thumb {background:var(--rule)}
@media (hover: hover) {
[data-tool="fieldreportage"] .output-scroll::-webkit-scrollbar-thumb:hover {background:var(--quiet)}
}
[data-tool="fieldreportage"] .output-empty {
    text-align:center;
    padding: var(--s-5) var(--s-4);
    color:var(--ink-soft);
}
[data-tool="fieldreportage"] .empty-icon {
    font-family:var(--font-display);
    font-weight:400;
    font-size: var(--t-7);
    color:var(--rule);
    margin-bottom: var(--s-3);
    line-height: calc(var(--s-1) * 10);
  }
[data-tool="fieldreportage"] .output-empty h3 {
    font-family:var(--font-display);
    font-weight:400;
    font-style:italic;
    font-size: var(--t-6);
    line-height: calc(var(--s-1) * 8);
    letter-spacing: var(--track-tight);
    color:var(--ink);
    margin-bottom: var(--s-2);
  }
[data-tool="fieldreportage"] .output-empty p {
    font-family:var(--font-body);
    font-size:14px;
    line-height: calc(var(--s-1) * 6);
    color:var(--ink-soft);
    max-width:42ch;
    margin:0 auto;
    /* The house answer to a stranded last word, already used on the front door
       and on the Editorial tool cards after he caught DCP's card ending in a
       49px stub. MEASURED here at 390px: three lines with the last at 12% of
       the longest, which is one word alone. */
    text-wrap: balance;
  }
[data-tool="fieldreportage"] #outputThread {padding-bottom: var(--s-3)}
/* THE OUTPUT PANEL'S TYPOGRAPHY, POINTED AT THE MARKUP THE PAGE ACTUALLY
   BUILDS (Ritu, 2026-08-21, on seeing a real blueprint: "an alchemy of chaos.
   Text, font, spacing, headline. Everything is random and does not adhere to
   the style and design of what the other tools").
   ROOT CAUSE, and it is one line long: every rule below used to be written for
   .thread-entry.ally .body and .thread-entry.user .body. The page has never
   created either class. It builds .thread-label + .thread-user-text on the
   request card and .output-content on the answer, so the ENTIRE output
   stylesheet was dead and the browser's defaults were what he saw: a 32px h1,
   loose paragraphs and no spacing system at all.
   The sizes now come from the scale rather than from hand-set pixels, and the
   body matches .rewrite-body, which is what the rewrite desks render into. */
[data-tool="fieldreportage"] .thread-entry {margin-bottom:24px;padding-bottom:24px;border-bottom:1px solid var(--rule)}
[data-tool="fieldreportage"] .thread-entry:last-child {border-bottom:none}
[data-tool="fieldreportage"] .thread-entry .thread-label {
    display:flex;
    align-items:center;
    gap: var(--s-2);
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    color:var(--ink-soft);
    margin-bottom:8px;
  }
[data-tool="fieldreportage"] .thread-entry .reporter-label {color:var(--quiet)}
[data-tool="fieldreportage"] .thread-entry .thread-user-text {
    background:var(--paper-deep);
    padding: var(--s-3) var(--s-3);
    font-family:var(--font-body);
    font-style:italic;
    font-size: var(--t-2);
    line-height: calc(var(--s-1) * 6);
    color:var(--ink);
  }
[data-tool="fieldreportage"] .thread-entry .output-content {
    font-family:var(--font-body);
    font-size: var(--t-4);
    line-height: calc(var(--s-1) * 8);
    color:var(--ink);
  }
[data-tool="fieldreportage"] .thread-entry .output-content h1, [data-tool="fieldreportage"] .thread-entry .output-content h2, [data-tool="fieldreportage"] .thread-entry .output-content h3 {
    font-family:var(--font-display);
    font-weight:700;
    color:var(--ink);
    margin: var(--s-3) 0 var(--s-2);
    letter-spacing:-.005em;
  }
[data-tool="fieldreportage"] .thread-entry .output-content h1 {font-size: var(--t-5)}
[data-tool="fieldreportage"] .thread-entry .output-content h2 {font-size: var(--t-4)}
[data-tool="fieldreportage"] .thread-entry .output-content h3 {font-size: var(--t-3);text-transform:uppercase;letter-spacing: var(--track-caps);font-family: var(--font-display); font-size-adjust: var(--x-adjust);font-weight:700;color:var(--ink-soft)}
[data-tool="fieldreportage"] .thread-entry .output-content p {margin-bottom: var(--s-3)}
[data-tool="fieldreportage"] .thread-entry .output-content strong {font-weight:700;color:var(--ink)}
[data-tool="fieldreportage"] .thread-entry .output-content em {font-style:italic}
[data-tool="fieldreportage"] .thread-entry .output-content blockquote {
    /* NO LEFT RAIL. This project retired the 3px bar by name
       (DESIGN-SYSTEM.md:89), and FieldReportage's copy of this rule still had
       one. It had never actually rendered: the whole output stylesheet was
       pointed at class names the page did not build until 2026-08-21. A quote
       is set apart by its indent, its ground and its italic. Both tools share
       one treatment, because they share one renderer. */
    padding: var(--s-2) var(--s-3);
    margin: var(--s-3) 0 var(--s-3) var(--s-3);
    background:var(--paper-deep);
    font-style:italic;
    color:var(--ink);
  }
[data-tool="fieldreportage"] .thread-entry .output-content ul, [data-tool="fieldreportage"] .thread-entry .output-content ol {margin: var(--s-2) 0 var(--s-3) var(--s-3)}
[data-tool="fieldreportage"] .thread-entry .output-content li {margin-bottom:4px}
[data-tool="fieldreportage"] .thread-entry .output-content code {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-size: var(--t-1);
    background:var(--paper-deep);
    padding: 1px var(--s-1);
    border:1px solid var(--rule);
  }
[data-tool="fieldreportage"] .thread-entry .output-content hr {
    border:none;
    border-top:1px solid var(--rule);
    margin: var(--s-3) 0;
  }
[data-tool="fieldreportage"] .thread-entry .entry-actions {margin-top: var(--s-2);display:flex;gap:8px}
/* THE PER-ANSWER COPY BUTTON AND THE FOURTH HEADING (Ritu, 2026-08-21: "Look at
   the copy button next to the blueprint sticking to the text"). The page appends
   it INTO the label row, where it had no styling of its own at all and rendered
   as a browser default button hard against the words. It takes the house
   outline pill, at the label's own size, and the row's gap now keeps it off the
   text. The renderer emits h4 as well, so the scale covers it. */
[data-tool="fieldreportage"] .btn-copy-single,
[data-tool="reporterally"] .btn-copy-single {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--btn-ink); background: transparent;
  border: var(--hairline) solid var(--rule); border-radius: var(--pill);
  padding: 2px var(--s-2); margin-left: auto; cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}
@media (hover: hover) {
[data-tool="fieldreportage"] .btn-copy-single:hover,
[data-tool="reporterally"] .btn-copy-single:hover { border-color: var(--accent-deep); color: var(--accent-deep); }
}
[data-tool="fieldreportage"] .btn-copy-single.copied,
[data-tool="reporterally"] .btn-copy-single.copied { color: var(--accent-deep); border-color: var(--accent-deep); }
[data-tool="fieldreportage"] .thread-entry .output-content h4,
[data-tool="reporterally"] .thread-entry .output-content h4 {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-weight: 700; font-size: var(--t-2); text-transform: uppercase;
  letter-spacing: var(--track-caps); color: var(--ink-soft);
  margin: var(--s-3) 0 var(--s-2);
}
/* THE RIGHT COLUMN IS A TRANSCRIPT, AND A TRANSCRIPT SCROLLS INSIDE ITS FRAME
   (Ritu, 2026-08-21: "it should be scrollable. Otherwise, compared to the left
   panel, this looks like a lizard's tongue hanging out"). The left column is a
   fixed instrument panel; the right one accumulated every turn and dragged the
   page down with it.
   The caps are MEASURED, not guessed: at 1512x900 the scroll starts at 317px on
   FieldReportage and 378px on ReporterAlly, so these land its bottom just above
   the fold at 860, 900 and 950 with room for the hero to wrap. Below 900px wide
   the columns stack and an inner scroller becomes a trap, so the cap lifts. */
/* THE CAP COUNTS THE BUTTONS, NOT JUST THE STORY.
   FieldReportage was on 60vh and ReporterAlly on 53vh, both set so the
   SCROLLER's bottom cleared the fold. The actions row hangs 77px below the
   scroller (61px of buttons plus a 16px margin) and was never in the sum, so
   at 1440x900 FieldReportage put Follow up, Copy all and Export at 954 against
   a fold of 900: 54px below it, before this notes box existed. That is the
   thing Ritu named when he ordered the box, so it is fixed here rather than
   inherited. One number for the pair now, and the two tools measure the same.
   scripts/notes_box_fit_check.mjs holds it at eleven widths and three heights. */
/* THE CAP IS CULLED, 2026-09-22, AND THE PIN IS WHAT REPLACES IT.
   Ritu: "There is a scroller only for the right side of the page where the
   output will land, which I find strange." He is right, and it was strange:
   MEASURED on FieldReportage at 1280x700 with a story in the pane, #outputScroll
   was clamped to 292px against 2944px of story and scrolled inside a page that
   was also scrolling. Two scrollbars on one screen, and the inner one shrank as
   the window did, because the cap was a percentage of the window while the
   chrome above it was a fixed number of pixels.

   WHAT THE CAP WAS FOR, and it is his own condition of 2026-09-06: the notes
   box "shouldn't bring us back to square one, where you again have to scroll
   down to find the buttons below." The cap met that by predicting where the
   fold would be. The action row is now pinned to the foot of the window on
   every desk (see the desktop frame block at the end of this file), so the
   buttons cannot fall below the fold at any height and nothing needs
   predicting. His condition is kept by a mechanism instead of an estimate.

   scripts/notes_box_fit_check.mjs no longer asks whether the cap cleared the
   fold. It asks whether the action row is inside the window, which is the thing
   he actually wanted, and it now runs the short laptop heights that let this
   fault through: it tested 800, 900 and 1000 only. */

/* THE NOTES BOX TAKES ITS ROOM FROM THE STORY, NOT FROM THE PAGE.
   Ritu's caveat when he ordered this box, 2026-09-06: it "shouldn't bring us
   back to square one, where you again have to scroll down to find the buttons
   below."

   So the panel is the same height with the box open as with it closed. ONE
   number does it, and it cannot drift: --notes-h is both the height the box is
   given and the height the scroller gives up. Change it in one place and the
   two stay equal by construction. There is no second measurement to go stale,
   which is the trap the caps above already warn about.

   The textarea, not the box, absorbs the slack, so the box can never clip its
   own button: the Send notes row keeps its natural height and the typing area
   takes what is left. Below 900px the columns stack and the cap lifts anyway,
   so the subtraction is inert there and the box simply flows. */
/* 206px, and every pixel of it is accounted for. Measured on the rendered box
   at 1512x840: 16 padding + 28 the microphone row + 96 the typing area
   + 16 + 50 the button row. At 172px the typing area was 62px against a
   32px line-height, so the hint clipped through the middle of its second line.
   96px is two whole lines; more scrolls, which is what a notes box should do. */
.panel-output.notes-open { --notes-h: 206px; }

.notes-box {
  height: var(--notes-h, 172px);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  /* MEASURED, 2026-09-06: the shared .feedback-disclosure-body brings a 16px
     margin-top, and a margin sits OUTSIDE the height the scroller gave up, so
     the panel grew by exactly 16px (694 closed, 710 open) and the gate caught
     it. Padding is inside the border-box height; margin is not. */
  margin-top: 0;
  padding-top: var(--s-3);
}
/* padding, not margin: a margin would sit OUTSIDE the height the scroller
   gave up, and the panel would grow by exactly that much. */
.notes-box .feedback-textarea {
  flex: 1 1 auto;
  min-height: 0;
  resize: none;
}
/* SEND NOTES IS THE SAME KIND OF CONTROL AS THE ROW ABOVE IT, SO IT LOOKS THE
   SAME. It takes .output-actions and inherits that row's pill, its 44px floor
   and its type, rather than carrying a second treatment for one button. Only
   the row's own separators come off: the hairline and the wide top margin
   belong between the story and its buttons, not between two rows of buttons
   six pixels apart. Measured first as .revise-btn, the shared underlined text
   button: at 28px it dwarfed the three pills above it and stole 50px from the
   typing area. */
.notes-box .notes-actions {
  flex: 0 0 auto;
  margin-top: var(--s-2);
  padding-top: 0;
  border-top: 0;
}
@media (max-width: 900px) {
  /* Stacked, nothing is competing for the fold, so the box can breathe and
     the reporter can drag it taller. */
  .notes-box { height: auto; }
  .notes-box .feedback-textarea { resize: vertical; min-height: 5rem; }
}

/* (3) THE ANSWER'S HEADINGS STOP SHOUTING. His words: "Is there a story here?,
   The lead etc needn't be so big and in upper case." Each level drops one step
   on the scale, and the uppercase this sheet was ADDING at h3 is gone: the caps
   that remain are the engine's own words, which is a prompt matter, not a page
   one. Weight and the display face carry the hierarchy instead of size. */
[data-tool="fieldreportage"] .thread-entry .output-content h1,
[data-tool="reporterally"] .thread-entry .output-content h1 { font-size: var(--t-4); }
[data-tool="fieldreportage"] .thread-entry .output-content h2,
[data-tool="reporterally"] .thread-entry .output-content h2 { font-size: var(--t-3); }
[data-tool="fieldreportage"] .thread-entry .output-content h3,
[data-tool="reporterally"] .thread-entry .output-content h3 {
  font-size: var(--t-2); text-transform: none; letter-spacing: var(--track-tight); color: var(--ink);
}
[data-tool="fieldreportage"] .thread-entry .output-content h4,
[data-tool="reporterally"] .thread-entry .output-content h4 {
  font-size: var(--t-2); text-transform: none; letter-spacing: var(--track-tight);
}
[data-tool="fieldreportage"] .thread-entry .stream-dot {
    display:inline-block;
    width:6px;height:6px;
    background:var(--mark);
    border-radius:50%;
    margin-left:3px;
    vertical-align:middle;
    animation:stream-dot 1.2s ease-in-out infinite;
  }
@keyframes stream-dot{0%,100%{opacity:.3}50%{opacity:1}}
[data-tool="fieldreportage"] .output-actions {
    display:flex;
    gap: var(--s-2);
    padding-top:16px;
    border-top:1px solid var(--rule);
    margin-top: var(--s-3);
    flex-shrink:0;
  }
[data-tool="fieldreportage"] .btn-followup, [data-tool="fieldreportage"] .btn-notes, [data-tool="fieldreportage"] .output-actions .btn-copy {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    padding: 8px var(--s-2);
    background:transparent;
    border:1px solid var(--rule);
    color:var(--ink-soft);
    border-radius:0;
    cursor:pointer;
    transition:border-color .12s ease,color .12s ease;
  }
@media (hover: hover) {
[data-tool="fieldreportage"] .btn-followup:hover, [data-tool="fieldreportage"] .btn-notes:hover, [data-tool="fieldreportage"] .output-actions .btn-copy:hover {color:var(--accent-deep);border-color:var(--accent-deep)}
}
[data-tool="fieldreportage"] .settings-backdrop {display:none;position:fixed;inset:0;background:rgba(10,10,10,.94);z-index:998}
[data-tool="fieldreportage"] .settings-backdrop.visible {display:block}
[data-tool="fieldreportage"] .settings-panel {display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:min(92vw,860px);max-height:85vh;overflow-y:auto;z-index:999;background:var(--paper);border:2px solid var(--accent-deep);border-radius:0;padding: var(--s-4) var(--s-4);box-shadow:0 24px 64px rgba(10,10,10,.18)}
[data-tool="fieldreportage"] .settings-panel.visible {display:block}
[data-tool="fieldreportage"] .settings-panel h2 {font-family:var(--font-display);font-weight:700;font-size: var(--t-5);letter-spacing: var(--track-tight);color:var(--ink);margin-bottom: var(--s-1)}
/* The two reportage tools used to restate .settings-subtitle here, each with
   its own font-size, leading and colour, and ReporterAlly's set margin-bottom
   to ZERO. Ritu saw the result on the live site, 2026-09-06: "Here too, tight.
   Sentence below STYLESHEET SETTINGS bunched together" — the sentence ran
   straight into the PRESET label with nothing between them. Neither override
   carried a reason, and between them they gave one element three different
   treatments across eight tools. Both are deleted; the shared rule at
   .settings-subtitle governs every drawer, which is his uniformity principle
   applied to the thing he was actually looking at. */
[data-tool="fieldreportage"] .settings-grid {display:grid;grid-template-columns:1fr 1fr;gap: var(--s-3) 24px}
[data-tool="fieldreportage"] .setting-group {display:flex;flex-direction:column;gap: var(--s-1);min-width:0}
/* THE NOTE UNDER A DIAL, 2026-09-15. Ritu's choice of three for the six menus
   whose options were whole sentences: the name goes in the menu and the
   explanation goes here, where it has the width to wrap and be read. It reads
   the way the Scroll Desk's voice note has read since 2026-08-15, which is the
   point: the same idea should not look like two ideas.

   No max-width. The Scroll Desk's note caps at 22rem because it sits in a
   narrow rail beside a wide page; this one sits in a drawer that is already the
   column, and a cap here would only make it wrap sooner than it must. */
.setting-note {
  display: block;
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-2);
  line-height: 1.45;
  color: var(--quiet);
}
.setting-note:empty { display: none; }

[data-tool="fieldreportage"] .setting-group label {font-family: var(--font-display); font-size-adjust: var(--x-adjust);font-weight:700;font-size: var(--t-1);text-transform:uppercase;letter-spacing: var(--track-caps);color:var(--ink-soft)}
/* THE 16px FLOOR ON ANYTHING A FINGER TAPS INTO. Measured 2026-09-20 at 390
   in headless Chrome: this desk rendered 19 controls below 16px (11 selects and
   one text input at 12px, six note boxes at 14px, the drawer's rename box at
   13px). Mobile Safari zooms the whole page when a focused text control is
   under 16px, and the reporter then types into a page scrolled sideways.
   The sizes here are not a new scale. `.setting-group select` already sets
   var(--t-3) for every other desk and `textarea.raw-input` sets var(--t-4);
   these rules were the outliers, so they join the scale the product already
   keeps at every width. */
[data-tool="fieldreportage"] .setting-group select, [data-tool="fieldreportage"] .setting-group input[type="text"] {width:100%;padding: var(--s-2) var(--s-2);font-family: var(--font-display); font-size-adjust: var(--x-adjust);font-size: var(--t-3);color:var(--ink);background:var(--paper);border:1px solid var(--accent-deep);border-radius:0}
[data-tool="fieldreportage"] .setting-group select:focus, [data-tool="fieldreportage"] .setting-group input[type="text"]:focus {outline:none;box-shadow:0 0 0 2px var(--accent)}
[data-tool="fieldreportage"] .setting-group input[type="text"]::placeholder {color:var(--quiet);font-family:var(--font-body);font-style:italic}
[data-tool="fieldreportage"] .settings-footer {display:flex;align-items:center;gap: var(--s-2);margin-top:24px;padding-top: var(--s-3);border-top:1px solid var(--rule);max-width:none}
[data-tool="fieldreportage"] .btn-save-settings {font-family: var(--font-display); font-size-adjust: var(--x-adjust);font-weight:700;font-size: var(--t-1);letter-spacing: var(--track-caps);text-transform:uppercase;padding: var(--s-2) var(--s-3);color:var(--paper);background:var(--accent-deep);--ico-color:currentColor;border:none;border-radius:0;cursor:pointer;transition:background .15s ease}
@media (hover: hover) {
[data-tool="fieldreportage"] .btn-save-settings:hover {background:var(--accent-deep);--ico-color:currentColor;}
}
[data-tool="fieldreportage"] .btn-reset-settings {font-family: var(--font-display); font-size-adjust: var(--x-adjust);font-weight:700;font-size: var(--t-1);letter-spacing: var(--track-caps);text-transform:uppercase;padding: var(--s-2) var(--s-3);color:var(--accent-deep);background:transparent;border:1.5px solid var(--accent-deep);border-radius:0;cursor:pointer;transition:background .15s ease,color .15s ease}
@media (hover: hover) {
[data-tool="fieldreportage"] .btn-reset-settings:hover {background:var(--accent-deep);color:var(--paper);--ico-color:currentColor;}
}
[data-tool="fieldreportage"] .settings-saved-msg {font-family:var(--font-mono);font-weight:700;font-size: var(--t-1);letter-spacing: var(--track-caps);text-transform:uppercase;color:var(--good);display:none}
/* CULLED 2026-08-07: this tool's private copy of the in-flow status bar, and
   the spinner drawn for it. Both restated the shared bar at a higher
   specificity, so FieldReportage would have kept the invisible --paper-deep
   strip after the shared one moved to the foot of the window. The shared
   .status-bar rule is the only one now, on every tool.
   Guard: tests/loading_uniformity.test.js, no per-tool status-bar override. */
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes settle{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
@media (prefers-reduced-motion: reduce) {
[data-tool="fieldreportage"] .header, [data-tool="fieldreportage"] .footer {animation:none}
[data-tool="fieldreportage"] * {transition:none !important}
}
/* SINGLE COLUMN FROM 1228px DOWN, raised from 960px on 2026-09-03. Ritu's
   ruling: the three actions stay on one line. With the pills at their padding
   floor the row needs 533.5px and the two-column input panel is 533.5px wide
   at a 1229px viewport and 533.1px at 1228px (bisected, scrollbars hidden,
   2026-09-03). Below that the page stacks and the bar takes the whole frame. */
@media (max-width:1228px) {
[data-tool="fieldreportage"] .header {padding: 24px 24px var(--s-3);grid-template-columns:1fr;row-gap: var(--s-3);align-items:start}
[data-tool="fieldreportage"] .header-right {justify-content:flex-start;padding-bottom:0}
/* h-padding stays 0: --shell already carries the gutter below its cap, and the
   old fixed 24px put the column 4px off the masthead on a phone. */
[data-tool="fieldreportage"] .main {grid-template-columns:1fr;padding: 24px 0 var(--s-4);gap:24px}
[data-tool="fieldreportage"] .panel-output {border-left:none;padding-left:0;border-top:1px solid var(--rule);padding-top:24px}
[data-tool="fieldreportage"] .footer {padding: var(--s-3) 24px}
}
@media (max-width:520px) {
[data-tool="fieldreportage"] .header h1 {font-size:36px}
[data-tool="fieldreportage"] .output-empty h3 {font-size: var(--t-5)}
}
[data-tool="reporterally"] *::before, [data-tool="reporterally"] *::after { box-sizing: border-box; margin: 0; padding: 0; }
[data-tool="reporterally"] .btn-copy-single {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);font-weight:700;font-size: var(--t-1);letter-spacing: var(--track-caps);text-transform:uppercase;
    padding: var(--s-2) var(--s-2);background:transparent;color:var(--ink-soft);border:1px solid var(--rule);border-radius:0;
    cursor:pointer;transition:border-color .15s ease,color .15s ease;
  }
@media (hover: hover) {
[data-tool="reporterally"] .btn-copy-single:hover {color:var(--ink);border-color:var(--accent-deep)}
}
[data-tool="reporterally"] .btn-copy-single.copied {color:var(--good);border-color:var(--good)}
/* On the column with the hero and .main above and below it (see the note on
   [data-tool="fieldreportage"] .main). The bar's rule has to start and end
   where the page's content does, or the step is visible directly under the
   hero. */
[data-tool="reporterally"] .mode-bar {
    width:var(--frame);
    max-width:none;
    margin:0 auto;
    padding:0;
    display:flex;
    border-bottom: var(--hairline) solid var(--rule-soft);
    gap:0;
    flex-shrink:0;
  }
[data-tool="reporterally"] .mode-tab {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    padding: var(--s-3) 24px;
    background:transparent;
    border:none;
    color:var(--ink-soft);
    cursor:pointer;
    border-bottom:2px solid transparent;
    margin-bottom:-1px;
    transition:color .15s ease,border-color .15s ease;
    display:inline-flex;
    align-items:center;
    gap:8px;
  }
@media (hover: hover) {
[data-tool="reporterally"] .mode-tab:hover {color:var(--ink)}
}
[data-tool="reporterally"] .mode-tab.active-ideate, [data-tool="reporterally"] .mode-tab.active-build, [data-tool="reporterally"] .mode-tab.active-elevate, [data-tool="reporterally"] .mode-tab.active-fieldreportage {
    color:var(--ink);
    border-bottom-color:var(--accent-deep);
  }
[data-tool="reporterally"] .mode-num {
    font-family:var(--font-display);
    font-weight:700;
    font-size:14px;
    letter-spacing: var(--track-tight);
    color:var(--ink-soft);
    line-height: calc(var(--s-1) * 4);
  }
/* Column: --shell, not a fixed cap (see the note on
   [data-tool="fieldreportage"] .main for the measurements). */
[data-tool="reporterally"] .main {
    flex:1;
    width:var(--frame);
    max-width:none;
    margin:0 auto;
    /* The opening moved UP to .ra-hero, which sits above this block. Leaving it
       here as well would open the page twice. */
    padding: var(--s-4) 0 var(--s-4);
    display:grid;
    grid-template-columns:1fr 1fr;
    gap: var(--s-4);
    min-height:0;
  }
/* Hero header shares the SAME container box as .main above so its left edge
   tracks the workspace at every breakpoint. Was a one-off inline wrapper that
   duplicated the desktop geometry but missed .main's mobile step-down, leaving
   the hero inset (looked centred) on phones. Both now sit on --shell, which is
   also what the masthead derives its inline padding from, so hero, workspace
   and wordmark share one left edge at every width. Keep these in lockstep. */
/* EQUIDISTANT OPENING. Ritu, 2026-09-06: "Maintain equidistance between the
   masthead space and the hero across all tools", and "ReporterAlly looks a tad
   tight". Measured at 1440 before this: six tools opened at 63px, FieldReportage
   at 24px and ReporterAlly at 16px. Three values for one gap, and he was looking
   at the tightest.

   63px is not a number typed here: it is what .main's own opening,
   clamp(2rem, 5vw, 3.5rem), resolves to at 1440 on this product's 18px root.
   The two reportage tools never had it because their hero sits OUTSIDE .main,
   in the grid, so the opening padding never reached it. They take the same
   expression now rather than a copy of its output, so all eight stay equal at
   every width, not just at 1440. */
[data-tool="reporterally"] .ra-hero {
    width:var(--frame);
    max-width:none;
    margin:0 auto;
    padding: clamp(2rem, 5vw, 3.5rem) 0 0;
  }
/* THE HERO GIVES UP HEIGHT SO THE BUTTON IS ON SCREEN. Ritu, 2026-09-05:
   "Making it two lines is eating into space in such a way that the user doesn't
   see the execute button at the bottom without scrolling down."
   Measured on prod at the time: Refine sat 5px below the fold at 1440x900 and
   91px below at 1280x800. He kept his own wording and ruled that the block
   shrink instead, over moving the "Anything the desk should know?" box into
   Settings (that box is a per-story note, not a setting) and over leaving it.

   h1.display is shared, and its own comment protects "every tool's original
   line count", so this is scoped to this one tool and FieldReportage's one-line
   hero is untouched. Capping the clamp at --t-8 rather than retuning the vw
   coefficient keeps the hero ON the type scale: 48px is a real step, added to
   the scale on 2026-08-12 for exactly this gap.

   Measured after: 62.7px to 48px, the hero box 156px to 66px, and his line now
   sets on ONE line at 1512, 1440 and 1280. Refine clears the fold by 92px at
   1440. At 1280 it clears too. Below 1024 the clamp's 4.4vw takes over again
   and the line wraps as "Where your story goes / before the editor's desk",
   which is where text-wrap: balance already put it. */
/* The font-size override that used to sit here is GONE. It capped this one
   tool at --t-8 while the other six ran to --t-9, which is the exception Ritu
   caught on 2026-09-06 ("UNIFORMITY PRINCIPLE"). The cap is now on the shared
   h1.display rule, so this tool gets the same 48px by being a tool, not by
   being special. Only the tightened bottom margin is this tool's own, and that
   is spacing, not size: it is half of how the hero block gave up the height
   that put Refine on screen. */
[data-tool="reporterally"] .ra-hero h1.display {
    margin-bottom: var(--s-3);
  }
[data-tool="reporterally"] .panel-input {display:flex;flex-direction:column;min-width:0}
[data-tool="reporterally"] .mode-desc {
    font-family:var(--font-body);
    font-style:italic;
    font-size: var(--t-2);
    line-height: calc(var(--s-1) * 6);
    color:var(--ink-soft);
    margin-bottom: var(--s-3);
    max-width:62ch;
  }
[data-tool="reporterally"] .context-line {margin-bottom: var(--s-3)}
[data-tool="reporterally"] .context-line textarea {
    width:100%;
    padding: var(--s-2) var(--s-2);
    font-family:var(--font-body);
    font-style:italic;
    /* THE HOUSE BOX, 2026-08-21. Measured across all three suites: every box a
       person types or pastes into runs 19px on 28px — the four rewrite desks,
       DeskTriage, IntroDoctor, Headliner and all nine Corporate boxes. These
       two ran 14 on 20 and were the only exception. His ruling: "The first box,
       which says 'What do you need?' can be like the rest." */
    font-size: var(--t-4);
    line-height: calc(var(--s-1) * 7);
    color:var(--ink);
    background:var(--paper);
    border:1px solid var(--accent-deep);
    border-radius: var(--box);
    outline:none;
    resize:none;               /* no drag-resize; the rows attribute sets the height */
    display:block;
  }
[data-tool="reporterally"] .context-line textarea:focus {box-shadow:0 0 0 2px var(--accent)}
[data-tool="reporterally"] .context-line textarea::placeholder {color:var(--quiet);font-style:italic}
/* FACT CHECK SITS WITH THE HEADING IT BELONGS TO (Ritu, 2026-08-21): "The
   button/pill should ideally be to the right of REFINED DRAFT and FIELD
   INTELLIGENCE." Only the OUTPUT panel's header takes this; the input side
   keeps space-between so its character count stays hard right. Copy is pushed
   right with margin-left:auto rather than by space-between, because Copy is
   display:none until there is output and space-between would fling Fact check
   to the far edge whenever it was hidden. */
[data-tool="fieldreportage"] .panel-output .panel-header,
[data-tool="reporterally"] .panel-output .panel-header {
  align-items: center;
  justify-content: flex-start;
  gap: var(--s-3);
}
/* COPY AND CLEAR ARE THE PANEL'S OWN PAIR, at the right of its header (his
   ruling 2026-08-21: "If copy is on top of the right panel, clear should also
   be next to it"). Clear used to sit inside the left column's action row, where
   it did not fit and wrapped onto a line of its own beneath the first button.
   It is not a make-something control; it undoes everything, and it belongs with
   the other panel-level actions, visible the moment a reporter decides to start
   again. Only the FIRST of the pair takes the auto margin, or the two would be
   pushed apart to the ends of the row. */
[data-tool="fieldreportage"] .panel-output .panel-header .btn-copy,
[data-tool="reporterally"] .panel-output .panel-header .btn-copy { margin-left: auto; }
[data-tool="fieldreportage"] .panel-output .panel-header .btn-clear,
[data-tool="reporterally"] .panel-output .panel-header .btn-clear { margin-right: var(--s-1); }

/* The bar the button left behind holds only the live status, which the shared
   client writes and clears (fidelity-assist.js). Empty it takes no space at
   all; filled it gets its own air. The resting sentence that used to live here
   is on the button's title now, so it shows on hover and never wraps. */
[data-tool="fieldreportage"] .fidelity-bar,
[data-tool="reporterally"] .fidelity-bar { margin: 0; }
[data-tool="fieldreportage"] .fidelity-status:not(:empty),
[data-tool="reporterally"] .fidelity-status:not(:empty) { display: block; margin: var(--s-3) 0; }
[data-tool="reporterally"] .panel-header {
    display:flex;
    align-items:baseline;
    justify-content:space-between;
    padding-bottom: var(--s-2);
    margin-bottom: var(--s-3);
    border-bottom: var(--hairline) solid var(--rule-soft);
  }
[data-tool="reporterally"] .panel-header h2 {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    color:var(--ink-soft);
  }
[data-tool="reporterally"] .char-count {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:500;
    font-size: var(--t-1);
    letter-spacing: normal;
    color:var(--quiet);
  }
[data-tool="reporterally"] .input-area {flex:1;display:flex;flex-direction:column;margin-bottom: var(--s-3)}
[data-tool="reporterally"] .input-area textarea {
    flex:1;
    width:100%;
    min-height:240px;
    background:var(--paper);
    border:2px solid var(--accent-deep);
    padding: var(--s-3) var(--s-3);
    font-family:var(--font-body);
    /* #inputCopy here does the same job as #inputCopy on the rewrite desks, and
       those run var(--t-4). 16px was this tool's own number. */
    font-size: var(--t-4);
    line-height: calc(var(--s-1) * 7);
    color:var(--ink);
    resize:vertical;
    outline:none;
    border-radius: var(--box);
  }
/* The left-over material is optional, and a box the same size as the story
   box says the opposite. It takes a third of the height and does not grow with
   the panel; the story box keeps the flex. MEASURED 2026-08-21 at 1280px: 240px
   for the story, 96px here, which holds four lines of the 16px body face. */
[data-tool="reporterally"] .input-area textarea:focus {border-color:var(--accent-deep);box-shadow:0 0 0 2px var(--accent)}
[data-tool="reporterally"] .input-area textarea::placeholder {color:var(--quiet);font-style:italic}
/* THE ACTION BAR'S AIR, ON THE REPORTAGE PAIR ONLY (Ritu, 2026-08-21: the
   masthead and the whole block below must be visible without scrolling). The
   shared .action-bar takes margin-top 40px; here the mode row above already
   carries 24px of its own, so 40 more is a gap, not a separation. FieldReportage
   also halves its own 16px padding, which it sets twice over the shared rule. */
[data-tool="fieldreportage"] .action-bar, [data-tool="reporterally"] .action-bar { margin-top: var(--s-4); }
[data-tool="fieldreportage"] .action-bar { padding-top: var(--s-2); padding-bottom: var(--s-2); }

/* The story box lost the room the left-over box used to take beneath it, and
   the left-over box now sits closed in one header. 190px still shows five lines
   of the 16px body face, and the box both grows with the panel and resizes. */
[data-tool="reporterally"] .input-area textarea { min-height: 190px; }
[data-tool="reporterally"] .action-bar {
    display:flex;
    align-items:center;
    gap: var(--s-2);
    /* Same cap, same reason as FieldReportage's bar above: this desk has no
       360px sidebar either. */
    max-width: 100%;
    padding-top: var(--s-3);
    border-top: var(--hairline) solid var(--rule-soft);
    flex-shrink:0;
  }
[data-tool="reporterally"] .action-left {display:flex;align-items:center;gap: var(--s-2);flex-wrap:wrap}
[data-tool="reporterally"] .action-divider {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:500;
    font-size: var(--t-1);
    letter-spacing: normal;
    color:var(--quiet);
    font-style:italic;
  }
[data-tool="reporterally"] .btn-go, [data-tool="reporterally"] .btn-submit {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    padding: var(--s-2) var(--s-3);
    border:1.5px solid var(--accent-deep);
    border-radius:0;
    cursor:pointer;
    transition:background .15s ease,color .15s ease,transform var(--press-out) var(--press-ease);
  }
/* Depth and curve from icons.css; the reasoning lives there. */
[data-tool="reporterally"] .btn-notes:active:not(:disabled), [data-tool="reporterally"] .btn-go:active:not(:disabled), [data-tool="reporterally"] .btn-submit:active:not(:disabled) {transform:scale(var(--press-deep));transition:background .15s ease,color .15s ease,transform var(--press-in) var(--press-ease);}
[data-tool="reporterally"] .btn-go {background:var(--cta-fill);color:var(--cta-ink);--ico-color:currentColor;}
@media (hover: hover) {
[data-tool="reporterally"] .btn-go:hover {background:var(--cta-fill);--ico-color:currentColor;}
}
[data-tool="reporterally"] .btn-submit {background:transparent;color:var(--btn-ink)}
@media (hover: hover) {
[data-tool="reporterally"] .btn-submit:hover {background:var(--cta-fill);color:var(--cta-ink);--ico-color:currentColor;}
}
[data-tool="reporterally"] .btn-go:disabled, [data-tool="reporterally"] .btn-submit:disabled {background:var(--paper-deep);color:var(--quiet);border-color:var(--rule);cursor:not-allowed}
[data-tool="reporterally"] .btn-submit.mode-ideate-btn, [data-tool="reporterally"] .btn-submit.mode-build-btn, [data-tool="reporterally"] .btn-submit.mode-elevate-btn, [data-tool="reporterally"] .btn-submit.mode-fieldreportage-btn {background:transparent;color:var(--btn-ink)}
/* --btn-ink, NOT --accent-deep, and the token was already there for this.
   2026-08-08, Ritu: "Fix ReporterAlly's night Clear button contrast."
   MEASURED on the night canvas: --accent-deep #1E4F80 on --paper #16171A is
   2.12:1 — the label failed the 4.5 text floor, the 1.5px border failed the 3.0
   non-text floor, and on hover the button filled with that same navy and put
   --paper text on it, failing again at 2.12:1. --btn-ink resolves to --accent
   (#56B6BF) on Night and takes all three to 7.54:1.
   Day and Press CANNOT move: --btn-ink is defined as --accent-deep on both, so
   this is byte-equivalent there (7.16:1, unchanged).
   The token's own declaration says why it exists — "accent-deep is
   near-invisible on the dark canvas (Ritu, MacBook night check)" — and until
   now only :focus-visible had adopted it.
   Guard: tests/loading_uniformity.test.js, no --accent-deep on a night canvas. */
[data-tool="reporterally"] .btn-clear {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    padding: var(--s-2) var(--s-3);
    min-height:var(--touch-min);
    display:inline-flex;
    align-items:center;
    background:transparent;
    border:1.5px solid var(--btn-ink);
    color:var(--btn-ink);
    border-radius:0;
    cursor:pointer;
    transition:background .15s ease,color .15s ease, transform var(--press-out) var(--press-ease);
  }
@media (hover: hover) {
[data-tool="reporterally"] .btn-clear:hover {background:var(--btn-ink);color:var(--paper);--ico-color:currentColor;}
}
/* Carried no press before this (measured 2026-09-10). --press-light, same
   idiom as Copy beside it. */
[data-tool="reporterally"] .btn-clear:active:not(:disabled) {transform:scale(var(--press-light));transition:background .15s ease,color .15s ease, transform var(--press-in) var(--press-ease);}
[data-tool="reporterally"] .panel-output {
    display:flex;
    flex-direction:column;
    min-width:0;
    border-left: var(--hairline) solid var(--rule-soft);
    padding-left: var(--s-4);
  }
[data-tool="reporterally"] .btn-copy {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    padding: var(--s-1) var(--s-2);
    background:transparent;
    border:1px solid var(--rule);
    color:var(--ink-soft);
    border-radius:0;
    cursor:pointer;
    transition:border-color .12s ease,color .12s ease;
  }
@media (hover: hover) {
[data-tool="reporterally"] .btn-copy:hover {color:var(--ink);border-color:var(--accent-deep)}
}
[data-tool="reporterally"] .btn-copy.copied {color:var(--good);border-color:var(--good)}
[data-tool="reporterally"] .output-scroll {flex:1;padding-right:4px}
[data-tool="reporterally"] .output-scroll::-webkit-scrollbar {width:6px}
[data-tool="reporterally"] .output-scroll::-webkit-scrollbar-thumb {background:var(--rule)}
@media (hover: hover) {
[data-tool="reporterally"] .output-scroll::-webkit-scrollbar-thumb:hover {background:var(--quiet)}
}
[data-tool="reporterally"] .output-empty {text-align:center;padding: var(--s-5) var(--s-4);color:var(--ink-soft)}
[data-tool="reporterally"] .empty-icon {
    font-family:var(--font-display);
    font-weight:400;
    font-size: var(--t-7);
    color:var(--rule);
    margin-bottom: var(--s-3);
    line-height: calc(var(--s-1) * 10);
  }
[data-tool="reporterally"] .output-empty h3 {
    font-family:var(--font-display);
    font-weight:400;
    font-style:italic;
    font-size: var(--t-6);
    line-height: calc(var(--s-1) * 8);
    letter-spacing: var(--track-tight);
    color:var(--ink);
    margin-bottom: var(--s-2);
  }
/* 400px, AND THE UNIT MATTERS AS MUCH AS THE NUMBER. The line reads "The tool
   extracts, identifies the story's spine and builds it" and wants 356px at
   14px; the old 42ch clamp resolved to 349.371px and dropped "it" onto a line
   of its own. A seven-pixel miss, so a layout fault and not a copy fault.
   NOT ch. A ch is the width of a "0" in whichever font actually won, so the
   clamp moves with the font while the sentence it has to hold does not.
   Measured: with Newsreader loaded, 45ch is 374px and the line fits; with the
   webfont failing and the stack falling back, the same 45ch is 350px and the
   widow returns. A fix that depends on a network request for a font is not a
   fix. 400px clears the sentence by 44px in every font in the stack and still
   keeps the line short against a 600px panel.
   Below 430px the panel itself is narrower than the sentence and it wraps
   there, correctly. FieldReportage keeps 42ch: its own line wants 593px
   against a 600px panel, so it is a two-line sentence by nature and widening
   the clamp would not make it one. */
[data-tool="reporterally"] .output-empty p {
    font-family:var(--font-body);
    font-size:14px;
    line-height: calc(var(--s-1) * 6);
    color:var(--ink-soft);
    max-width:400px;
    /* On a phone the panel is 337-376px and the sentence wants 356px, so it
       has to take two lines there. Balance splits them evenly instead of
       dropping the last word alone, which is the same widow in a narrower
       window. It does nothing at the widths where the line already fits. */
    text-wrap: balance;
    margin:0 auto;
  }
[data-tool="reporterally"] #outputThread {padding-bottom: var(--s-3)}
[data-tool="reporterally"] .thread-entry {margin-bottom:24px;padding-bottom:24px;border-bottom: var(--hairline) solid var(--rule-soft)}
[data-tool="reporterally"] .thread-entry:last-child {border-bottom:none}
[data-tool="reporterally"] .thread-entry .thread-label {
    display:flex;
    align-items:center;
    gap: var(--s-2);
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    color:var(--ink-soft);
    margin-bottom:8px;
  }
[data-tool="reporterally"] .thread-entry .user-label {color:var(--quiet)}
[data-tool="reporterally"] .thread-entry .thread-user-text {
    background:var(--paper-deep);
    padding: var(--s-3) var(--s-3);
    font-family:var(--font-body);
    font-style:italic;
    font-size: var(--t-2);
    line-height: calc(var(--s-1) * 6);
    color:var(--ink);
  }
[data-tool="reporterally"] .thread-entry .output-content {
    font-family:var(--font-body);
    font-size: var(--t-4);
    line-height: calc(var(--s-1) * 8);
    color:var(--ink);
  }
[data-tool="reporterally"] .thread-entry .output-content h1, [data-tool="reporterally"] .thread-entry .output-content h2, [data-tool="reporterally"] .thread-entry .output-content h3 {
    font-family:var(--font-display);
    font-weight:700;
    color:var(--ink);
    margin: var(--s-3) 0 var(--s-2);
    letter-spacing:-.005em;
  }
[data-tool="reporterally"] .thread-entry .output-content h1 {font-size: var(--t-5)}
[data-tool="reporterally"] .thread-entry .output-content h2 {font-size: var(--t-4)}
[data-tool="reporterally"] .thread-entry .output-content h3 {font-size: var(--t-3);text-transform:uppercase;letter-spacing: var(--track-caps);font-family: var(--font-display); font-size-adjust: var(--x-adjust);font-weight:700;color:var(--ink-soft)}
[data-tool="reporterally"] .thread-entry .output-content p {margin-bottom: var(--s-3)}
[data-tool="reporterally"] .thread-entry .output-content strong {font-weight:700;color:var(--ink)}
[data-tool="reporterally"] .thread-entry .output-content em {font-style:italic}
/* Was --accent while the identical FieldReportage blockquote (see the
   [data-tool="fieldreportage"] block above) was --rule: one component, two
   treatments. DESIGN-SYSTEM.md:89 retires the ACCENT rail; a neutral quote rail
   is ordinary typographic convention, so the twins converge on --rule rather
   than both losing it. Ritu's ruling 2026-07-28. */
[data-tool="reporterally"] .thread-entry .output-content blockquote {
    /* NO LEFT RAIL. This project retired the 3px bar by name
       (DESIGN-SYSTEM.md:89), and FieldReportage's copy of this rule still had
       one. It had never actually rendered: the whole output stylesheet was
       pointed at class names the page did not build until 2026-08-21. A quote
       is set apart by its indent, its ground and its italic. Both tools share
       one treatment, because they share one renderer. */
    padding: var(--s-2) var(--s-3);
    margin: var(--s-3) 0 var(--s-3) var(--s-3);
    background:var(--paper-deep);
    font-style:italic;
    color:var(--ink);
  }
[data-tool="reporterally"] .thread-entry .output-content ul, [data-tool="reporterally"] .thread-entry .output-content ol {margin: var(--s-2) 0 var(--s-3) var(--s-3)}
[data-tool="reporterally"] .thread-entry .output-content li {margin-bottom:4px}
[data-tool="reporterally"] .thread-entry .output-content code {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);font-size: var(--t-1);background:var(--paper-deep);
    padding: 1px var(--s-1);border: var(--hairline) solid var(--rule-soft);
  }
[data-tool="reporterally"] .thread-entry .output-content hr {border:none;border-top: var(--hairline) solid var(--rule-soft);margin: var(--s-3) 0}
[data-tool="reporterally"] .thread-entry .stream-dot {
    display:inline-block;width:6px;height:6px;background:var(--mark);border-radius:50%;
    margin-left:3px;vertical-align:middle;animation:stream-dot 1.2s ease-in-out infinite;
  }
@keyframes stream-dot{0%,100%{opacity:.3}50%{opacity:1}}
[data-tool="reporterally"] .output-actions {
    display:flex;
    gap: var(--s-2);
    padding-top:16px;
    border-top: var(--hairline) solid var(--rule-soft);
    margin-top: var(--s-3);
    flex-shrink:0;
  }
[data-tool="reporterally"] .btn-followup, [data-tool="reporterally"] .btn-notes, [data-tool="reporterally"] .output-actions .btn-copy {
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-weight:700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform:uppercase;
    padding: 8px var(--s-2);
    background:transparent;
    border:1px solid var(--rule);
    color:var(--ink-soft);
    border-radius:0;
    cursor:pointer;
    transition:border-color .12s ease,color .12s ease;
  }
@media (hover: hover) {
[data-tool="reporterally"] .btn-followup:hover, [data-tool="reporterally"] .btn-notes:hover, [data-tool="reporterally"] .output-actions .btn-copy:hover {color:var(--ink);border-color:var(--accent-deep)}
}
[data-tool="reporterally"] .settings-backdrop {display:none;position:fixed;inset:0;background:rgba(10,10,10,.94);z-index:998}
[data-tool="reporterally"] .settings-backdrop.visible {display:block}
[data-tool="reporterally"] .settings-panel {display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:min(92vw,720px);max-height:85vh;overflow-y:auto;z-index:999;background:var(--paper);border:2px solid var(--accent-deep);border-radius:0;padding: var(--s-4) var(--s-4);box-shadow:0 24px 64px rgba(10,10,10,.18)}
[data-tool="reporterally"] .settings-panel.visible {display:block}
[data-tool="reporterally"] .settings-panel h2 {font-family:var(--font-display);font-weight:700;font-size: var(--t-5);letter-spacing: var(--track-tight);color:var(--ink);margin-bottom: var(--s-1)}
[data-tool="reporterally"] .settings-coming-soon {
    /* The 3px amber left rail is retired (DESIGN-SYSTEM.md:89) and the colour
       carried no meaning here — this is a "coming soon" note, not a status.
       Carried as a rule line above instead, the house's own structural idiom
       (same move as social-dark.css:329). Ritu's ruling 2026-07-28. */
    margin-top: var(--s-3);padding: var(--s-3) var(--s-3);background:var(--paper-deep);
    border:none;border-top: var(--hairline) solid var(--rule-soft);
    font-family:var(--font-body);font-style:italic;font-size:14px;line-height: calc(var(--s-1) * 5);color:var(--ink);
  }
/* CULLED 2026-08-07: same private in-flow status bar and spinner as
   FieldReportage carried, culled for the same reason — they outranked the
   shared rule and would have held this tool on the invisible strip.
   Guard: tests/loading_uniformity.test.js, no per-tool status-bar override. */
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes settle{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
[data-tool="reporterally"] .mode-bar {animation:settle 700ms cubic-bezier(.2,.7,.1,1) both}
[data-tool="reporterally"] .mode-bar {animation-delay:120ms}
@media (prefers-reduced-motion: reduce) {
[data-tool="reporterally"] .header, [data-tool="reporterally"] .mode-bar, [data-tool="reporterally"] .footer {animation:none}
[data-tool="reporterally"] * {transition:none !important}
}
@media (max-width:960px) {
[data-tool="reporterally"] .header {padding: 24px 24px var(--s-3);grid-template-columns:1fr;row-gap: var(--s-3);align-items:start}
[data-tool="reporterally"] .header-right {justify-content:flex-start;padding-bottom:0}
[data-tool="reporterally"] .mode-bar {padding:0 24px;overflow-x:auto;white-space:nowrap}
[data-tool="reporterally"] .mode-tab {padding: var(--s-2) 16px}
/* h-padding stays 0 on both: --shell already carries the gutter below its cap
   (the old fixed 24px put them 4px off the masthead on a phone). */
[data-tool="reporterally"] .main {grid-template-columns:1fr;padding: 24px 0 var(--s-4);gap:24px}
/* The hero's top padding is NOT restated here. It used to be, at the same
   --s-4 the base rule carried, so the line said nothing. When the base dropped
   to --s-3 on 2026-09-05 that silent copy would have handed a phone MORE room
   above the hero than a desktop, which is backwards. One declaration now. */
[data-tool="reporterally"] .panel-output {border-left:none;padding-left:0;border-top: var(--hairline) solid var(--rule-soft);padding-top:24px}
[data-tool="reporterally"] .footer {padding: var(--s-3) 24px}
}
@media (max-width:520px) {
[data-tool="reporterally"] .header h1 {font-size:36px}
[data-tool="reporterally"] .output-empty h3 {font-size: var(--t-5)}
[data-tool="reporterally"] .mode-bar {padding: 0 var(--s-2);overflow-x:visible}
[data-tool="reporterally"] .mode-tab {flex:1;justify-content:center;padding: var(--s-2) var(--s-1);letter-spacing: var(--track-caps);gap: var(--s-1)}
}

/* ===== The Teaching Room (teach.html) ===== */
/* Ported verbatim from the approved _teach-mockup.html prototype.
   Tokens-only, sharp corners (border-radius: 0) throughout. */

/* Masthead "Teach" — the navy wayfinding button INTO the Teaching Room.
   A FILLED control (matches the editorial-home button in suite.css) so it never
   reads as a fourth theme-toggle segment. Sits in the brand cluster beside the
   wordmark, away from the Day/Night/Press toggle. Fixed navy/cream like the
   masthead's own bg + rule — does NOT theme (the masthead is always cream). */
.mast-nav-link {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: #FBFAF6;                 /* cream — does NOT theme */
  background: #082A4C;            /* brand navy — matches the masthead rule, does NOT theme */
  --ico-color: currentColor;   /* the mortarboard follows the cream label, not the accent */
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-min);   /* 44px — aligns with the theme-toggle + Settings capsules */
  padding: var(--s-2) var(--s-3);
  border-radius: 0;
  line-height: calc(var(--s-1) * 3);
  transition: background .15s ease;
}
@media (hover: hover) {
.mast-nav-link:hover { background: #156169; color: #FBFAF6; --ico-color:currentColor; }
}  /* brand teal; the hover ground IS the day accent, so an accent mark would be 1:1 */
.mast-nav-link:focus-visible { outline: 2px solid #082A4C; outline-offset: 2px; }

/* Room layout */
.teach-room { margin-top: var(--s-5); }

/* The phone step, and it sits HERE rather than beside the other phone rules
   further up this file because the line above re-declares the same property at
   the same specificity and would win on order. Measured 2026-09-16: the rule
   was written at line 855, the page still drew 40, and the cascade was the
   reason, not the value. */
@media (max-width: 430px) {
  .teach-room,
  .teach-room .tz { margin-top: calc(var(--s-1) * 8); }
}

/* Zone chrome */
.tz { margin: var(--s-5) 0 var(--s-3); }
.tz-label {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink);
  display: flex; justify-content: space-between; align-items: baseline;
  border-bottom: 1px solid var(--rule); padding-bottom: var(--s-2);
}
.tz-label .note {
  font-weight: 400; letter-spacing: 0; text-transform: none;
  color: var(--quiet); font-family: var(--font-body);
  font-size: var(--t-3); font-style: italic;
}

/* Feed channel. The comment here read "teal border-left input channel" and the
   rail was exactly the pattern DESIGN-SYSTEM.md:89 retires suite-wide; the teal
   marked nothing a reader could act on. Rule line above instead, matching
   social-dark.css:329. Ritu's ruling 2026-07-28. */
.feed {
  border-top: var(--hairline) solid var(--rule-soft);
  background: var(--paper-deep);
  padding: var(--s-3) var(--s-3);
}
.feed p { margin: 0 0 var(--s-3); color: var(--ink-soft); font-size: var(--t-3); }
.feed textarea {
  width: 100%; box-sizing: border-box;
  font-family: var(--font-body); font-size: var(--t-4);
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--box);
  padding: var(--s-3) var(--s-2); min-height: 4.5rem; resize: vertical;
  transition: border-color .2s ease;
}
.feed textarea:focus { border-color: var(--ink); }
.feed .undertext { font-size: var(--t-3); color: var(--quiet); margin: var(--s-2) 0 var(--s-3); }
.feed select {
  /* 16px, not 12. See the 16px floor note above. */
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-3); letter-spacing: var(--track-caps);
  text-transform: uppercase; background: var(--paper); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 0; padding: var(--s-2) var(--s-2);
  margin-top: var(--s-2); cursor: pointer; outline: none;
}
.feed select:focus { border-color: var(--ink); }

/* Primary CTA — solid mono, sharp, the system's strong-action button */
.cta {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-weight: 700; font-size: var(--t-1);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  padding: var(--s-3) var(--s-4);
  background: var(--cta-fill); color: var(--cta-ink);
  border: none; border-radius: 0; cursor: pointer;
  --ico-color: currentColor;
  transition: transform var(--press-out) var(--press-ease);
}
/* The system's strong-action button carried no press before this (measured
   2026-09-10, Teach's Add to memory). --press-deep, the same depth every
   other loud action in the product takes. */
.cta:active:not(:disabled) {
  transform: scale(var(--press-deep));
  transition: transform var(--press-in) var(--press-ease);
}

/* Lesson cards */
.lesson {
  border: var(--hairline) solid var(--rule-soft); border-radius: 0;
  padding: var(--s-3) var(--s-3); margin-bottom: var(--s-3); background: var(--paper);
}
/* Status recolours the card's own 1px rule — no side-bar tell
   (de-tell 2026-07-31, Ritu's ruling). */
.lesson.proposed { border-color: var(--mark); }
.lesson.active   { border-color: var(--good); }
.lesson-claim {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--t-5); line-height: calc(var(--s-1) * 7); color: var(--ink);
}
.lesson-scope {
  display: inline-block; margin-top: var(--s-2);
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--accent-deep); border: var(--hairline) solid var(--rule-soft); padding: .15rem var(--s-2);
}
.chips { margin-top: var(--s-2); display: flex; gap: var(--s-2); flex-wrap: wrap; }
.chip {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1); letter-spacing: normal;
  padding: .15rem var(--s-2); border: 1px solid var(--rule); color: var(--quiet);
}
.chip.evi   { border-color: var(--mark); color: var(--mark); }
.chip.proof { border-color: var(--good); color: var(--good); }

/* Proof block — before/with-lesson pair */
.proof {
  margin-top: var(--s-3); border-top: var(--hairline) dashed var(--rule-soft);
  padding-top: var(--s-3); display: flex; gap: var(--s-3);
}
.proof > div { flex: 1; }
.proof .plab {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--quiet); margin-bottom: var(--s-1);
}
.proof .ptext {
  font-family: var(--font-body); font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6); color: var(--ink);
}
.proof .ptext.was { color: var(--quiet); }

/* Lesson action row */
.acts { margin-top: var(--s-3); display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center; }
.mini {
  font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  padding: var(--s-2) var(--s-2); border: var(--hairline) solid var(--rule-soft);
  background: transparent; color: var(--ink-soft);
  border-radius: 0; cursor: pointer;
  transition: transform var(--press-out) var(--press-ease);
}
.mini.yes { border-color: var(--good); color: var(--good); }
.mini.no  { border-color: var(--warn); color: var(--warn); }
.mini.see { border-color: var(--accent); color: var(--accent); }
/* Dismissed and Undo last change carried no press before this (measured
   2026-09-10, Teach). --press-light: a disclosure toggle, not the run. */
.mini:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: transform var(--press-in) var(--press-ease);
}

/* Gathering note — quiet italic, below proposed zone */
.gathering { font-size: var(--t-3); color: var(--quiet); font-style: italic; margin-top: var(--s-2); }

/* Floor — locked rules */
.floor { background: var(--paper-deep); padding: var(--s-3) var(--s-3); }
.floor .lock {
  display: flex; gap: var(--s-2); align-items: baseline;
  padding: var(--s-1) 0; font-family: var(--font-body);
  font-size: var(--t-4); color: var(--ink-soft);
}
.floor .lock .k { color: var(--quiet); font-family: var(--font-mono); font-size: var(--t-2); }
.floor .undertext { font-size: var(--t-3); color: var(--quiet); margin-top: var(--s-2); font-style: italic; }

/* ============================================================
   TOUCH FLOORS ON THE TOOL PAGES — 2026-07-28.

   Ritu's ruling of the same day: 44px is the floor for real CONTROLS; inline and
   footer text links take 24px (--touch-link), the AA target size. The landing
   pages got both floors on 2026-07-27; the tool sheet was never touched, because
   the review did not cover these ten pages.

   Measured at 390px before this block, on DeskCopyPro and teach:
     .run-btn      26.6px   <- the PRIMARY action ("Rewrite") on five tools
     .mini         27.8px
     .cta          37.4px
     .fidelity-btn 42.8px
     footer links  15.0px

   Two techniques, because this sheet has two kinds of button and says so at the
   pill-pivot note below. BOXED buttons are capsules and simply take a
   min-height. UNDERLINE actions (.run-btn, .copy-btn, .hist-btn, .clear-btn,
   .revise-btn) have no box: their affordance IS the rule sitting tight under the
   text, so a min-height would strand that rule 18px below the words it belongs
   to. They keep their paint and get the hit area from a transparent pseudo
   instead — the same paint/target split used on .mast-nav-link in suite.css and
   on the front door's masthead CTA.

   ::before, not ::after. This used to be because the run button drew its
   arrow from the trailing pseudo. That arrow was culled on 2026-08-05 when
   the run button took a real mark, so the slot is free — but ::before stays,
   because it is the side the mark sits on and a hit area anchored there
   covers the mark as well as the words. Do not "reclaim" ::after for an icon:
   the mark is a real element, which is what lets setBtnLabel relabel around
   it without erasing it.
   ============================================================ */
.run-btn,
.copy-btn,
.hist-btn,
.clear-btn,
.revise-btn { position: relative; }

.run-btn::before,
.copy-btn::before,
.hist-btn::before,
.clear-btn::before,
.revise-btn::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: var(--touch-min);
  /* Hit area only, never painted. It must NOT carry pointer-events:none — that
     was the first attempt and it made the pseudo untappable, leaving .run-btn at
     a 26px target while looking correct in the stylesheet. Verified by
     hit-testing elementFromPoint above and below the painted box, not by
     reading the CSS. */
}

.mini,
.cta,
.fidelity-btn,
/* The popover action buttons sat at ~24px against the 44px floor (Ritu's
   ruling 2026-07-28: a button below 44px is drift, whatever it looks like).
   The fidelity pair carried the gap first; the provenance review caught it,
   and both take the floor together so the two cards stay siblings. */
.fidelity-acts button,
.prov-acts button { min-height: var(--touch-min); }

/* Footer links are text in a line, not controls: the AA floor, not the AAA one.
   Applying 44px here is what turned the front door's footer into 1,444px. */
.tool-foot a,
.tool-foot .back a { display: inline-flex; align-items: center; min-height: var(--touch-link); }

/* ============================================================
   Pill pivot (2026-07-12) — boxed buttons are capsules.
   Appended last so it wins source-order over the scoped
   border-radius:0 rules above (equal specificity → later wins).
   Underline/typeset actions (.copy-btn, .hist-btn, .run-btn,
   .clear-btn, .revise-btn) have no box and are intentionally
   excluded. Panels, inputs, cards, spinners stay sharp.
   ============================================================ */
.settings-toggle,
.btn-save-settings,
.btn-reset-settings,
.fidelity-btn,
.fidelity-acts button,
.prov-acts button,
.brief-reclass,
.eye-btn,
.mast-nav-link,
.toggle-btn,
.mini,
.cta,
[data-tool="reporterally"] .btn-copy-single,
[data-tool="fieldreportage"] .btn-save-settings,
[data-tool="fieldreportage"] .btn-reset-settings,
[data-tool="fieldreportage"] .btn-go,
[data-tool="fieldreportage"] .btn-submit,
[data-tool="fieldreportage"] .btn-clear,
[data-tool="fieldreportage"] .btn-followup,
[data-tool="fieldreportage"] .btn-notes,
[data-tool="fieldreportage"] .btn-copy,
[data-tool="fieldreportage"] .output-actions .btn-copy,
[data-tool="reporterally"] .btn-go,
[data-tool="reporterally"] .btn-submit,
[data-tool="reporterally"] .btn-clear,
[data-tool="reporterally"] .btn-followup,
[data-tool="reporterally"] .btn-notes,
[data-tool="reporterally"] .btn-copy,
[data-tool="reporterally"] .output-actions .btn-copy {
  border-radius: var(--pill);
}
/* segmented Day / Night / Press control: pill the group, clip the segments */
.theme-toggle { border-radius: var(--pill); overflow: hidden; }

/* ============================================================
   HOUSE-STYLED DROPDOWN MENUS — de-tell, 2026-07-31
   The closed <select> boxes have worn house style since the re-skin, but
   the OPEN menu stayed the browser's own: system font, blue highlight bar,
   checkmark — the identical control every unstyled AI-generated page ships
   (Ritu's Gemini a-ha, 2026-07-31). `appearance: base-select` (Chromium
   135+) hands the open menu to the page. Safari/Firefox ignore this whole
   block and keep today's system menu until they support it; nothing breaks.
   ============================================================ */
@supports (appearance: base-select) {
  /* The per-tool rules pin `appearance: none` at higher specificity
     (.toggle-row select, inline tool styles) and must keep it for the
     non-Chromium fallback, so the opt-in has to win by weight. */
  select { appearance: base-select !important; }
  ::picker(select) { appearance: base-select; }
  /* Each tool draws its own chevron (background gradients); hide the
     base-select arrow so the closed box doesn't wear two. */
  select::picker-icon { display: none; }

  ::picker(select) {
    background: var(--paper);
    color: var(--ink);
    border: 1px solid var(--rule);
    border-radius: 0;
    /* offset + soft blur — a floating panel, not a halo */
    box-shadow: 0 10px 28px -12px var(--overlay-bg);
    padding: var(--s-1) 0;
    /* Long lists scroll inside the panel; the chunky system scrollbar is
       its own small tell, so it thins down and wears the rule colour. */
    scrollbar-width: thin;
    scrollbar-color: var(--rule) transparent;
    /* Enter only: 150ms strong ease-out from near-full scale, origin at
       the trigger. Close stays instant — menus answer, they don't linger. */
    transform-origin: top;
    transition: opacity 150ms cubic-bezier(0.23, 1, 0.32, 1),
                transform 150ms cubic-bezier(0.23, 1, 0.32, 1),
                display 150ms allow-discrete, overlay 150ms allow-discrete;
  }
  @starting-style {
    ::picker(select) { opacity: 0; transform: translateY(-3px) scale(0.98); }
  }

  select option {
    padding: var(--s-2) var(--s-3);
    cursor: pointer;
  }
  /* Chrome's checkmark indents the checked row off the others' left edge;
     the current choice carries the house underline instead — the same
     accent underline the closed select answers focus with. */
  select option::checkmark { display: none; }
  select option:checked {
    text-decoration: underline;
    text-underline-offset: 0.22em;
    text-decoration-thickness: 1px;
    text-decoration-color: var(--accent);
  }
  /* Row highlight = the house teal, never the system blue. :focus-visible
     drops its ring because the bar IS the focus indicator here. */
  select option:focus-visible { outline: none; }
  select option:focus-visible,
  select option:active {
    background: var(--accent);
    color: var(--paper);
  }
  @media (hover: hover) and (pointer: fine) {
    select option:hover { background: var(--accent); color: var(--paper); }
  }
  @media (max-width: 520px) {
    select option { min-height: var(--touch-min); display: flex; align-items: center; }
  }
  @media (prefers-reduced-motion: reduce) {
    ::picker(select) { transition: opacity 120ms ease; }
    @starting-style {
      ::picker(select) { opacity: 0; transform: none; }
    }
  }
}

/* ============================================================
   HOUSE-STYLED CHECKS — same de-tell sweep as the dropdown block
   above (2026-07-31). Native checkboxes/radios rendered the raw
   system control, blue check and all. Redrawn in house grammar:
   hairline box, teal fill, glyph in the page's own paper so every
   theme keeps contrast. --check-accent / --check-glyph let a page
   recolour a lane (FrameWork's navy "verified" check) without
   falling back to the native control.
   ============================================================ */
input[type="checkbox"], input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 16px; height: 16px; flex: none;
  display: inline-grid; place-content: center;
  background: transparent;
  border: 1px solid var(--quiet);
  border-radius: 0;
  vertical-align: -3px;
  margin: 0;
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 120ms ease-out;
}
input[type="radio"] { border-radius: 50%; }
input[type="checkbox"]:checked, input[type="radio"]:checked {
  background-color: var(--check-accent, var(--accent));
  border-color: var(--check-accent, var(--accent));
}
input[type="checkbox"]::after {
  content: ""; width: 4px; height: 8px; margin-top: -2px;
  border-right: 2px solid var(--check-glyph, var(--paper));
  border-bottom: 2px solid var(--check-glyph, var(--paper));
  /* pop, never from nothing: scale .6 → 1, 120ms strong ease-out */
  transform: rotate(45deg) scale(0.6);
  opacity: 0;
  transition: transform 120ms cubic-bezier(0.23, 1, 0.32, 1), opacity 120ms ease-out;
}
input[type="checkbox"]:checked::after { transform: rotate(45deg) scale(1); opacity: 1; }
input[type="radio"]::after {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--check-glyph, var(--paper));
  transform: scale(0.6);
  opacity: 0;
  transition: transform 120ms cubic-bezier(0.23, 1, 0.32, 1), opacity 120ms ease-out;
}
input[type="radio"]:checked::after { transform: scale(1); opacity: 1; }
@media (hover: hover) and (pointer: fine) {
  input[type="checkbox"]:hover, input[type="radio"]:hover { border-color: var(--accent); }
}
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  input[type="checkbox"]::after, input[type="radio"]::after { transition: opacity 120ms ease; }
}

/* The `hidden` ATTRIBUTE outranks any display we set on a control.
   Ritu caught this live on 2026-08-02: CLEAR was sitting on screen at
   "DRAFT · 0 WORDS", and SPEAK was overlapping the draft box. Cause: the
   control pass gave these buttons `display: inline-flex`, and a class selector
   beats the UA stylesheet's `[hidden] { display: none }`. Every control that
   reveals itself by dropping the attribute — Clear, Speak, the output actions,
   the fact bar — was therefore always visible. Any rule that sets `display` on
   something toggled by `hidden` needs this backstop, so it lives at the sheet
   level rather than being remembered per rule. */
[hidden] { display: none !important; }

/* The run button's mark sits AFTER its label.
   Ritu, 2026-08-10, looking at DeskCopy Pro and IntroDoctor: "Align the Rewrite
   button." Then, choosing between three fixes: "Put the mark after the word."

   WHAT WAS ACTUALLY OUT OF LINE, MEASURED AT 1000px ON DESKCOPY PRO: not the
   button. Its own box, the input box above it and that box's label all start at
   x 397.25. What sat in was the WORD. icons.css gives any button holding a mark
   `display: inline-flex` and `gap: 0.5em`, and the mark is 1em wide, so the
   label began 1.5em inside the column edge — 34.5px at the button's 23px. Every
   other label in that column starts on the edge; the primary action's did not.

   WHY THE MARK IS NOT HUNG IN THE MARGIN INSTEAD, which would align the word and
   keep the mark leading. Measured and rejected: DeskTriage's run button sits at
   x 49.25, which IS the page's left edge, so there is no margin to hang it in,
   and on the three-column tools 34.5px would cross the rule between the columns.

   `order` and nothing else. The mark stays inside the button, so it keeps its
   0.5em gap, the underline still spans the whole control, and the 44px hit area
   below is untouched. Verified by rendering all five tools at 1000 and 1440:
   the word starts on the same x as the input box and its label, 0px out, 10/10.

   NOT .btn-go. FieldReportage and ReporterAlly draw a FILLED button, where the
   mark sits inside the fill and the fill's own edge is what lines up with the
   column. There is nothing out of line there to correct.
   Guard: tests/test_css_architecture.py::RunButtonMarkOrderTest */
.run-btn > .ico { order: 2; }

/* The options row is a grid, so it cannot wrap when its labels get wider.
   MEASURED, 2026-08-10: .toggle-options was a wrapping flex row of uppercase
   pills. Taking those labels off the typewriter face widened them enough to
   wrap the row on DeskCopy Pro, which broke a three-element alignment by 53px
   at 1440 and 52.8px at 1280. Columns rather than a wrap is the fix, and it has
   to land WITH the type change, not after it.

   Two columns is the real maximum: probed on the rendered pages, no
   .toggle-options in this product holds more than two buttons (DeskCopy Pro has
   one with two and one with one; DeskCopy and DeskPop have one each). A single
   button therefore fills its cell, which is what it already did as a flex item
   with no sibling. */
.toggle-options {
  display: grid;
  grid-auto-flow: column;
  /* minmax(max-content, 1fr), NOT minmax(0, 1fr). Equal columns was the first
     try and it clipped the longer label: STANDFIRST and SUBHEADS were forced to
     the same width and the T was cut off by its own pill. Caught by looking at
     the render; the delta table said the alignment break was fixed and it was.
     max-content as the floor means a column is never narrower than its label,
     and 1fr shares whatever is left so a lone button still fills its cell. */
  grid-auto-columns: minmax(max-content, 1fr);
}


/* Per-band text sizes, appended by preview/build-precision.mjs
   --apply-type. The product's root font is a clamp, so one rem is a
   different number of pixels at each width and a single mapped value
   would be wrong everywhere except the width it was mapped at. These
   carry identical specificity to the rule above and come later, which is
   how the narrower band wins. */
@media (max-width: 620px) {
.lede {
  font-size: var(--t-4);
}
}
@media (max-width: 620px) {
.panel-cell .style-sheet-picker {
  font-size: var(--t-3);
}
}
@media (max-width: 620px) {
.panel-cell select,
.toggle-row select,
.desk-inline select {
  font-size: var(--t-3);
}
}
@media (max-width: 620px) {
.input-label .label-hint,
.source-disclosure-trigger .label-hint {
  font-size: var(--t-2);
}
}
@media (max-width: 620px) {
textarea.raw-input {
  font-size: var(--t-3);
}
}
@media (max-width: 620px) {
#briefInput {
  font-size: var(--t-3);
}
}
@media (max-width: 620px) {
.run-btn {
  font-size: var(--t-4);
}
}
@media (max-width: 620px) {
.error-msg {
  font-size: var(--t-3);
}
}
@media (max-width: 620px) {
.copy-headline {
  font-size: var(--t-6);
}
}
@media (max-width: 620px) {
.feedback-textarea {
  font-size: var(--t-3);
}
}
@media (max-width: 620px) {
.revise-btn {
  font-size: var(--t-4);
}
}
@media (max-width: 620px) {
.diag-purpose-note {
  font-size: var(--t-2);
}
}
@media (max-width: 620px) {
.rewrite-headline {
  font-size: var(--t-6);
}
}
@media (max-width: 620px) {
.rewrite-body {
  font-size: var(--t-3);
}
}
@media (max-width: 620px) {
.fidelity-status {
  font-size: var(--t-2);
}
}
@media (max-width: 620px) {
.alternatives-heading {
  font-size: var(--t-5);
}
}
@media (max-width: 620px) {
[data-tool="fieldreportage"] .output-empty h3 {
  font-size: var(--t-5);
}
}
@media (max-width: 620px) {
[data-tool="reporterally"] .output-empty h3 {
  font-size: var(--t-5);
}
}
@media (max-width: 620px) {
.feed textarea {
  font-size: var(--t-3);
}
}
@media (max-width: 620px) {
.feed .undertext {
  font-size: var(--t-2);
}
}
@media (max-width: 620px) {
.lesson-claim {
  font-size: var(--t-4);
}
}
@media (max-width: 620px) {
.gathering {
  font-size: var(--t-2);
}
}
@media (max-width: 620px) {
.floor .lock {
  font-size: var(--t-3);
}
}
@media (max-width: 620px) {
.floor .undertext {
  font-size: var(--t-2);
}
}
@media (max-width: 430px) {
.skip-link {
  font-size: var(--t-1);
}
}
@media (max-width: 430px) {
.lede {
  font-size: var(--t-3);
}
}
@media (max-width: 430px) {
.working-line {
  font-size: var(--t-1);
}
}
@media (max-width: 430px) {
.copy-dateline {
  font-size: var(--t-1);
}
}
@media (max-width: 430px) {
.copy-body {
  font-size: var(--t-3);
}
}
@media (max-width: 430px) {
.copy-subhead {
  font-size: var(--t-4);
}
}
@media (max-width: 430px) {
.copy-block {
  font-size: var(--t-3);
}
}
@media (max-width: 430px) {
.diag-points-lead {
  font-size: var(--t-2);
}
}
@media (max-width: 430px) {
.calibration-flag {
  font-size: var(--t-2);
}
}
@media (max-width: 430px) {
.editors-notes-panel-hint {
  font-size: var(--t-2);
}
}
@media (max-width: 430px) {
.editors-note-row {
  font-size: var(--t-2);
}
}
@media (max-width: 430px) {
.tool-foot {
  font-size: var(--t-2);
}
}
@media (max-width: 430px) {
.tool-foot .copy {
  font-size: var(--t-1);
}
}
@media (max-width: 560px) {
@media (max-width: 430px) {
.masthead-utility .settings-toggle {
  font-size: var(--t-1);
}
}
}
@media (max-width: 430px) {
.feedback-hint {
  font-size: var(--t-2);
}
}
@media (max-width: 520px) {
@media (max-width: 430px) {
.rewrite-headline {
  font-size: var(--t-5);
}
}
}
@media (max-width: 430px) {
.fidelity-pop {
  font-size: var(--t-2);
}
}
@media (max-width: 430px) {
.tz-label .note {
  font-size: var(--t-2);
}
}

/* ============================================================
   THE OPTIONAL SOURCE FIELDS FOLD AWAY
   ============================================================
   Ritu's standing grouse: paste, then hit the action, without scrolling.
   MEASURED on DeskCopy Pro at 1440x900, the action sat 269px below the fold and
   362px below at 1280x800. The cause is task order, not density: between the
   required input and the required action sit two fields the editor does not
   have to fill, one of which carries the word "optional" in its own label.

   This is the SAME PATTERN this sheet already uses for the editor's notes
   (.feedback-disclosure-*): a trigger that flips .open on its wrapper, a
   chevron that turns, and a body that is display:none until then. A second
   disclosure vocabulary would be exactly the drift the one-name rule forbids.

   THE MARKER SITS IN THE MARGIN, NOT IN THE TEXT. Measured 2026-08-10 on
   DeskPop: a chevron inside the row pushed its own label right, so "RAW COPY"
   started at 540 and "THE BRIEF" at 560, and the column those labels form came
   apart. A disclosure marker that indents its label breaks the column it sits
   in. 8px wide and pulled back 16 keeps the label on the column and leaves 8px
   of air on the rule side.
   ============================================================ */
.source-disclosure { margin-top: var(--s-4); }

.source-disclosure-trigger {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  min-height: var(--touch-min);
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  text-align: left;
  /* The row keeps the .input-label voice above it, because it IS that label. */
  font-family: var(--font-display); font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  font-weight: 500;
  color: var(--quiet);
  transition: color 0.18s ease, transform var(--press-out) var(--press-ease);
}
@media (hover: hover) {
.source-disclosure-trigger:hover { color: var(--ink); }
}
.source-disclosure-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* Raw copy and Protect-a-section carried no press before this (measured
   2026-09-10, DeskCopy Pro). --press-light: a disclosure trigger, not the
   run. */
.source-disclosure-trigger:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: color 0.18s ease, transform var(--press-in) var(--press-ease);
}

.source-disclosure-chevron {
  flex: none;
  width: var(--s-2);
  margin-inline-start: calc(var(--s-2) * -2);
  font-size: var(--t-1);
  line-height: 1;
  color: var(--accent);
  transition: transform 0.18s ease;
}
.source-disclosure.open .source-disclosure-chevron { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .source-disclosure-trigger, .source-disclosure-chevron { transition: none; }
}

/* The hint keeps the right edge it has today, where the row is space-between. */
.source-disclosure-trigger .label-hint { margin-inline-start: auto; }

.source-disclosure-body { display: none; }
.source-disclosure.open .source-disclosure-body { display: block; }

/* ============================================================
   THE STORY CONTROL, NOW IN THE MASTHEAD
   ============================================================
   Moved 2026-08-11 out of a full-width strip below the header. Ritu, on how the
   action gets above the fold: "by removing the panel beneath the masthead, for
   one." The strip cost 65px on every tool page and its second word repeated the
   tool name the masthead already carries.

   allcap-tool.js INJECTS its own #allcap-mount rules into a <style> at runtime,
   which lands after this file, so equal specificity would lose. A class plus an
   id beats a bare id, which is why every rule here is written that way rather
   than with !important.

   The control keeps everything it had: its dot, its title, its saved flash, and
   the click that opens Save title / New story / Download / Delete / Open storage
   admin. Only its surroundings changed.
   ============================================================ */
.masthead-utility #allcap-mount {
  display: flex;
  align-items: center;
  /* Was a full-bleed bar: space-between, a bottom rule, and padding derived
     from --frame. In a utility row it is one item among four. */
  justify-content: flex-start;
  padding: 0;
  border-bottom: 0;
  background: transparent;
  font-family: var(--font-display);
  font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  color: var(--quiet);
}
/* ===========================================================
   THE STORY NAME ON THE RAW COPY ROW, 2026-09-16.

   The owner asked for "untitled story" to sit next to RAW COPY instead of up
   in the masthead. My first build got it wrong and he said so: "I never asked
   for Untitled story to be larger than it was or 'Title'. I wanted it next to
   raw copy in a small font rather than under the hero, almost like a subhead."

   What was wrong: I set it in caps with the label's tracking and put "Title: "
   in front, which made it loud, and that made it too wide for the row, which
   pushed it onto a line of its own, where it read as a subhead under the
   headline. Three mistakes compounding, and the first one caused the rest.

   IT KEEPS THE VOICE IT HAD IN THE MASTHEAD: sentence case, quiet, no
   tracking, --t-1. Only its position changes. Small and beside RAW COPY at
   every width, because at that size it fits at every width.

   allcap-tool.js MOVES the existing #allcap-mount node into .input-label, so
   this is the same control it always was and the drawer still opens.

   SPECIFICITY IS LOAD-BEARING, the same reason the masthead block below
   carries its own prefix: both scripts inject bare `#allcap-mount .ac-*` rules
   AFTER this sheet loads, at (1,1,0). */
.input-label #allcap-mount {
  display: flex;
  align-items: baseline;
  min-width: 0;
  margin-left: var(--s-3);
  margin-right: auto;                /* beside RAW COPY, not opposite it */
  /* The injected rule carries a hardcoded `border-bottom: 1px solid #242424`
     and `padding: 10px <shell gutter>`, both drawn for a full-width story bar
     under the masthead. In a label row they are a dark rule under the text and
     a second left edge. */
  border-bottom: 0;
  padding: 0;
  overflow: visible;
  /* The row keeps the height it was tuned to. Adding this mount took the label
     from 19px to 25px and box_tools_align_check caught the consequence at six
     widths: the clip's mark moved from 8px above its box to 10. Same answer
     .box-tools uses further up this file. */
  height: 0;
  align-self: center;
}
.input-label #allcap-mount .ac-byline {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
  cursor: pointer;
  /* Keeps the 44px target and gives the height back to the row. */
  margin-block: calc(var(--touch-min) / -2);
  transition: transform var(--press-out) var(--press-ease);
}
.input-label #allcap-mount .ac-byline:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: transform var(--press-in) var(--press-ease);
}
.input-label #allcap-mount .ac-title {
  font-family: var(--font-body);
  font-size: var(--t-1);
  letter-spacing: normal;
  text-transform: none;
  font-style: italic;
  color: var(--quiet);
  /* Sentence case with no tracking, "Untitled story" measures about 75px, so
     the row holds RAW COPY (81), this, the clip (44) and its gaps inside 310px
     at 358 with room to spare. The cap is here only so a long title can never
     push the clip off its column edge, which box_tools_align_check measures. */
  max-width: 24ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
@media (hover: hover) {
  .input-label #allcap-mount .ac-byline:hover .ac-title { color: var(--ink); }
}

/* ── THE SAME NAME, IN A PANEL HEADER. 2026-09-17. ────────────────────────
   Ritu, on FieldReportage: "Even the typeface here is different", then "I'm
   comparing the typeface to the other tools where it is right above the input
   box in titles", then "In field reportage, it is still the same font as
   before and placed in the masthead."

   All three are one carve-out. When the name moved to the work on 2026-09-16
   the shared layer looked for an .input-label and this page has none, so it
   was passed over and kept the masthead's capitals. It now mounts in the
   header above the output, which is the same position on a page whose work
   ARRIVES rather than being pasted, and it takes the same type the editing
   desks give it: the reading serif, 12px, italic, quiet.

   The declarations are the input-label rule's, matched rather than invented,
   so the two cannot drift apart without somebody noticing they were meant to
   be one thing. */
.panel-header #allcap-mount { flex: 0 1 auto; min-width: 0; margin-inline-start: var(--s-2); }
.panel-header #allcap-mount .ac-byline {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
  cursor: pointer;
  transition: transform var(--press-out) var(--press-ease);
}
.panel-header #allcap-mount .ac-byline:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: transform var(--press-in) var(--press-ease);
}
.panel-header #allcap-mount .ac-title {
  font-family: var(--font-body);
  font-size: var(--t-1);
  letter-spacing: normal;
  text-transform: none;
  font-style: italic;
  color: var(--quiet);
  max-width: 24ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
/* The tool's own name is already on this page twice over. */
.panel-header #allcap-mount .ac-sep,
.panel-header #allcap-mount .ac-tool { display: none; }
@media (hover: hover) {
  .panel-header #allcap-mount .ac-byline:hover .ac-title { color: var(--ink); }
}
/* ── THE NAME AS THE OUTPUT'S TITLE, 2026-10-03. ──────────────────────────
   Ritu, with DeskCopy Pro's Draft showing "RAW COPY • Untitled story": "It
   should ideally appear only after an output has been generated. On the
   STORY page for the app and the designated space on the desktop version."
   On the six desks with a Story place the name no longer sits beside RAW
   COPY. allcap-tool.js and allcap-deskcopypro.js give it a line of its own,
   .story-title-line, directly above the output's heading, and leave the
   mount empty until the output lands. The masthead's Stories button reaches
   the story list before then.

   Its type is the app's Story-screen name line, matched rather than invented
   (mockups/48-stories-2026-10-03): the display face at 600, one pixel over
   --t-3, in ink, tight. The dot, the separator and the tool's name are the
   old masthead furniture and stay off. A click on the name still opens the
   drawer, and the name carries the app's chevron to say so; on a pointer it
   keeps its ink, as the app's does (fix round 1).

   An empty line takes no space: before the output, and in the app once
   story-actions.js has moved the name onto the Story screen. The injected
   #allcap-mount rule draws a full-width story bar (padding, a bottom rule,
   mono type), so the class-plus-id prefix is load-bearing, as above. */
.story-title-line:not(:has(> #allcap-mount:not(:empty))) { display: none; }
.story-title-line { margin: 0 0 var(--s-1); min-width: 0; }
.story-title-line > #allcap-mount {
  display: flex; justify-content: flex-start; align-items: center;
  min-width: 0; margin: 0; padding: 0; border-bottom: 0; background: none;
}
.story-title-line > #allcap-mount .ac-byline {
  display: flex; align-items: center; gap: calc(var(--s-1) + 2px);
  min-width: 0; max-width: 100%; cursor: pointer;
  transition: transform var(--press-out) var(--press-ease);
}
.story-title-line > #allcap-mount .ac-byline:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: transform var(--press-in) var(--press-ease);
}
.story-title-line > #allcap-mount :is(.ac-dot, .ac-sep, .ac-tool),
.story-title-line > #allcap-mount .ac-saved:empty { display: none; }
.story-title-line > #allcap-mount .ac-title {
  font-family: var(--font-display); font-style: normal;
  font-size: calc(var(--t-3) + 1px); font-weight: 600; color: var(--ink);
  letter-spacing: var(--track-tight); text-transform: none;
  max-width: none; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.story-title-line > #allcap-mount .ac-saved {
  font-family: var(--font-body); font-size: var(--t-1); font-style: italic;
  letter-spacing: normal; text-transform: none; color: var(--mark);
}
.story-title-line > #allcap-mount .ac-byline::after {
    content: ''; flex: none; width: 16px; height: 16px; background: var(--accent);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
  }
@media (hover: hover) {
  .story-title-line > #allcap-mount .ac-byline:hover .ac-title { color: var(--ink); }
}
/* ── SETTINGS AND STORIES AS ROUND BUTTONS, 700 TO 1168px. 2026-10-03. ──
   The Stories pill raised the width at which the desk masthead holds one row
   by about 179px (DeskCopy Pro 982 to 1161px; FieldReportage, the widest,
   994 to 1169px). Below that the controls dropped under the wordmark, and
   the masthead grew from 94.6px to 159.5px at 1024. Shown four renders, Ritu
   chose "1": the two buttons take the phone's 44px round form, words hidden,
   their aria-labels unchanged. Nothing else moves: the wordmark, Day / Night
   / Press, Teach, the menu and the desk's name stay, with the same gaps and
   tracking.
   The band starts at 700px, where the phone layout (max-width: 699.98px),
   which already draws these two as circles, ends, so there is no gap. It
   was first written from 1001px, directly above this file's (max-width:
   1000px) layout. Measured then: at 1000px, and from about 940px down to
   700px, five desks still wrapped where they held one row before the
   Stories button (at 1000px every desk did). From 700px every width that
   held one row before holds it again. 1169px and wider keep the words.
   Only a masthead that carries Stories is touched, so DeskTriage and the
   Teaching Room are as they were. Plain min-width and max-width, which
   every browser this product supports reads. */
@media (min-width: 700px) and (max-width: 1168px) {
  .masthead-utility:has(> #btnStories) > :is(#btnSettings, #btnStories) {
    min-width: var(--touch-min); min-height: var(--touch-min);
    padding: 0; justify-content: center; font-size: var(--t-2);
  }
  .masthead-utility:has(> #btnStories) > :is(#btnSettings, #btnStories) .btn-label {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
}
.input-label #allcap-mount .ac-dot { background: var(--mark); }
/* The tool name is dropped on this row: the page IS the tool and its name is
   already in the masthead. Dropped in CSS, not in the template, so teach.html
   and the reportage desks keep theirs. */
.input-label #allcap-mount .ac-sep,
.input-label #allcap-mount .ac-tool { display: none; }
.input-label #allcap-mount .ac-saved {
  font-family: var(--font-body);
  font-size: var(--t-1);
  letter-spacing: normal;
  text-transform: none;
  font-style: italic;
  color: var(--mark);
}


.masthead-utility #allcap-mount .ac-byline {
  gap: var(--s-2);
  margin-inline-end: calc(var(--track-caps) * -1);
  transition: transform var(--press-out) var(--press-ease);
}
/* Open story drawer carried no press before this (measured 2026-09-10).
   --press-light: it opens a panel, it is not the run. The class plus id
   prefix is required here, not for the pill's own reasons but because
   allcap-tool.js injects its own #allcap-mount rules after this sheet loads;
   see the note above this block on why every rule here carries it. */
.masthead-utility #allcap-mount .ac-byline:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: transform var(--press-in) var(--press-ease);
}
.masthead-utility #allcap-mount .ac-title {
  font-family: var(--font-display);
  font-size: var(--t-1);
  color: var(--quiet);
  /* 44ch was right across a full-width strip. Beside three other controls it
     is not: the masthead's own drift is a known open item and a long story
     title must not be allowed to feed it. */
  max-width: 22ch;
}
@media (hover: hover) {
.masthead-utility #allcap-mount .ac-byline:hover .ac-title { color: var(--ink); }
}
.masthead-utility #allcap-mount .ac-dot { background: var(--mark); }
.masthead-utility #allcap-mount .ac-sep { color: var(--rule); }
/* The tool name is dropped here, not in the JS: the masthead's own suite-mark
   already says which tool this is, one row to the right. */
.masthead-utility #allcap-mount .ac-sep,
.masthead-utility #allcap-mount .ac-tool { display: none; }
/* ---- Provenance receipts (2026-08-19) ----
   A receipt is proof, not an error. The mark voices already taken:
   fidelity-flag = hairline solid accent, style-change = hairline dotted
   accent, style-flag = hairline dashed warn. The receipt speaks in the
   annotation voice: hairline dotted QUIET ink, no background. Only
   .is-ungrounded raises its voice: solid warn, apart from the dashed-warn
   style flag by line style and from the solid-accent fidelity flag by
   colour. One rule weight product-wide (the hairline fixed point), so the
   alarm is carried by colour, not thickness. */
mark.prov-mark { background: transparent; border-bottom: var(--hairline) dotted var(--quiet); color: inherit; cursor: pointer; padding: 0 1px; }
mark.prov-mark.is-ungrounded { background: transparent; border-bottom: var(--hairline) solid var(--warn); }
.prov-status { font-family: var(--font-body); font-size: var(--t-3); color: var(--quiet); }
/* The span starts empty and fills only after a trace. An empty flex item
   still owns its two row gaps, which doubled the gap mid-bar on every load
   (review catch, 2026-08-19). */
.prov-status:empty { display: none; }
.prov-pop {
  margin: var(--s-2) 0; background: var(--paper-warm);
  border: var(--hairline) solid var(--rule-soft);
  border-radius: 0; padding: var(--s-3) var(--s-3);
  font-family: var(--font-body); font-size: var(--t-3); line-height: calc(var(--s-1) * 6);
}
.prov-pop .prov-verdict { color: var(--ink); font-weight: 600; margin-bottom: var(--s-1); }
.prov-pop .prov-verdict.is-bad { color: var(--warn); }
.prov-pop .prov-src { color: var(--quiet); font-size: var(--t-2); margin-bottom: var(--s-1); }
.prov-pop .prov-note { color: var(--quiet); font-size: var(--t-2); margin-bottom: var(--s-2); }
.prov-acts { display: flex; gap: var(--s-2); }
.prov-acts button { font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; border-radius: 0; padding: var(--s-1) var(--s-2); cursor: pointer; }
.prov-dismiss { background: transparent; color: var(--accent-deep); border: var(--hairline) solid var(--rule-soft); }
.prov-list { margin-top: var(--s-2); }
.prov-list .prov-row { display: flex; align-items: baseline; gap: var(--s-2); padding: var(--s-2) 0; border-top: var(--hairline) solid var(--rule-soft); font-size: var(--t-2); }
.prov-tag { text-transform: uppercase; font-size: var(--t-1); letter-spacing: var(--track-caps); padding: 1px var(--s-1); border: var(--hairline) solid var(--rule-soft); border-radius: 3px; color: var(--quiet); flex: none; }
.prov-text { flex: 1; color: var(--ink-soft); }

@media (max-width: 620px) {
.prov-status {
  font-size: var(--t-2);
}
}
@media (max-width: 430px) {
.prov-pop {
  font-size: var(--t-2);
}
}

/* ============================================================
   THE HEADLINE BENCH (2026-08-23)
   ============================================================
   Ritu: "As it is, there are far too many buttons already." So the bench adds
   NO button. The rendered headline is the control; a chevron trails its last
   line and the alternatives open beneath it.

   The mark's vertical placement is MEASURED, not judged by eye. At
   vertical-align:0 the 44px box's bottom sits on the baseline, putting the mark
   22px high. At -0.3em it lands exactly ON the baseline, 10px low. Schibsted
   Grotesk's x-height is 0.527em (the same figure --x-adjust records), so the
   midline is 0.25em above the baseline and -0.05em is the value that puts the
   mark's centre there. In em so it holds at --t-6 and --t-5 too.

   The panel is a POPOVER, so it stays square: Ritu's 2026-08-07 curve ruling
   covers input and output boxes only, and leaves panels, cards, popovers and
   tags sharp.

   The reveal animates grid-template-rows, the technique for an unknown height.
   Spacing lives INSIDE the collapsing row rather than on a margin, so no second
   layout property has to be animated alongside it. */

.hb-trigger {
  display: block; width: 100%; text-align: left;
  font: inherit; letter-spacing: inherit; color: inherit;
  background: transparent; border: 0; padding: 0; margin: 0; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.hb-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 6px; }

/* The mark is 26px, NOT a 44px box. MEASURED: a 44px box after the last word
   does not fit inside the output column and drops the chevron onto its own
   line. The 44px floor is met by the control itself, because the whole
   headline is the button and it is far larger than 44px in every direction.
   A mark inside a button does not need its own tap target. */
.hb-chev {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  /* The negative right margin takes the mark OUT of line-breaking. MEASURED on
     DeskCopy at 1440: the last line ended 9px short of the column, so any
     positive footprint drops the mark onto its own line.
     The value is --s-5 and not --s-4 for a reason worth writing down. The mark
     is 26px with a 4px left margin, so it advances the line by 30px. At --s-4
     (24px) the net footprint is +6px, which fits the 9px measured here and is
     a coin toss at any other width. At --s-5 (40px) the net is -10px, so the
     line box never grows and the mark can never wrap, at any width. */
  margin: 0 calc(var(--s-5) * -1) 0 var(--s-1);
  vertical-align: -0.16em;
  color: var(--quiet);
  transition: color .18s ease, transform .22s cubic-bezier(.16,1,.3,1);
  flex: none;
}
.hb-chev svg { display: block; width: 26px; height: 26px; }
/* :hover and :focus-visible no longer share one rule, split below: a tap
   fires :hover on a touch screen and it sticks; keyboard focus must answer
   with no pointer involved at all. */
@media (hover: hover) { .hb-trigger:hover .hb-chev { color: var(--accent); } }
.hb-trigger:focus-visible .hb-chev { color: var(--accent); }
[data-bench-open="true"] .hb-chev { color: var(--accent); transform: rotate(180deg); }

.hb-panel {
  overflow: hidden;
  max-width: var(--measure);
  margin: 0;
  border-top: var(--hairline) solid var(--rule);
  border-bottom: var(--hairline) solid var(--rule);
  border-color: transparent;
  display: grid; grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows .24s cubic-bezier(.16,1,.3,1), opacity .18s ease, border-color .18s ease,
              margin-bottom .24s cubic-bezier(.16,1,.3,1);
}
.hb-panel > .hb-inner { min-height: 0; overflow: hidden; padding-bottom: var(--s-3); }
/* THE BOTTOM RULE HAD NOTHING UNDER IT. Ritu, 2026-09-06, on opening the bench:
   the indicator "is so tight and almost touching the text or the line below".
   Measured on DeskCopy at 1440, open and waiting: the headline clears the panel
   by 16px, the top rule clears the words by 17px, and the bottom rule cleared
   the body by ZERO. The rewrite body's first paragraph carries margin-top: 0,
   so the hairline sat on its line box.

   The space is given to the OPEN state only, and travels with the reveal. Both
   halves matter. A margin on the resting rule would hold the body 16px down
   while the bench is shut, which is a gap for a panel nobody opened. And the
   note above this block says spacing was kept inside the collapsing row so the
   reveal animates one property; that reasoning is about the row's own padding,
   which is still inside. This is the panel's clearance from its neighbour, it
   cannot live inside the row, and it is listed in the transition so it opens
   with the panel instead of snapping 16px the moment the rule appears.

   --s-3 is the same 16px the headline already leaves above the panel, so the
   block is evenly bounded rather than hanging off the copy beneath it. */
[data-bench-open="true"] .hb-panel {
  grid-template-rows: 1fr; opacity: 1; border-color: var(--rule);
  margin-bottom: var(--s-3);
}
@media (prefers-reduced-motion: reduce) { .hb-panel { transition: none; } }

.hb-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-3) 0 var(--s-2);
  font-family: var(--font-display); font-size: var(--t-1); font-size-adjust: var(--x-adjust);
  font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--quiet);
}
.hb-esc { letter-spacing: normal; text-transform: none; font-weight: 400; }
/* THE WAIT. The bench calls the engine itself and never raises #statusBar, so
   the status capsule and the held line both stay down for this run: without a
   mark here, the panel says "Reading the story" and then nothing moves for the
   length of two engine calls. It takes the held line's own dot rather than a
   mark of its own, so a wait looks the same everywhere in the tool, and the dot
   carries its own reduced-motion rule with it. */
.hb-wait { display: inline-flex; align-items: flex-start; gap: var(--s-2); }
/* THE DOT HOLDS THE FIRST LINE, NOT THE CENTRE OF THE BOX. The head is
   align-items: baseline, which would hang the dot off the cap line, so the row
   aligns to the top and the dot takes the half-leading down to the centre of
   the first line. The shipped words fit one line at every width measured, 320
   to 1440, with 23px to spare at 320. The rule is here for when they do not:
   they are authored copy, and a webfont that fails to load widens them. A
   longer wording tried on the way here measured 293px inside a 310px panel at
   390 and took two lines at 360, and a centred mark then floats between them,
   which is the trap the status bar's spinner already carries a note about.
   The rem value is the fallback and is the precision generator's own
   (--apply-space); browsers that have `lh` take the exact half-leading and
   stay right if the line-height ever moves. */
.hb-wait .working-dot { margin-top: var(--s-1); margin-top: calc((1lh - 0.5rem) / 2); }
.hb-retry {
  font: inherit; letter-spacing: var(--track-caps); text-transform: uppercase;
  background: transparent; border: var(--hairline) solid var(--rule); color: var(--ink);
  border-radius: var(--pill); padding: var(--s-1) var(--s-2); min-height: var(--touch-min); cursor: pointer;
}
@media (hover: hover) {
.hb-retry:hover { border-color: var(--accent); }
}
.hb-retry:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.hb-opt {
  display: block; width: 100%; text-align: left;
  background: transparent; border: 0; border-top: var(--hairline) solid var(--rule-soft);
  padding: var(--s-3);
  cursor: pointer; position: relative;
  transition: background-color .14s ease;
}
.hb-opt:first-of-type { border-top: 0; }
/* THE OPTIONS LAND IN SEQUENCE. They arrive after a wait of two engine calls,
   and arriving all at once reads as a block of text appearing rather than a
   list being offered. The motion is the one the diagnosis rows already use
   (`dt-rowIn`, five pixels and a fade) rather than a second entry animation
   with the same job; the keyframes are global, and the `dt-` name is where
   DeskTriage happened to declare them first.

   50ms between rows, which is inside the 30-80ms that reads as a sequence
   rather than a queue. Five rows at most, so the last one starts 200ms in and
   the whole list has landed by 550ms. Nothing waits for it: a row is clickable
   and focusable throughout, the delay only holds its paint. */
.hb-opt { opacity: 0; transform: translateY(5px); animation: dt-rowIn .35s ease forwards; }
.hb-opt:nth-of-type(2) { animation-delay: 50ms; }
.hb-opt:nth-of-type(3) { animation-delay: 100ms; }
.hb-opt:nth-of-type(4) { animation-delay: 150ms; }
.hb-opt:nth-of-type(5) { animation-delay: 200ms; }
@media (prefers-reduced-motion: reduce) {
  /* Fewer and gentler, not none — but a stagger is pure movement, so it goes
     and the rows are simply there. */
  .hb-opt { animation: none; opacity: 1; transform: none; }
}
/* :hover and :focus-visible no longer share one rule, split below: a tap
   fires :hover on a touch screen and it sticks; keyboard focus must answer
   with no pointer involved at all. */
@media (hover: hover) { .hb-opt:hover { background: var(--paper-deep); } }
.hb-opt:focus-visible { background: var(--paper-deep); }
.hb-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.hb-text {
  display: block;
  font-family: var(--font-display); font-weight: 600; font-size: var(--t-5);
  line-height: 1.24; letter-spacing: var(--track-tight); color: var(--ink);
  margin: 0 0 var(--s-1);
}
.hb-why {
  display: block; font-family: var(--font-body); font-size: var(--t-2);
  line-height: 1.5; color: var(--quiet);
}
/* The current headline is marked by its word, not by a coloured stripe down the
   side of the row: the tag already says it, and the stripe is the callout
   pattern this system does not use. */
.hb-tag {
  display: inline-block; vertical-align: 3px; margin-right: var(--s-2);
  font-family: var(--font-display); font-size: var(--t-1); font-size-adjust: var(--x-adjust);
  font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--mark);
}
.hb-tag.hb-now { color: var(--accent); }

@media (max-width: 620px) {
  .hb-text { font-size: var(--t-4); }
}

/* ============================================================
   COMPARE SHEET (2026-10-01, spec section 3). Opens and closes
   with no animation. One highlight colour. No side stripes.
   ============================================================ */
.cmp-scrim { position: fixed; inset: 0; background: rgba(26, 23, 23, 0.32); z-index: 900; }
.cmp-sheet {
  position: fixed; inset: 32px 48px; z-index: 901; display: flex; flex-direction: column;
  background: var(--paper-warm); border: var(--hairline) solid var(--rule-soft);
}
.cmp-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding: var(--s-3) var(--s-4) 16px; border-bottom: var(--hairline) solid var(--rule-soft);
}
.cmp-words { min-width: 0; }
.cmp-title {
  margin: 0; font-family: var(--font-display); font-weight: 700; font-size: var(--t-1);
  letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink);
}
.cmp-summary { margin: var(--s-1) 0 0; font-family: var(--font-body); font-size: var(--t-3); color: var(--ink-soft); }
.cmp-hint { margin: 2px 0 0; font-family: var(--font-body); font-style: italic; font-size: var(--t-2); color: var(--quiet); }
.cmp-close, .cmp-switch button {
  flex: none; min-height: 44px; padding: var(--s-2) var(--s-3); border: 1px solid var(--rule); border-radius: var(--pill);
  background: none; color: var(--ink); font-family: var(--font-display); font-weight: 600; font-size: var(--t-1);
  letter-spacing: var(--track-caps); text-transform: uppercase; cursor: pointer; white-space: nowrap;
}
.cmp-switch { display: none; }
.cmp-pane[hidden] { display: none; }
.cmp-panes { flex: 1; min-height: 0; display: grid; grid-template-columns: 1fr 1fr; }
.cmp-pane { overflow-y: auto; padding: 0 var(--s-4) 40px; }
.cmp-pane + .cmp-pane { border-left: var(--hairline) solid var(--rule-soft); }
.cmp-pane-head {
  position: sticky; top: 0; padding: var(--s-3) 0 var(--s-2); background: var(--paper-warm);
  font-family: var(--font-display); font-weight: 600; font-size: var(--t-1); letter-spacing: var(--track-caps);
  text-transform: uppercase; color: var(--quiet);
}
.cmp-pane p {
  margin: 0 -8px var(--s-3); padding: 4px 8px; border-radius: 2px; cursor: pointer;
  font-family: var(--font-body); font-size: var(--t-3); line-height: calc(var(--s-1) * 6); color: var(--ink);
  overflow-wrap: anywhere;
}
.cmp-pane p.is-match { background: color-mix(in srgb, var(--accent) 11%, transparent); }
html.panel .cmp-sheet { inset: 0; border: 0; }
html.panel .cmp-switch { display: flex; gap: 8px; padding: var(--s-2) 16px; border-bottom: 1px solid var(--rule-soft); }
html.panel .cmp-switch button { flex: 1; }
html.panel .cmp-switch button[aria-pressed="true"] { border-color: var(--ink); background: var(--paper-deep); }
html.panel .cmp-panes { grid-template-columns: 1fr; }
html.panel .cmp-pane + .cmp-pane { border-left: 0; }
html.panel .cmp-pane-head { display: none; }
@media (display-mode: standalone) {
  .cmp-sheet { inset: 0; border: 0; }
  .cmp-switch { display: flex; gap: 8px; padding: var(--s-2) 16px; border-bottom: 1px solid var(--rule-soft); }
  .cmp-switch button { flex: 1; }
  .cmp-switch button[aria-pressed="true"] { border-color: var(--ink); background: var(--paper-deep); }
  .cmp-panes { grid-template-columns: 1fr; }
  .cmp-pane + .cmp-pane { border-left: 0; }
  .cmp-pane-head { display: none; }
}

/* ============================================================
   THE SYNC NOTICE (2026-10-04). When two devices disagree about a
   story, it asks which version to keep (Ritu: "2", ask the user).
   Built as approved in mockups/49 (notice A, notice B), with the
   story's title added as a line under the heading (his "Yes").
   It follows the Compare sheet: the same scrim, paper-warm ground,
   hairline rule-soft edge, square corners, and outline pills
   (.clear-btn), none filled, equal weight. No close button. On a
   phone it is a sheet along the bottom, in thumb reach, and each
   pill takes the full width (in the phone layer). Drawn by showSyncNotice in both story
   layers (allcap-tool.js, allcap-deskcopypro.js).
   ============================================================ */
.sync-scrim { position: fixed; inset: 0; z-index: 900; background: rgba(26, 23, 23, 0.32); }
.sync-notice {
  position: fixed; z-index: 901; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(560px, calc(100% - 32px));
  max-height: calc(100% - 32px); overflow-y: auto;
  background: var(--paper-warm); border: var(--hairline) solid var(--rule-soft);
  padding: var(--s-4) var(--s-4) var(--s-3);
  box-shadow: 0 12px 32px rgba(26, 23, 23, 0.14);
}
.sync-notice:focus { outline: none; }
.sync-notice:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sync-head {
  margin: 0 0 var(--s-2); font-family: var(--font-display); font-weight: 600;
  font-size: var(--t-5); line-height: 1.24; letter-spacing: var(--track-tight, -0.01em);
  color: var(--ink); text-wrap: balance;
}
/* The story's title, one line, so the reader knows which story is meant. */
.sync-story {
  margin: 0 0 var(--s-1); font-family: var(--font-body); font-weight: 600;
  font-size: var(--t-3); line-height: 1.4; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sync-line {
  margin: 0 0 var(--s-3); font-family: var(--font-body); font-size: var(--t-3);
  line-height: 1.5; color: var(--ink-soft); text-wrap: pretty;
}
.sync-rows { list-style: none; margin: 0; padding: 0; }
.sync-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-3) 0; border-top: var(--hairline) solid var(--rule-soft);
}
.sync-who { min-width: 0; }
.sync-label {
  display: block; margin: 0 0 var(--s-1); font-family: var(--font-display); font-weight: 600;
  font-size: var(--t-1); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink);
}
.sync-meta {
  display: block; font-family: var(--font-body); font-size: var(--t-3); line-height: 1.4;
  color: var(--quiet); font-variant-numeric: tabular-nums;
}
.sync-meta .dot { padding: 0 var(--s-1); }
.sync-pill { flex: none; justify-content: center; white-space: nowrap; }
.sync-actions {
  display: flex; gap: var(--s-2); flex-wrap: wrap;
  padding-top: var(--s-3); border-top: var(--hairline) solid var(--rule-soft);
}
/* Its phone shape lives in the phone layer below (THE SYNC NOTICE ON A PHONE). */

/* ============================================================
   THE PHONE LAYER (2026-09-10). Below --phone-max the tool page
   becomes three standing places. Above it every rule here is
   inert and the desktop is untouched, which
   scripts/place_switch_check.mjs proves rather than assumes.
   ============================================================ */
@media (max-width: 699.98px) {
  /* FieldReportage's Blueprint button drops "Build" in the app (Ritu,
     2026-09-29). The reasons sit beside the other two rules for it, near
     [data-tool="fieldreportage"] .action-left .btn-label. */
  [data-tool="fieldreportage"] .bp-build { display: none; }

  /* INTRODOCTOR'S OPTION HEADER STACKS, 2026-09-16, and it lives in THIS block
     rather than one of its own because tests/phone_places.test.js requires the
     phone layer to be a single block and fails on a second.

     Ritu sent the output from his phone: "Need I say anything?" He did not.
     The strategy sentence was squeezed into a 126px column, ran to ten lines,
     spilled 34px past the card at 358 and 56px at 320, and the Copy button sat
     on top of the words. Header height measured 53px at 1440 and 207px at 320,
     358, 390 and 412: one row cannot hold a big "Option 4", a bordered tag, a
     sentence of tracked caps and a button inside 310px, so flex starved the
     only item that could shrink.

     Row one is the number, the tag and the button. Row two is the sentence,
     full card width. The order is swapped against the desktop's on purpose:
     with the sentence at order 2 it took row two and pushed the button onto a
     third row of its own, left-aligned under the text, which read as a
     mistake. Measured after: nothing past the card at any width, and the
     button never over the text. */
  /* REVISE BECOMES THE ACTION IT IS, on a phone only. His choice of three
     rendered options, 2026-09-16.

     phone_balance_check reported it standing 222.98px inside the panel's right
     edge, and alignment could not answer that: everything else in the column
     holds BOTH edges, so left-aligned missed the right by 223px and
     right-aligned missed the left by the same. Measured both ways. Only a
     full-width control satisfies it.

     Which is why this is a pill here and not a wider underline. `.revise-btn`
     is an underlined text action; stretched across the column that 1px bottom
     border reads as a divider under the box, not as a button. A bordered pill
     is what every other desk gives its primary action on a phone, and it is
     unmistakably the thing you press. The desktop keeps the underline. */
  .revise-btn {
    width: 100%;
    border: 1px solid var(--ink);
    border-radius: var(--pill);
    padding: var(--s-2) var(--s-3);
    font-size: var(--t-4);
    justify-content: center;
    text-align: center;
  }
  @media (hover: hover) {
    .revise-btn:hover:not(:disabled) { border-color: var(--accent); }
  }
  .revise-btn:disabled { border-color: var(--rule); }

  .alt-card-header { flex-wrap: wrap; align-items: center; }
  .alt-label { flex: 1 1 auto; }
  .alt-card-header .btn-copy { order: 2; margin-left: auto; }
  .alt-card-header .alt-strategy { order: 3; flex: 0 0 100%; }
  /* The masthead's measured height at phone width, read by the sticky hero
     below and by nothing else. Its 60px is not a free number: the masthead is
     gated at that height on nine pages at 320/358/360/390/412 by
     scripts/box_tools_align_check.mjs, and that gate now also measures the
     scrolled hero standing exactly on the masthead's foot, so the two cannot
     drift apart in silence. */
  :root {
    --phone-mast-h: 60px;
    /* The fixed hero band's height. Declared because the page below spends
       the same number as padding-top, and gated by box_tools_align_check.mjs
       rather than trusted. 62px is 32 above one 22px line and 8 below it: the
       Scroll Desk's title band (assets/css/scroll-desk.css), a Social surface
       that loads this sheet and keeps its own band. */
    --phone-hero-h: 62px;
  }
  /* THE EDITORIAL BAND ENDS ON A RULE (2026-10-07, option 1 of the hero band
     mockup, chosen after the owner said "Can't decide, take the right call",
     as the one option that keeps the headline completely static). Page text
     scrolls under the band and
     was cut against its foot with nothing to mark the edge. The band now takes
     the foot bar's ground and hairline rule (h1.display, below), with 16px
     under the words: 32 above + 22 line + 16 below + 1 rule = 71px.
     Scoped by :has(h1.display) because the Scroll Desk loads this sheet and
     its title is .hook-title, not h1.display, so its band stays at 62px. A
     headline that wraps adds one line-height per extra line to this number
     (phone-places.js, heroRoom). */
  :root:has(h1.display) { --phone-hero-h: 71px; }
  /* The masthead wrapped to two rows at 390 and dropped Settings onto a
     line of its own, costing a fifth of the screen before the work starts.
     The wordmark and the tool controls hold one row; the words come off
     the buttons and go to aria-label, on Ritu's ruling of 2026-08-20 that
     a mark everyone reads ships with no word. */
  .tool-masthead {
    display: flex; align-items: center; gap: var(--s-2);
    /* The vertical padding is the phone's own; the SIDE padding is the base
       rule's expression, put back. This block had overridden both with a flat
       var(--s-3), which bought room in a tight row and quietly took the
       wordmark off the column every page sets its own text on.

       MEASURED at 390 before this line, across the six surfaces the wordmark
       gate watches: every page's first line of content begins at 24.5px, the
       three landing pages put the mark at 23.5 (the 1px is .wordmark img's
       documented optical offset, not a fault), and the three TOOL pages put
       it at 15. The tool pages were the deviation, 9.5px left of their own
       copy, and the gate had been reporting the 8.5px spread since. After:
       23.23 / 23.5 / 23.7 at 360 / 390 / 412, against content at 24.23 /
       24.52 / 24.7, which is the same 1px offset the landing pages carry.

       Deriving it from --frame rather than restating a number is the whole
       point of the base rule's own note: a fixed gutter and a centred column
       drift apart as the viewport grows, and this way they cannot. */
    padding: var(--s-1) calc((100% - var(--frame)) / 2);
    flex-wrap: nowrap;
    /* Ritu, 2026-09-12, as a non-negotiable: "In every tool on the app, the
       masthead and the main settings must be static when the user scrolls
       down." The masthead was ordinary flow and scrolled away, which is one
       of the things he had already named under "everything should be
       app-like". It now pins to the top of the screen at phone width.

       It needs no new background or rule to do it: the base rule already
       paints .tool-masthead with var(--paper) and draws the navy brand rule
       along its foot, so content passes under an opaque bar with a real edge
       rather than showing through it.

       z-index 30 puts it over the page and UNDER the tab bar's 40, so the two
       fixed pieces of chrome can never argue about an overlap, and far under
       the settings overlay's 999, which must still cover everything. */
    position: sticky;
    top: 0;
    z-index: 30;
  }
  /* AND THE HERO DOES NOT MOVE AT ALL. Ritu, 2026-09-13, twice: first "can we
     not make the hero static along with the masthead? Do it only if technically
     feasible", and then, looking at the sticky version on his own phone, "The
     hero still moves in each of the tools. It should be completely static."

     He is describing what sticky IS: the band scrolls up with the page until it
     reaches the masthead and only then pins. Static means fixed, from the first
     frame, so the rule below is position: fixed and the band never travels.

     THREE THINGS FOLLOW FROM FIXED, and each one is why this is not a one-line
     change. (a) The band leaves the flow, so the page has to be pushed down by
     its exact height or the first card opens underneath it: --phone-hero-h is
     that height and .main pays it. (b) The height therefore cannot be whatever
     the font happens to come to on each page. line-height is set to 22px,
     the band's height is the declared --phone-hero-h (71px for one line since
     2026-10-07), and the same number is spent above the content. (c) Fixed
     takes its position from the viewport, not from a parent, so the wrapper
     trouble sticky had is gone: the heading itself is fixed on all five
     pages. The phone rule .ra-hero { padding: 0; } below was meant to take
     off ReporterAlly's opening padding, which used to place the heading. It
     does not: [data-tool="reporterally"] .ra-hero outranks it, so
     ReporterAlly keeps 32px of opening padding on the phone (measured at 358
     on 2026-10-07). It is kept: without it ReporterAlly's first painted
     content would sit 16px under the band, where four desks show 56
     (measured the same day).

     MEASURED at 358, his own phone, on 2026-09-13: the band was 46px, the
     writing area on FieldReportage's Notes place went from 448px to 402px,
     and it was 402px at the top of the page as well as at the bottom, which
     is the difference he asked for. Since 2026-10-07 the band is 71px for a
     one-line headline and 93px for two (the declared --phone-hero-h, above),
     so those writing-area figures are from the 46px band and were not
     measured again. box_tools_align_check.mjs measures the band standing on
     the masthead's foot AND the first content below it clearing the band, at
     rest and scrolled, on every phone width. */
  h1.display {
    position: fixed;
    top: var(--phone-mast-h);
    left: 0;
    right: 0;
    z-index: 29;
    /* The foot bar's ground and its hairline rule, full bleed, so the edge the
       page scrolls under is drawn (2026-10-07; the :root:has(h1.display) note
       above). Nothing moves on scroll. The desktop hero has neither. */
    background: var(--paper-warm);
    border-bottom: var(--hairline) solid var(--rule);
    /* The column, the same way the masthead and the pinned action bar find it. */
    /* 2rem above the words and var(--s-3) below them, down to the rule; the
       page below adds 16 more, so its flow starts 16px under the rule. Until
       2026-10-07 it was 8px below the words and no rule, 24px from the words
       to the page. The 32px above is the spacing the page opened
       on before the band was fixed. Ritu, 2026-09-13, on the first fixed version:
       "I see the blueprint becomes the story, too close to the masthead rule.
       The original placement was perfect."

       2rem, not the clamp(2rem, 5vw, 3.5rem) the page itself opens on: that
       expression resolves to exactly 2rem at every phone width up to 640px and
       then starts to grow, and the band's height is a number the page below
       spends as padding. A band that grows is a band the page stops clearing.

       It is the same 32px on all seven desks, which is his standing rule for
       hero spacing: equal across every tool, ruled 2026-09-06 when it cost
       FieldReportage 47px and he chose to pay it. */
    padding: 2rem calc((100% - var(--frame)) / 2) var(--s-3);
    line-height: 22px;
    margin: 0;
    /* The height is DECLARED, not left to the type. The Scroll Desk's title
       carries an inline-block highlight on one word, and that box made the line
       taller than the line-height: the band came out 47.7px against the 46 the
       page below had been pushed down by, and the standfirst sat 1.7px too high.
       A band whose height a page depends on cannot be a font metric. */
    box-sizing: border-box;
    height: var(--phone-hero-h);
  }
  /* The room the band no longer takes in the flow, given back to the page.
     var(--s-3), 16px, of air under the band's rule. */
  /* [data-tool] .main as well as .main, and it is not decoration: four tools
     set their own padding on [data-tool="x"] .main, which outranks a bare
     .main, and the first version of this rule lost to them silently. This sits
     later in the sheet at the same specificity, so it wins on order. */
  .main, [data-tool] .main { padding-top: calc(var(--phone-hero-h) + var(--s-3)); }
  /* THE PAGE ENDS WHERE ITS CONTENT ENDS (2026-10-06, the owner's "Yes").
     His phone showed a sliver of the Raw copy box under the headline band on
     DeskCopy Pro's Draft, scrolled to the end during a run. Measured at 358 in
     app mode: Draft scrolled 235px past its content. 104px of that was this
     padding, the desktop's room under the page, which the phone layer never
     reset. The room the page owes the bars at its foot is the body's
     --phone-foot-h (below), measured from the bars themselves, so this
     padding was a second allowance on top of it. Same selector pair and the
     same reason as the padding-top rule above. The desktop keeps its 104px. */
  .main, [data-tool] .main { padding-bottom: 0; }
  /* The footer's 64px top margin (4rem) is the desktop's gap, and it was the
     second allowance in the 235px. On a phone the footer stands var(--s-5),
     40px, under the content: the agreed step, one below the desktop's on the
     spacing scale. The desktop keeps its 64px. */
  .tool-foot { margin-top: var(--s-5); }
  /* ReporterAlly opened its hero with clamp(2rem, 5vw, 3.5rem) of padding and
     that padding placed the heading. With the heading fixed it would be 32px of
     nothing. This rule does not win: [data-tool="reporterally"] .ra-hero sets
     the padding with a higher specificity, so on the phone the 32px stays
     (measured at 358 on 2026-10-07; see the h1.display note above for why it
     is kept). */
  .ra-hero { padding: 0; }
  /* .masthead-brand .wordmark img, not .masthead-brand img. Ritu, 2026-09-12:
     "the masthead space in the tools is vertically cramped, unless of course
     it's because the URL is taking up space. Correct me if I am wrong."

     He is right and it is not the URL bar. This rule has been LOSING since it
     was written: the base sheet carries .masthead-brand .wordmark img { height:
     clamp(36px, 4vw, 48px) }, three selectors against this one's two, so every
     phone drew the mark at 36px in a row that is 44px tall. MEASURED before:
     8px above the mark and 10px below it, inside a 54px bar. After: the mark
     sits at the 22px this block always meant, which leaves 15px above and 17px
     below IN THE SAME 54px BAR. The masthead does not grow, so nothing is taken
     off the writing area; the air was already there and the mark was eating it. */
  /* 40px, and the bar grows to 60 to carry it. Ritu, 2026-09-12, holding up a
     photograph of the editorial landing masthead on his own phone: "The
     wordmark on an app should be at least this size", after "the wordmark on
     the landing page and tools are too tiny" and "Do apps have such tiny
     identities?"

     MEASURED what he photographed, on live prod through Access at 360: the
     landing mark renders 36px in a 77px bar. That is the floor, in his words,
     not the target.

     MEASURED the ceiling at 360, the tightest width, by growing the mark and
     watching the story control beside it:

       mark 30px -> brand column  83px, story control 92px, title fits
       mark 36px -> brand column  99px, story control 92px, title fits
       mark 40px -> brand column 110px, story control 92px, title fits
       mark 46px -> brand column 127px, story control 80px, TITLE TRUNCATES

     So 40 is the largest mark this bar carries without cutting the story name,
     and 46 is not available here however much the landing page can take. 40
     clears his 36px floor and matches the landing, which comes down to 40 in
     the same pass so the two are one number and cannot drift.

     The bar goes 54 -> 60px, which is the one place the app spends screen on
     this. At 40px in the old 54px bar the mark left 7px above and below and
     read as a sticker crammed into the chrome. The row carries it instead. */
  .masthead-brand .wordmark img { height: 40px; width: auto; }
  /* The growth goes on the BAR, not on the brand box. A min-height on
     .masthead-brand is overridden three rules below by the min-height that
     matches the brand box to the 44px control cluster, and defeating that rule
     would put the two children back on two different tops, which is the
     two-row read it exists to fix. A min-height here grows the row and both
     children stay centred in it: measured 10px above the mark and 10 below,
     against 6/8 when the mark was forced into the old 54px bar. */
  .tool-masthead { min-height: 60px; }
  /* Two problems, one rule. First: the logo box (36px, from its own clamp)
     and the control cluster (44px, the touch floor) are different heights,
     so centring the two on .tool-masthead's align-items: center put them
     at two different top offsets, four pixels apart, which read as two
     rows even though both sit on the same visual line; min-height matches
     the two boxes so they share one top. Second: the base rule (line 555)
     sets min-width: 0 on this box, which is what let it wrap cleanly on a
     wider row before. In a nowrap row that is also a licence to shrink:
     flex-shrink squeezed the box below its own logo's width and the logo
     rendered past its own right edge, overlapping the utility cluster.
     flex-shrink: 0 holds it at full size; the brand mark is not the part
     of this row that should give ground. */
  .masthead-brand { min-height: var(--touch-min); flex-shrink: 0; }
  /* Two problems, one rule, again. First: the base rule (line 595) sets
     flex-wrap: wrap on this cluster, for a wider phone where it can afford
     a second row. Measured at 360px: left as wrap, the cluster's own
     children folded onto two lines inside it, which grew its box taller
     than the wordmark beside it and, under the parent's align-items:
     center, put the two top-level children at two different heights, once
     more reading as two rows; nowrap holds this cluster to the one row the
     rule above already secured for the OUTER row. Second: flex: none held
     it to its own content width, which on FieldReportage/DeskCopyPro/
     IntroDoctor ran wider than the space left beside the wordmark:
     measured, the settings button landed at 329..373px inside a 360px
     viewport, 13px off screen and untappable. flex: 1 1 auto with
     min-width: 0 gives this cluster only the room that is actually left
     and lets it shrink into that room instead of forcing the row wider
     than the phone. */
  .masthead-utility { margin-left: auto; display: flex; align-items: center;
    flex-wrap: nowrap; gap: var(--s-1); flex: 1 1 auto; min-width: 0; justify-content: flex-end; }
  .masthead-utility .suite-mark { display: none; }
  /* The story control's own byline repeats the tool name a second time
     (".ac-tool", after its separator dot), the same redundancy the ruling
     above already removes once for .suite-mark: the name is in the page's
     h1 and in the browser tab. */
  #allcap-mount .ac-sep, #allcap-mount .ac-tool { display: none; }
  /* Ritu, 2026-09-11, on the built screens: a lone amber dot with nothing
     beside it reads as a stray mark, not as something to tap. It was hidden
     here for a real reason at the time: with the 179px Day/Night/Press
     toggle still in this row, the title's own box was narrower than one
     character, and a single sliver of a glyph rendered on top of the
     wordmark, reading as a fault rather than a truncated word. The toggle
     came off this row two commits later (the theme-mount rule below), and
     the room it freed was never handed back to the title.

     MEASURED with the toggle already gone: at 360px the byline (dot only)
     sat at the 44px floor inside a masthead using 320 of its 360px, with
     128.8px still unclaimed inside .masthead-utility. Un-hiding the title
     and giving it the longest headline in the approved mockups ("Bridge
     falls at Birbhum, cutting off nine villages") grew the byline to
     174.7px at 360, 191px at 390 and at 412, and the masthead held one row
     at all three widths with no overflow. The desktop rule above
     (.masthead-utility #allcap-mount .ac-title) already caps the title at
     22ch and truncates it with an ellipsis, never a wrap, so nothing
     further is needed here for that; .ac-title's own overflow: hidden
     (allcap-tool.js) is also what lets it shrink below its text's
     content width instead of forcing the row wider. */
  #allcap-mount { flex-shrink: 1; min-width: 0; overflow: hidden; }
  #allcap-mount .ac-byline { min-width: 0; }
  /* MEASURED, 2026-09-12, FieldReportage at 390: the byline renders 127.66px
     wide, its margin box is 125.74px, and #allcap-mount takes its auto width
     from the margin box. The mount therefore comes out 1.92px narrower than
     the child it holds and, with overflow: hidden on the line above, clips
     it: the gate reads 128px of content in a 126px box on every page whose
     story control carries a title.

     The 1.92px is the desktop rule .masthead-utility #allcap-mount .ac-byline
     { margin-inline-end: calc(var(--track-caps) * -1) }, one --track-caps at
     the byline's own 12px font size. That rule is NOT cancelling a trailing
     letter-space after the title, which is the reading this fix was first
     written from and which measurement disproves: the byline's last visible
     child is .ac-saved, a zero-width flash span, and the 8px gap before it
     already separates the title from the byline's edge. The margin is a plain
     outer pull on whatever follows the byline in the row. The same 1.92px
     under-count exists on the desktop and always has; it is invisible there
     only because the desktop mount does not clip.

     At phone width the thing that follows the byline is the settings button,
     and the row already declares its own gap. The pull is taking 1.92px of a
     declared gap and costing the title its last two pixels, so it comes off
     here and nowhere else. Measured after: mount 128px of content in a 128px
     box on all five pages at 360, 390 and 412, and the gap from byline to
     settings button reads 4px, which is var(--s-1), instead of 2.09px.
     The prefix repeats the desktop rule's own .masthead-utility: the bare
     #allcap-mount form used two lines above is one class short of it and
     would lose. */
  .masthead-utility #allcap-mount .ac-byline { margin-inline-end: 0; }
  /* The byline is a flex row of dot, title, and .ac-saved, the span that
     flashes the word "saved" after a save and is EMPTY the rest of the time.
     Empty or not, it is a flex item, so the row's 8px gap is spent in front of
     it: 8px of dead space between the title and the byline's own right edge,
     every moment except the 1.5 seconds after a save.

     That 8px is what the story title needed. MEASURED at 360, the tightest
     width, once the Day/Night button took its 44px in this row: the byline had
     124.77px and wanted 127, so .ac-title showed 105px of text in a 103px box
     and truncated to an ellipsis. Reclaiming the gap leaves the byline wanting
     119 and the title whole, with room over.

     :empty is exact rather than approximate here: allcap-tool.js writes
     "saved" into this span to flash it and writes '' back 1.5s later, so the
     selector stops matching for exactly as long as there is something to show.
     Phone width only. The same dead gap sits in the desktop row, where there
     is room for it and where changing it is not this task's to do. */
  #allcap-mount .ac-byline .ac-saved:empty { display: none; }
  /* The story title is set in sentence case (text-transform: none) and was
     inheriting --track-caps from #allcap-mount, the tracking this sheet
     declares at line 99 as "for text the sheet also sets in uppercase".
     Caps tracking on lowercase words is a typographic fault on its own terms,
     and it is expensive: MEASURED, "Untitled story" is 106px tracked and
     78.8px untracked, so the wrong tracking was costing this row 27.2px.

     Giving it back is what lets the masthead sit on the content column above
     without the title truncating: at 360, the tightest width, the mount now
     holds 93px for a title that wants 79. Nothing truncates at 360, 390 or
     412 on any of the seven pages that carry a story control.

     Phone width only. The same tracking sits on the desktop title, where
     there is room for it and where changing how the masthead reads is a
     design call, not this task's. It is written down rather than quietly
     carried over. */
  .masthead-utility #allcap-mount .ac-title { letter-spacing: normal; }
  .masthead-utility .theme-mount, .masthead-utility .settings-toggle { flex-shrink: 0; }
  /* One button, not three, and shaped as the settings gear beside it so the
     two read as a pair rather than as a control and a leftover. The 44px floor
     and the pill radius both come from rules this sheet already declares
     (.theme-mount button below, .theme-toggle above); this only centres the
     mark and takes off the segment border a single segment does not need.
     MEASURED at 360, the tightest width: story control 127px, this 44px, the
     gear 44px and two 4px gaps come to 223px in the 220.77px the utility row
     has, and #allcap-mount gives up the 2.2px from its own flex-shrink, so the
     title truncates by two pixels rather than the row overflowing. */
  /* The capsule gives up its border and its floor to the single button, which
     then IS the control: a 44px bordered circle with a teal mark, drawn by the
     same declarations as the settings gear beside it, so the two read as a
     pair. Leaving the floor on the capsule the way the three-segment pill does
     would put the border outside the button and stand the control at 46px,
     which is the exact defect the note on .theme-toggle records.

     The .masthead-utility prefix is required, not decorative: the rule at
     line 2862 in this file is .masthead-utility .theme-toggle button and sets
     min-width: 0 and its own padding. A bare .theme-toggle--swap button is one
     class short of it and loses, which is how this first rendered 28px wide
     with a 12px mark. font-size is --t-2 for the same reason the gear uses it:
     .ico is sized in em, so the button's font size IS the mark's size. */
  .theme-toggle--swap { border: 0; min-height: 0; }
  .masthead-utility .theme-toggle--swap button {
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0;
    border: 1px solid var(--rule);
    border-radius: var(--pill);
    min-width: var(--touch-min);
    min-height: var(--touch-min);
    font-size: var(--t-2);
  }
  .settings-toggle .btn-label { position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .settings-toggle { min-width: var(--touch-min); min-height: var(--touch-min);
    justify-content: center; padding: 0; }

  /* Every control a thumb hits clears the floor the product already
     declares. The theme buttons were 42px and the main actions 33.
     These are minimums, not fixed heights: a control with more to say
     stays as tall as its content needs. .btn-export and .style-pass-btn
     were dropped from this list on 2026-09-10: neither matches a single
     served page (the export button carries .copy-btn, the style-pass
     button carries .fidelity-btn already), and a selector that matches
     nothing only looks like a guard.
     .copy-btn dropped the same day, on a hit-tested trial rather than a
     grep: its rule was removed, the gate re-run, and it stayed CLEAN.
     Measured why: DeskCopyPro's plain .copy-btn buttons (Copy, Export)
     are already 44px tall on their own box, no rule needed; its
     .hist-btn pair (Prev, Next) and IntroDoctor's .revise-btn measure
     0x0 (collapsed, no active history or revision to show) and are
     excluded by this gate's own visibility check regardless of any
     rule. A selector that matches something but changes nothing it is
     tested against is the same defect as one that matches nothing. */
  .btn-submit, .btn-go, .btn-copy, .btn-clear,
  .fidelity-btn, .settings-toggle, .theme-mount button,
  .mode-switch button, .harvest-header, select, summary {
    min-height: var(--touch-min);
  }
  .theme-mount button { min-width: var(--touch-min); }
  /* An action is full width on a phone, so the label has room and the
     target is unmissable. */
  .action-bar .btn-submit, .action-bar .btn-go { width: 100%; }
  /* IntroDoctor and DeskCopyPro name their primary action .run-btn, not
     .btn-submit or .btn-go; measured at 213x33 and 96x33, under the floor
     for the same reason .copy-btn was, EXCEPT .copy-btn's own live
     controls turned out not to need this (see the dropped-from-the-list
     note above): .run-btn genuinely does. Confirmed load-bearing on a
     walked hit-test that measures the true edge on each side (the near
     side inclusive, the far side exclusive, matching how a plain DOMRect
     itself works, verified directly on a 44px test box before trusting
     it): on runBtn the walk reaches the full 22px from centre on the near
     side and 21.5px on the far side, 43.5px measured, half a pixel under
     the floor. A synthetic, unobstructed copy of the same ::before
     pattern measures exactly 44px, so this is not a defect in the
     pattern itself; something specific to .run-btn's real layout (a
     hidden .clear-btn sibling gives .action-row a 44px row height that
     coincides suspiciously closely with the shortfall, though the exact
     mechanism is not fully traced) costs it the last half pixel. This
     rule only fires below --phone-max, so the desktop underline
     treatment is untouched. */
  .run-btn { min-height: var(--touch-min); }
  /* Ritu, 2026-09-11, on the built screens: DeskCopy Pro's Rewrite sits as a
     quiet underlined word beside FieldReportage's Build blueprint, a solid
     filled button, though both are the same kind of action: the tool's one
     way to run. The typeset CTA is the desktop voice this file names in its
     own opening line ("cream paper, rule-lines, typeset CTA, nib masthead")
     and it is not changed here; only below --phone-max does the main action
     take the weight FieldReportage's own [data-tool="fieldreportage"]
     .btn-go already carries. Values only, read from that rule rather than
     invented: the fill and its ink, the pill this product already draws
     every other boxed control in, and the same transition. .run-btn is the
     shared class for the primary action on DeskTriage, DeskCopy, DeskCopy
     Pro, DeskPop and IntroDoctor, so this one rule reaches all five;
     IntroDoctor's Diagnose & Generate carries the identical quiet-word
     defect and is answered by the same rule, not a second one.

     NAMED, NOT ACCIDENTAL: preview/build-precision.mjs --apply-type
     generates `@media (max-width: 620px) { .run-btn { font-size: var(--t-4);
     } }` further up this file (620px is inside 699.98px's range, same
     specificity, and that block is appended by a script rather than
     hand-deleted here, since a script-owned block that gets hand-deleted
     comes back the next --apply-type run). This rule sits later in source,
     so below 620px it wins over the generated one: --t-1 (12px) is what
     actually renders at 360 and at 390, not the generated --t-4 (19px).
     Deliberate: matching .btn-go's own type scale is the point of this
     rule, and .btn-go is 12px too. The 620px band's .run-btn rule is dead
     by this rule's design, not by where it happened to land in the file. */
  .run-btn {
    font-variation-settings: "wght" 700;
    font-size: var(--t-1);
    letter-spacing: var(--track-caps);
    text-transform: uppercase;
    line-height: normal;
    padding: var(--s-2) var(--s-4);
    border: none;
    border-radius: var(--pill);
    background: var(--cta-fill);
    color: var(--cta-ink);
    --ico-color: currentColor;
    transition: background .15s ease, transform var(--press-out) var(--press-ease);
  }
  @media (hover: hover) {
    .run-btn:hover:not(:disabled) {
      background: var(--cta-fill);
      color: var(--cta-ink);
      --ico-color: currentColor;
    }
  }
  .run-btn:focus-visible:not(:disabled) {
    background: var(--cta-fill);
    color: var(--cta-ink);
    --ico-color: currentColor;
  }
  .run-btn:disabled {
    background: var(--paper-deep);
    color: var(--quiet);
    cursor: not-allowed;
  }

  /* The Day/Night/Press toggle does not fit beside the wordmark, the story
     control and Settings in the same row. Measured at 360px: the utility
     cluster needs 221px, of which the theme toggle alone is 179px, and the
     full masthead needs 362px in a 360px viewport, 2px short before a
     single word of the story title is drawn. min-width on .theme-mount
     button (above) is a floor, not a cap, so it cannot recover that width;
     nothing else in this file can either. This used to hide the toggle
     here (fix round 1, Task 7 ruling 1: STATE named the rule as "the one
     deleted rule that undoes the whole decision"). It no longer needs
     hiding: phone-places.js moves the toggle into Desk, beside the
     settings drawer, and gives it back to this row when the phone layer
     comes off, so all three buttons stay, present and reachable, only
     standing somewhere else. Its styling once inside Desk is further down
     this block, beside the drawer's own reset. */

  /* With the title hidden above, the story control's own box shrank to
     the width of its dot alone, 14px, a decorative size standing in for a
     touch target. The dot still opens the drawer, so the box under it
     keeps the floor even though the dot painted inside stays small. Not
     pseudo-covered on any side (checked: no ::before/::after on
     .ac-byline anywhere in this file), and confirmed load-bearing: removed
     under the hit-tested gate (2026-09-10 fix round), the gate correctly
     reports it, on the axis it is actually short on (width, not height;
     see tap_target_check.mjs's per-axis probing). The box is 44px wide now
     but its only visible content, the 6px dot, sat flush left inside it,
     so most of the target was unmarked; centred, what a user aims at is
     the middle of what they hit. */
  #allcap-mount .ac-byline { min-width: var(--touch-min); justify-content: center; }

  /* The panel's own stylesheet picker resets .style-sheet-picker's floor
     back to auto (see the panel-cell rule earlier in this file, which
     scopes that reset to the panel on purpose so other surfaces keep the
     floor). On a phone the picker is still a control a thumb must hit, so
     the floor returns here, inside the panel's own scope, matching that
     rule's specificity and winning on source order. Confirmed load-bearing
     under the hit-tested gate (2026-09-10 fix round): removed, the gate
     correctly reports it; not pseudo-covered (native <select> elements do
     not render ::before/::after in this stylesheet or by default). */
  .panel-cell .style-sheet-picker { min-height: var(--touch-min); }

  /* Task 6, 2026-09-10: three standing places, one on screen at a time.
     A place that is not the current one is off the screen, not deleted; its
     inputs keep their values and their scroll positions, because place-off
     is only ever display:none on the elements Task 5 marked. */
  .place-off { display: none !important; }
  /* A CONTAINER WHOSE PLACES ARE ALL OFF TAKES NO ROOM (2026-10-02). On
     FieldReportage and ReporterAlly the Story place left .panel-input in the
     .main grid at zero height, and a zero-height grid item still takes a
     24px gap; on DeskCopy Pro, DeskCopy, DeskPop and IntroDoctor the Story
     place left .tool-config showing its 40px margin and both 1px borders
     with nothing between them. Measured at 358 from the headline's foot to
     the first thing in the place: Story stood 24px (64 against 40) lower than
     Notes and Desk on FieldReportage, 24px on ReporterAlly, and 41px (98
     against 57) on DeskCopy Pro, DeskCopy and DeskPop. IntroDoctor's Story
     place showed nothing before output, over the same empty .tool-config.
     Measured after: 40, 48.3 and 56. The rule asks only what the phone layer
     has done: a child of .main that holds at least one place, none of them on
     screen, is off too. It never matches above the breakpoint, where nothing
     is .place-off. */
  .main > :has(> .place-off):not(:has(> [data-place]:not(.place-off))) { display: none; }
  /* var(--s-6), 64px: the nearest step on the spacing scale to the bar's own
     62px min-height (preview/build-precision.mjs --apply-space owns this
     mapping; 2px of slack under the bar is harmless, less would not be). */
  /* THE PAGE ENDS ABOVE THE FURNITURE, NOT BEHIND IT.
     Ritu, 2026-09-15, with a photograph of FieldReportage scrolled as far as
     it goes: "At the last point till which one can scroll, look at TEACH
     playing peekaboo."

     MEASURED at 358x650 at the page's own maximum scroll: the footer runs
     419.3 to 545.5 and the pinned action bar starts at 459, so 86.5px of the
     footer is behind the furniture and no further scrolling reveals it. The
     Teach link itself spans 444.3 to 468.3 and loses 9.3px of its 24, which is
     why it reads as a row of half-letters rather than as something hidden.

     The two numbers that were being compared were never the same quantity.
     This padding was set to the TAB BAR's height, 62px rounded to the scale's
     64, and on Notes and Copy to var(--s-7), 104px, for the action bar as well.
     The furniture on Notes at 358 is 191px tall: a 129px action bar standing on
     a 62px tab row. The 104 was measured against the PLACE's own content
     clearing the bar, which it does, and the footer sits below the places.

     So the room is taken from the furniture itself now, not from a step chosen
     to match it. assets/js/phone-places.js writes --phone-foot-h from the
     rendered height of the tab bar plus whichever pinned bar is standing on it,
     and re-writes it whenever either changes size. The values below are what
     the page uses until that runs, and they are the values it used before, so
     a page whose script fails is no worse off than it was. */
  body.has-phone-tabs {
    --phone-foot-h: var(--s-6);
    padding-bottom: calc(var(--phone-foot-h) + var(--s-2));
  }
  .phone-tabs {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    display: flex; background: var(--paper-warm);
    border-top: var(--hairline) solid var(--rule);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .phone-tabs button {
    flex: 1; min-height: 62px; border: 0; background: none;
    display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: var(--s-1);
    font-family: var(--font-display); font-size-adjust: var(--x-adjust);
    font-size: var(--t-1);
    letter-spacing: var(--track-caps); text-transform: uppercase; font-weight: 500;
    color: var(--quiet); cursor: pointer; position: relative;
  }
  .phone-tabs button[aria-current="page"] { color: var(--btn-ink); }
  /* The selector is attribute-only, not tag-plus-attribute, so it reads as
     a decorative indicator rather than a button to the two design-drift
     sweeps (test_no_square_boxed_buttons, test_every_filled_control_
     declares_its_mark_colour): both key off the literal word "button" in a
     selector, and this bar is not the tap target, the button underneath it
     is (the rule above). border-radius: var(--pill), not a literal 0-led
     value, for the same reason on the square-box sweep specifically. */
  .phone-tabs [aria-current="page"]::before {
    content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
    width: 34px; height: 3px; background: var(--btn-ink); border-radius: var(--pill);
  }
  .phone-tabs button:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

  /* Task 8, 2026-09-10: An action that scrolls away is an action you hunt
     for. The bar sits above the tabs and belongs to the place it acts on:
     Notes carries the two that produce a piece, Story carries the revision
     and the export.

     :not([data-place]) SCOPES THIS TO THE UNDECORATED BAR (FieldReportage's
     and ReporterAlly's), on a measured finding. DeskCopy Pro's own
     .action-bar (#dcpActionBar, Copy/Export/history, plus the bulletise
     button allcap-bulletise.js injects into '.action-bar') already carries
     data-place="story" and is already shown and hidden correctly by
     phone-places.js's own place switching, proven CLEAN before this task.
     Run without the exclusion, place_switch_check.mjs reported btnCopy,
     btnExport and the bulletise button UNREACHABLE at 390: the class name
     is shared by two unrelated bars, and the generic display:none for
     "story" and "desk" hid DeskCopy Pro's bar in the one place, Story, it
     is meant to be seen. Confirmed by reverting the exclusion and re-running
     the gate, then restoring it. */
  /* Task 14, 2026-09-11: the inline padding is the page's own gutter, not a
     spacing step. Ritu, on the built screens: "The alignment of the buttons
     is all over the place." Measured at 390 on FieldReportage's Notes with
     var(--s-3) here: Build Blueprint and Write the piece stood at 16..374
     while everything above them on the same screen, the heading, Test the
     idea and the harvest list, stood on the page column at 24.4..365.6, so
     the two biggest buttons on the screen were the two that did not line up
     with it. calc((100% - var(--frame)) / 2) is what .tool-masthead already
     uses to put brand chrome on the content column (see its own note around
     line 500), read from that rule rather than invented, and it tracks the
     column at every width instead of agreeing with it at one. The bar is
     fixed and spans left: 0 to right: 0, so its 100% is the viewport's
     width, which is the same 100% .main resolves --frame against. */
  body.has-phone-tabs .action-bar:not([data-place]) {
    position: fixed; left: 0; right: 0; bottom: 62px; z-index: 39;
    margin: 0; padding: var(--s-3) calc((100% - var(--frame)) / 2);
    background: var(--paper-warm);
    border-top: var(--hairline) solid var(--rule);
  }
  /* flex: 1 1 100% is what makes the bar's own width reach the buttons.
     .action-left is a shrink-to-fit flex item, and the width: 100% the rule
     above gives .btn-submit and .btn-go resolves against IT, not against
     the bar. Measured on ReporterAlly at 390, whose bar holds one action:
     Refine rendered 109.7px wide and sat alone at the left of a 390px bar
     with 248px of empty bar beside it, because .action-left was 109.7px
     wide. FieldReportage did not show this, and that is the trap rather
     than a reason to leave it: its own [data-tool] rule happens to let the
     cluster fill the row, so the same defect in the same shared markup was
     visible on one tool and invisible on the other. */
  /* TWO ACROSS, NOT THREE DOWN, AND IT IS HIS OWN INSTRUCTION.
     Ritu, 2026-09-12: "Test the idea should be put back next to Build
     Blueprint, and Write the Piece at the bottom." NEXT TO. The button came
     back to this bar and then stacked above Build Blueprint with it, which is
     half of what he asked for.

     It is also what was eating the screen. Measured on FieldReportage at
     390x664, which is a phone with Chrome's address bar showing: three 44px
     buttons with two gaps and the bar's own padding make a 165px bar, pinned
     above a 63px tab row, so 227px of a 664px viewport is furniture. The
     harvest ran to 509 and the bar started at 437, so the last section was
     entirely behind it and Surprises & Gaps was cut through the middle. That
     is what he photographed: "Gaps isn't even visible unless you scroll down,
     in which case even the hero disappears."

     A grid rather than a wrapping flex row, because the buttons carry
     width: 100% from their own rule and a flex row obeys it by giving each
     one a line of its own. The grid decides the columns and the buttons fill
     the cells they are given.

     One button stays full width (ReporterAlly's Refine is alone in its bar),
     two go side by side, and a third spans the row underneath, which is
     exactly the shape he described. The status line spans too, so it is never
     squeezed into half a row. */
  body.has-phone-tabs .action-bar:not([data-place]) .action-left {
    display: grid; grid-template-columns: 1fr; gap: var(--s-2); align-items: center;
    flex: 1 1 100%;
  }
  /* minmax(0, 1fr), not 1fr. A grid track's default minimum is min-content, so
     1fr 1fr gave the two buttons 158px and 186px: the wider label simply
     refused to shrink and the row read as two different buttons rather than a
     pair. minmax(0, ...) lets both halves be halves. */
  body.has-phone-tabs .action-bar:not([data-place]) .action-left:has(> button:nth-of-type(2)) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  body.has-phone-tabs .action-bar:not([data-place]) .action-left > button:nth-of-type(3),
  body.has-phone-tabs .action-bar:not([data-place]) .action-left > .status-bar {
    grid-column: 1 / -1;
  }
  /* The marks stay. They were dropped here on 2026-09-12 so the pair would fit
     at 360, and that was my call to make and not mine to make. Ritu,
     2026-09-13: "you dropped the logos from Test the Idea and Build Blueprint
     without asking me, just so they would fit. Always a bad idea to compromise
     on design aesthetics." The fit is bought from the tracking and the padding
     below instead, which are quantities, not parts of the design. */

  /* THE TRACKING GIVES, NOT THE PILL.
     Ritu, 2026-09-13, on the paired row: "Build Blueprint is tight around the
     margins", and then "But don't enlarge the pills."

     So the space comes from inside. MEASURED at 360: the pill is 152px and the
     label 144, which is 4px of air each side. The label is set in capitals at
     --track-caps, 0.16em, which on 12px type over fifteen characters is 29px of
     the 144 spent on letter-spacing alone. It is the right value for a button
     that has a row to itself and too generous for one sharing a row.

     0.08em, half the standard, on the paired buttons only. Measured after:
     360 gives 11px each side, 390 gives 18, 412 gives 24, and the pill is the
     size it was. Capitals still need tracking to be read as capitals, which is
     why this is halved rather than dropped. */
  body.has-phone-tabs .action-bar:not([data-place]) .action-left:has(> button:nth-of-type(2)) > button:nth-of-type(1) .btn-label,
  body.has-phone-tabs .action-bar:not([data-place]) .action-left:has(> button:nth-of-type(2)) > button:nth-of-type(2) .btn-label {
    letter-spacing: 0.06em;
  }
  /* And the side padding comes down with it, on the pair only. A pill that
     shares a row with another pill cannot also carry the side padding of one
     that has the row to itself. This is what buys the mark its place back. */
  body.has-phone-tabs .action-bar:not([data-place]) .action-left:has(> button:nth-of-type(2)) > button:nth-of-type(1),
  body.has-phone-tabs .action-bar:not([data-place]) .action-left:has(> button:nth-of-type(2)) > button:nth-of-type(2) {
    padding-inline: var(--s-2);
  }
  body.has-phone-tabs[data-phone-place="story"] .action-bar:not([data-place]),
  body.has-phone-tabs[data-phone-place="desk"] .action-bar:not([data-place]) { display: none; }

  /* 16px above and below the buttons, not 8. Ritu, 2026-09-13, on the pinned
     Rewrite bar: "so tight at the bottom". */
  /* THE BUTTONS STAY IN REACH. The Notes exception that let this bar scroll
     with the page is gone. Ritu, 2026-09-13, on his own phone: "At first
     glance, can't even see the three buttons" and "That's not how apps are
     meant to be." His Chrome shows pages at about 115%, so the unpinned bar
     sat below the tab bar at rest. The notes scroll under the bar instead. */
  /* var(--s-7), 104px, not the brief's literal 134px: build-precision.mjs
     --apply-space snaps any spacing declaration off the seven-step scale to
     its nearest step, and 134px is off it (rewrote to var(--s-7) itself
     when tried), which the fixed-point test in test_css_architecture.py
     (HairlineTokenTest) fails on until the sheet matches what the
     generator would already produce. MEASURED before choosing it: at 390,
     with every collapsible section open (the realistic full place, not the
     collapsed default), FieldReportage's Notes clears the pinned bar by
     209px and ReporterAlly's by 244px at 134px; var(--s-7) only asks for
     30px less clearance than either margin has to give, and the same walk
     with var(--s-7) in place still clears on every place of every tool
     (script run recorded in the task report). */
  body.has-phone-tabs[data-phone-place="notes"],
  body.has-phone-tabs[data-phone-place="copy"] { --phone-foot-h: var(--s-7); }

  /* THE HERO'S AIR PAYS THE REST OF THE BILL.
     After the action bar went to two columns the harvest still ended 20px
     behind it at 390x664. Measured from the masthead down: the bar ends at 60,
     the hero's own line is 24px tall, and the first section starts at 156. That
     is 72px of air around one line of type, on a screen where the last section
     is being cut in half for want of 20.

     Desktop keeps every pixel of it. This is the phone, where the whole point
     of the hero is that it names the tool once before the work starts. */
  /* THE HERO'S AIR IS NOT AVAILABLE. It was trimmed here on 2026-09-12 to buy
     clearance for the harvest, first on every phone place and then on Notes
     alone. Ritu, 2026-09-13: "The tightness is because they are placed too
     close to the masthead. It's a placement issue." He is right, and the air
     goes back on every place. What the harvest needs is bought from the button
     bar and the row gaps, which are quantities; the space under a masthead is
     placement. */

  /* And the last ten pixels come from between the rows. At 360x640, the
     smallest screen the gates measure, the harvest still ended 10px behind the
     bar after the two changes above. Six collapsed rows carry five 8px gaps
     between them; at 4px the list still reads as separate rows and the whole
     harvest clears. Measured after: 360x640 clears by 10px, 390x664 by 32,
     412x700 by 86.

     A screen shorter than about 620px still scrolls, and that is honest: six
     sections, a hero and 175px of pinned furniture do not fit in 600px, and
     the fix for that is not a fourth shaving. */
  body.has-phone-tabs .harvest-section { margin-bottom: var(--s-1); }

  /* .phone-lead-action lived here and is gone. It styled Test the idea as a
     full-width lead action at the top of Notes, which was my placement, not
     his. Ritu, 2026-09-12: "Test the idea should be put back next to Build
     Blueprint, and Write the Piece at the bottom." The button takes the
     action bar's own rules again, like the two it stands with. */

  /* Task 7, 2026-09-10: phone-places.js moves the settings drawer into the
     Desk place at phone width. Left with its desktop rules, it would still
     paint as a fixed, centred overlay with its own backdrop; these reset it
     to read as ordinary content inside Desk, and hide the backdrop it no
     longer needs. Above the breakpoint the drawer never moves, so its
     desktop rules are untouched. */
  /* Task 18, 2026-09-12: max-height and overflow-y were the two the reset
     above missed, and missing them is why the drawer had an inner scrollbar
     inside the text column and ran under the tab bar. The base rule gives
     the drawer max-height: 85vh and overflow-y: auto because a centred
     overlay must not grow past the screen. Inside Desk it is not an overlay,
     it is ordinary content in a page that already scrolls, so the cap made
     a scrolling box inside a scrolling page: a reader who reached the foot
     of the page still had a second, hidden scroll to find.

     MEASURED at the page's own maximum scroll, before this line: Save
     settings and Reset to defaults sat ENTIRELY behind the tab bar on
     FieldReportage, IntroDoctor and ReporterAlly at 360, 390 and 412, along
     with IntroDoctor's setMonthAbbrev and setCustomRules and ReporterAlly's
     context line and its dictate button. 37 findings in all, none of them
     reachable by scrolling, all of them from this one pair of properties.
     scripts/foot_clearance_check.mjs is the gate that found them. */
  /* z-index: auto, and it is the third property this reset missed rather than
     a new idea. Ritu, 2026-09-12, with a photograph of the open drawer: "Can
     you see the settings panel is transparent, and you can see the word mark
     behind it? It's a mess."

     The transparency is deliberate and stays: inside Desk this is ordinary
     content in the page, not a card floating over one, so it must not draw its
     own ground. What was wrong is what the ground was showing THROUGH: the
     drawer was painting on top of the masthead and on top of the tab bar, so
     three layers of text sat in the same 150 pixels.

     The base rule (line 2593) gives the drawer z-index: 999 because as a
     centred overlay it must clear everything. A z-index is inert on a static
     element, so resetting position to static looked like enough. It is not,
     and this is the trap: THE PANEL IS A FLEX ITEM once it is moved into Desk,
     and z-index applies to a flex item with no positioning at all. A dormant
     999 woke up the moment phone-places.js moved the drawer, and it outranks
     the masthead's 30 and the tab bar's.

     Measured with the drawer open at 390: background rgba(0,0,0,0), position
     static, z-index 999, against a masthead at z-index 30. */
  [data-place="desk"] .settings-panel {
    position: static; transform: none; width: auto; max-width: none;
    height: auto; max-height: none; overflow: visible;
    box-shadow: none; border: 0; padding: 0;
    background: transparent; visibility: visible; opacity: 1;
    z-index: auto;
  }
  /* !important: FieldReportage and ReporterAlly both carry a tool-scoped
     [data-tool="X"] .settings-backdrop.visible { display: block; } rule
     (two classes plus the tool attribute, higher specificity than a bare
     .settings-backdrop selector can reach in source order alone), so a
     plain override here loses the moment the drawer opens and .visible is
     added. Measured directly: without !important, opening the drawer at
     phone width painted the backdrop back in at full opacity, dimming the
     whole page, including Desk's own other controls, behind glass. .place-
     off (above) already uses the same escape hatch for the same reason:
     a phone-width rule that must win regardless of what desktop specificity
     it is up against. */
  [data-place="desk"] .settings-backdrop, .settings-backdrop { display: none !important; }

  /* WHILE THE DRAWER IS OPEN IT IS THE ONLY THING ON DESK.
     Ritu, 2026-09-14, from three rendered options, on his own phone report
     that the settings "only part of it closes".

     They closed. Every time, on all three tools, MEASURED with two real taps
     at 358. What survived the close was Desk's own permanent content, which
     was never part of the settings and looks exactly like it: MODE and WAY
     IN on FieldReportage, STANDFIRST, SUBHEADS and STYLESHEET on DeskCopy
     Pro. Same type, same size, same ground, no boundary between them.

     The rule above is what makes them indistinguishable, and it is right:
     inside Desk the drawer is ordinary content in the page, not a card
     floating over one, so it must not draw its own ground. Rather than give
     the drawer a frame it should not have, Desk shows one kind of control at
     a time. That is the principle this sheet already applies to the harvest
     box below: an open section is a screen, not a drawer inside a list.

     Two selectors, because a place is a GROUP and not one element
     (phone-places.js says so at the top, and DeskCopy Pro's Story is five
     siblings). The first hides every sibling of the drawer inside the
     element it was moved into; the second hides any other element of the
     same place, which holds no drawer to keep. :has() is what tells the two
     apart, and Android Chrome has carried it since 105.

     !important for the same reason the backdrop rule above needs it: the
     tool-scoped [data-tool="X"] rules outrank a bare selector here. */
  body.phone-settings-open [data-place="desk"] > *:not(.settings-panel) { display: none !important; }
  body.phone-settings-open [data-place="desk"]:not(:has(.settings-panel)) { display: none !important; }

  /* AND NEITHER MUST THE PLACE IT SITS IN. Ritu, 2026-09-18, on a photograph of
     DeskCopy Pro's open settings: the sentences are "almost sticking to the
     borders", and "it is across the tools".

     MEASURED at 358 and at 412, drawer open: every heading, label and
     paragraph in it stands 0px from the tinted edge, left, right and top, on
     DeskCopy Pro, DeskPop and IntroDoctor. Not tight. Zero. FieldReportage and
     ReporterAlly drop the drawer into .fr-mode, which paints nothing, so there
     is no edge for the text to touch and neither shows the fault. Off the
     phone the drawer is still the centred card and still gives var(--s-5).

     The tint is the tool's own left rail, .tool-panel. It carries no padding
     because on desktop every child it has is a .panel-cell holding its own
     var(--s-3), and the drawer, reset to padding 0 by the rule above, is the
     one child that does not. So the ground is being drawn by accident: the
     rail is still painting for the controls the two rules above have just
     hidden.

     TAKING THE GROUND AWAY, not insetting the text, and the difference was
     rendered before it was chosen. Insetting the drawer by var(--s-3) takes
     32px off every dropdown and adds a fifth line to the subtitle, and it
     pushes the drawer off the column the tool's own headline stands on.
     Without the ground the drawer keeps the full column and reads as what the
     comment above already says it is: ordinary content in the page. The rail's
     ground comes back the moment the drawer closes, with the controls it is
     there for. */
  body.phone-settings-open [data-place="desk"]:has(> .settings-panel) {
    background: transparent;
  }

  /* The Day/Night toggle used to be placed here, at the foot of Desk, and on
     three pages at the foot of the settings drawer. Both placements are gone.
     Ritu, 2026-09-12, on a photograph of his own phone showing it cut across
     by the tab bar: "DAY/NIGHT should be either on the masthead or inside the
     footer, certainly not in the middle. Basic design discipline." It is in
     the masthead at every width now, as one 44px button rather than three,
     which byline-theme.js builds; the rule that shapes it sits with the other
     masthead rules above. Nothing places it here any more. */

  /* Task 9, 2026-09-10: An open section is a screen, not a drawer inside a
     list. The keyboard takes roughly half a phone, so a textarea inside a
     scrolling list leaves about four lines to write in. This box renders at
     its full 34vh in ordinary page flow, not inside a clipped, internally
     scrolling frame: .harvest-scroll declares overflow-y: auto but nothing
     up its ancestor chain (.panel-input, .main, body) gives it a bounded
     height, so that declaration never has anything to clip and the page
     itself grows and scrolls instead. What keeps this taller box clear of
     the fixed tab bar and action bar at the foot is the page-bottom
     padding below (var(--s-7), body.has-phone-tabs[data-phone-place=
     "notes"]), which holds a constant gap regardless of how tall the
     content above it gets, not any scrolling region absorbing the growth. */
  [data-place="notes"] .harvest-section.open .harvest-body textarea {
    min-height: 34vh;
  }
  [data-place="notes"] .harvest-header {
    min-height: var(--touch-min); align-items: center;
  }
  [data-place="notes"] .harvest-hint { display: block; }

  /* Task 14, 2026-09-11: alignment and balance, measured by
     scripts/phone_balance_check.mjs. Every rule below answers a finding
     that gate reported, with the page, the place and the pixel gap in the
     task report; none was written from reading the stylesheet.

     THE LIST GIVES BACK ITS SCROLLBAR GUTTER. padding-right: 4px on
     .harvest-scroll and .output-scroll (the [data-tool] rules around lines
     3452, 3796 and 4509) reserves room beside a scrollbar for a region
     that scrolls inside itself. Task 9 already recorded that the phone
     layer gives that region no bounded height, so it never scrolls inside
     itself and never draws that scrollbar; the gutter is left taking 4px
     from one side only. Measured at 390 on FieldReportage's Notes: the
     whole list, Test the idea and all six sections, stood at 24.4..361.6
     while the heading above it and the footer below it stood at
     24.4..365.6. The same 4px sits on the Story side of both reportage
     tools. Nothing is hidden by removing it: the 6px webkit scrollbar
     rules beside it stay, and above the breakpoint this rule does not
     apply, so the desktop region keeps its gutter. */
  .harvest-scroll[data-place="notes"],
  [data-place="story"] .output-scroll {
    padding-right: 0;
  }

  /* THE TOOL'S PRIMARY ACTION HOLDS THE COLUMN. Ritu, 2026-09-11, on the
     built screens: DeskCopy Pro's Rewrite sat 136px wide at the left of a
     341px column, 205px short of the edge the two disclosure triggers
     directly above it hold, with the footer's rule running out of its
     right-hand side so it read as stuck onto a rule rather than placed on
     the page. .action-row is a wrapping flex ROW, so its one visible child
     took its own content width and its invisible .clear-btn sibling sat
     beside it; as a COLUMN with stretch it gives both the full column,
     which is the shape the sheet already declares three rules above ("An
     action is full width on a phone, so the label has room and the target
     is unmissable"). IntroDoctor's Diagnose & Generate carries the
     identical defect (245px in the same 341px column) and is answered by
     the same rule, as is DeskCopy Pro's Revise inside its feedback
     disclosure. The gap between the two is the row's own, unchanged.
     Both declarations are load-bearing and were each removed and
     re-measured: without flex-direction the button stays 136px, and
     without align-items it centres at 102.5px from either edge.

     SCOPE WIDENED 2026-09-12, on Ritu's word: "get to the V3 button
     straight away." It was [data-place] .action-row, which reached the four
     tools with places and deliberately left DeskCopy V3, DeskTriage and
     DeskPop standing with the same defect, on the honest ground that no
     gate then measured them and nothing could prove the result. Both halves
     of that ground are gone: phone_balance_check reports all three, and the
     result is measured below.

     MEASURED at 390 before and after, on every page in this sheet's reach:
     DeskCopy V3's Rewrite 25..161 and DeskPop's 25..161 become 25..365;
     DeskTriage's Diagnose 25..171 becomes 25..365; the same column their own
     neighbours already hold. DeskCopy Pro and IntroDoctor were already
     column and did not move. FieldReportage and ReporterAlly show no action
     row at this width and did not move.

     THE EXCLUSION IS THE POINT OF THE NEW SELECTOR. scroll-desk.html loads
     this sheet alongside social-dark.css and carries an .action-row of its
     own; unscoped, the old note records that this rule pulled its platform
     pills out to the full column. It is a social surface, which this product
     keeps apart from the editorial system on purpose, and body[data-theme=
     "social"] is the page's own declaration of that, not a name this rule
     invents. Measured with the new scope in place: scroll-desk's row is
     untouched, still a row, its three controls exactly where they were. */
  body:not([data-theme="social"]) .action-row {
    flex-direction: column;
    align-items: stretch;
  }

  /* TWO CHECKS STACKED IN A CELL SHARE AN EDGE. .panel-cell > .fidelity-bar
     (line 932) already stacks them on a phone-narrow rail, with
     align-items: flex-start, so each pill took the width of its own words:
     measured on IntroDoctor's Desk at 390, Fact check ends at 171.5 and
     Style pass at 166.7, 4.88px apart and directly under one another,
     which is the near miss that reads worse than either agreement or a
     clear difference. Stretch gives both the cell's column, the same
     column the stylesheet picker above them already runs. Scoped to the
     panel inside Desk: DeskCopy Pro's Story carries a .fidelity-bar too,
     it is a ROW there with the two pills side by side, and align-items on
     a row is the cross axis, which is not this. */
  [data-place="desk"] .panel-cell .fidelity-bar {
    align-items: stretch;
  }

  /* Fix round 1, 2026-09-11: Teach's card action holds its card's column.
     Reported once finding 5 was widened from labelled buttons to every
     control, which is what let it reach a page with no places on it:
     inside .feed the textarea holds 40.4..349.6 and Add to memory held
     40.4..239.3, 80.81px short at 360, 110.00px at 390 and 131.61px at
     412. The same shape as DeskCopy Pro's Rewrite, on the same screen as
     the box it acts on. Scoped to .feed, which is the card the gate
     reported: index.html carries eight other .cta elements and loads this
     sheet, and none of them was measured here. justify-content as well as
     width, and for the reason .phone-lead-action used to record before it
     was removed on 2026-09-12: widening a shrink-to-fit button turns it
     into a full-width box while its contents stay at flex-start, so the
     words sit 24px in from the left of a box they used to fill, and three
     buttons of one width with their words in two different places is what
     "the alignment of the buttons is all over the place" looks like at its
     plainest. */
  .feed .cta {
    width: 100%;
    justify-content: center;
  }

  /* THE RUN BUTTON STAYS IN REACH on the Copy place, the same way the action
     bar does on Notes. Ritu, 2026-09-13: "DeskCopy Pro is the same. Can't see
     the rewrite button without scrolling." Reaches DeskCopy Pro's Rewrite and
     IntroDoctor's Diagnose & Generate, the two .run-btn rows inside a Copy
     place. Clear shows beside it only once there is something to clear. */
  body.has-phone-tabs[data-phone-place="copy"] [data-place="copy"] .action-row:has(> .run-btn) {
    position: fixed; left: 0; right: 0; bottom: 62px; z-index: 39;
    margin: 0; padding: var(--s-3) calc((100% - var(--frame)) / 2);
    background: var(--paper-warm);
    border-top: var(--hairline) solid var(--rule);
    flex-direction: row; flex-wrap: wrap; gap: var(--s-2);
  }
  body.has-phone-tabs[data-phone-place="copy"] [data-place="copy"] .action-row:has(> .run-btn) > .run-btn { flex: 1 1 0; }
  body.has-phone-tabs[data-phone-place="copy"] [data-place="copy"] .action-row:has(> .run-btn) > .clear-btn[style*="hidden"] { display: none; }
  body.has-phone-tabs[data-phone-place="copy"] [data-place="copy"] .action-row:has(> .run-btn) > .status-bar { flex-basis: 100%; }
  /* COMPARE on the phone (2026-10-01): full screen, one text at a time. */
  .cmp-sheet { inset: 0; border: 0; }
  .cmp-head { padding: var(--s-3) 16px var(--s-2); }
  .cmp-summary { font-size: var(--t-2); }
  .cmp-switch { display: flex; gap: 8px; padding: var(--s-2) 16px; border-bottom: 1px solid var(--rule-soft); }
  .cmp-switch button { flex: 1; }
  .cmp-switch button[aria-pressed="true"] { border-color: var(--ink); background: var(--paper-deep); }
  .cmp-panes { grid-template-columns: 1fr; }
  .cmp-pane { padding: 0 16px var(--s-4); }
  .cmp-pane + .cmp-pane { border-left: 0; }
  .cmp-pane-head { display: none; }
  .cmp-pane p { font-size: 16px; }

  /* The story's name, in the Story place, keeps the ink on hover. The
     injected #allcap-mount .ac-byline:hover .ac-title turns it amber; the
     desktop's masthead and header rules answer it the same way. Outside the
     ICON ROW copies because a :hover rule needs its own (hover: hover). */
  @media (hover: hover) {
    .story-name-line > #allcap-mount .ac-byline:hover .ac-title { color: var(--ink); }
  }

  /* THE SYNC NOTICE ON A PHONE (2026-10-04, as approved in mockups/49 at
     358): a sheet along the bottom, in thumb reach, as the Copy place's
     action bar is. Rows stack, and each pill takes the full width. */
  .sync-notice {
    left: 0; right: 0; top: auto; bottom: 0; transform: none; width: auto;
    max-height: calc(100% - 48px);
    border: 0; border-top: var(--hairline) solid var(--rule);
    padding: var(--s-4) 16px calc(var(--s-3) + env(safe-area-inset-bottom, 0px));
  }
  .sync-head { font-size: var(--t-4); }
  .sync-row { flex-direction: column; align-items: stretch; gap: var(--s-2); }
  .sync-pill { width: 100%; }
  .sync-actions { flex-direction: column; }

  /* ICON ROW (2026-10-01). Every desk action is an icon over a small word
     wherever the phone layout applies. Ritu: "the icons in teal and the words or
     the labels in blue or black", "I meant navy." The desktop keeps its full
     buttons and app.html is untouched.
     The grid is equal cells, so the six actions read as two rows: Fact check,
     Style pass and Bulletise, then Compare, Copy and Export. A child of the bar that is
     not one of the actions (the history pair, a check's status line, the
     Bulletise undo pill) takes a row of its own under them, so nothing is
     squeezed into a quarter of the width. A status line with nothing to say
     takes no row at all. IntroDoctor's two checks sit in a Desk cell that is
     narrower than the page: a quarter of it is 61px at 320 and "Fact check"
     needs 62.4 with its padding, so there the grid has two columns.
     The padding, not the centring, sets the height: 8 + 20 + 4 + 12 + 8 is
     52px, the min-height exactly. FieldReportage's Copy is shown by script
     with an inline display: inline-block, which no class rule outranks, and a
     block button does not centre its content. With the padding doing the work, both kinds
     of button put the mark and the word in the same place.
     The word is 11px, the size approved for the row, one pixel under the
     smallest step of the type scale; it is written against --t-1 so that it
     moves if the scale does. The tracking is normal, as for every word in
     sentence case (preview/map.json, note_tracking).
     The checks are named through their bar or header for weight, not for
     scope: icons.css loads after this sheet and its button:has(> .ico) sets
     gap: 0.5em, which a bare .fidelity-btn loses to. Bulletise is named the
     same way because in the app it sits in the checks bar, after Style pass
     (allcap-bulletise.js, followTheApp; Ritu, 2026-10-02, "the app only").
     A button marked .copied keeps the green "Copied" signal the desktop gives
     it: the mark and the word take --good, one class above the rules that
     colour them teal and navy.
     FieldReportage and ReporterAlly keep their buttons in two places, the
     output panel's header (Fact check, Copy, Clear) and its foot (Follow up,
     Copy all, Export). Ritu, 2026-10-02: "Pills mixed with the new icons.
     There should be uniformity with the rewriting tools." So every one of the
     six is a cell, and each row keeps the rewriters' 4px between cells. The
     rows stay flex, not grid: the foot is shown by script with an inline
     display: flex, which no class rule outranks. A cell a quarter of the row
     wide, with the 4px gap, lands on the same x as the rewriters' grid. The
     ids carry the specificity the two tools' own rules would otherwise win
     with: [data-tool] .panel-output .panel-header .btn-copy sets margin-left:
     auto and .btn-clear sets margin-right and a 1.5px border.
     FieldReportage's header also holds the story name's mount, which
     allcap-tool.js leaves empty until a piece lands; its injected style still
     draws the old story bar's 1px bottom rule, so an empty mount painted a
     short line under the row. Empty, it takes no space. Holding the name, it
     shows as before.
     The history pair (Prev, Next) keeps its own row under the actions, as a
     grid of the same four cells: Prev in the first, Next in the second, the
     count after them. Next's markup puts its word before its mark, so the
     mark is ordered first. On DeskCopy and DeskPop the pair carries only
     .copy-btn, not .hist-btn, so they are named as the group's buttons.
     The same rules are repeated for the installed app and for html.panel
     below this block. */
  .action-bar[data-place], .fidelity-bar:has(> .fidelity-btn) {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-flow: row dense;
    gap: var(--s-1); align-items: start;
  }
  .action-bar[data-place] > :not(button), .action-bar[data-place] > :is(.bulletise-undo-pill, .style-undo-pill), .fidelity-bar:has(> .fidelity-btn) > :not(button) { grid-column: 1 / -1; justify-self: start; }
  .fidelity-bar:has(> .fidelity-btn) > span:empty { display: none; }
  .panel-cell > .fidelity-bar:has(> .fidelity-btn) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .action-bar[data-place] > .hist-group:not([hidden]) {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s-1); align-items: center; justify-self: stretch;
  }
  .hist-group > #histPrev { grid-area: 1 / 1; }
  .hist-group > #histNext { grid-area: 1 / 2; }
  .hist-group > #histCount { grid-area: 1 / 3; justify-self: start; }
  .hist-group > #histNext .ico { order: -1; }
  .panel-output .panel-header:has(> .btn-clear), .panel-output #outputActions { column-gap: var(--s-1); }
  .panel-output .panel-header > #allcap-mount:empty { display: none; }
  .panel-header .fidelity-btn, .panel-output .panel-header #btnCopy, .panel-output .panel-header #clearBtn, .panel-output #outputActions > button { flex: 0 0 calc((100% - 3 * var(--s-1)) / 4); }
  .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill), .action-bar > .hist-group > button, :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn), .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport, .panel-output .panel-header #btnCopy, .panel-output .panel-header #clearBtn, .panel-output #outputActions > button {
    flex-direction: column; align-items: center; justify-content: center; gap: 0;
    min-width: 0; min-height: 52px; margin: 0; padding: var(--s-2) 2px;
    border: 0; border-radius: var(--pill); background: none; text-align: center;
  }
  .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill) .ico, .action-bar > .hist-group > button .ico, :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn) .ico, .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport .ico, .panel-output .panel-header #btnCopy .ico, .panel-output .panel-header #clearBtn .ico, .panel-output #outputActions > button .ico {
    display: block; width: 20px; height: 20px; margin: 0 auto var(--s-1); line-height: 0; color: var(--accent);
  }
  .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill) .ico svg, .action-bar > .hist-group > button .ico svg, :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn) .ico svg, .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport .ico svg, .panel-output .panel-header #btnCopy .ico svg, .panel-output .panel-header #clearBtn .ico svg, .panel-output #outputActions > button .ico svg { display: block; width: 20px; height: 20px; }
  .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill) .btn-label, .action-bar > .hist-group > button .btn-label, :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn) .btn-label, .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport .btn-label, .panel-output .panel-header #btnCopy .btn-label, .panel-output .panel-header #clearBtn .btn-label, .panel-output #outputActions > button .btn-label {
    display: block; font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: calc(var(--t-1) - 1px); font-weight: 500;
    letter-spacing: normal; text-transform: none; line-height: calc(var(--s-1) * 3); white-space: nowrap; color: var(--btn-ink);
  }
  .action-bar > button.copied:not(.bulletise-undo-pill, .style-undo-pill) .ico, .action-bar > button.copied:not(.bulletise-undo-pill, .style-undo-pill) .btn-label, .panel-output .panel-header #btnCopy.copied .ico, .panel-output .panel-header #btnCopy.copied .btn-label, .panel-output #outputActions > button.copied .ico, .panel-output #outputActions > button.copied .btn-label { color: var(--good); }
  /* THE STORY SCREEN, OPTION 2 (2026-10-02). Ritu, on FieldReportage's Story
     place: "Looking clumsy and muddled, the entire layout", "Untitled story
     shouldn't appear like that", "The placement is so awkward". Of two
     pictures he chose Option 2, every action in one block below the story
     (mockups/47-story-layout-2026-10-02/2-actions-below-358.png).
     assets/js/story-actions.js moves the nodes; these rules dress them. The
     header keeps its heading and loses its rule. The story's name sits on the
     story as its title, in the display face, with a chevron that says a tap
     opens the story list; the dot, the separator and the tool name are the
     masthead's furniture and do not come along. #outputActions becomes one
     row of four: Fact check, Follow up, Copy and Export (Clear moved to the
     name line in Task 5j, below). Copy
     all runs the same copyOutput() as Copy and is not in the picture, so it
     does not appear here; the desktop keeps it. The block is shown by the
     tool with an empty inline display, so the grid applies; before a piece
     lands it is display: none and nothing of this shows. */
  .panel-output > .panel-header { border-bottom: 0; padding-bottom: 0; }
  .panel-output #outputActions { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-1); align-items: start; }
  .panel-output #outputActions > .btn-copy:not([id]) { display: none; }
  .panel-output #outputActions > #exportMenu { grid-column: 1 / -1; }
  .story-name-line > #allcap-mount { display: flex; justify-content: flex-start; align-items: center; flex: 0 1 auto; min-width: 0; margin: 0; padding: 0; border-bottom: 0; overflow: visible; }
  .story-name-line > #allcap-mount:empty { display: none; }
  .story-name-line > #allcap-mount .ac-byline { gap: calc(var(--s-1) + 2px); min-width: 0; min-height: var(--touch-min); }
  .story-name-line > #allcap-mount :is(.ac-dot, .ac-sep, .ac-tool), .story-name-line > #allcap-mount .ac-saved:empty { display: none; }
  .story-name-line > #allcap-mount .ac-title {
    font-family: var(--font-display); font-style: normal; font-size: calc(var(--t-3) + 1px); font-weight: 600; color: var(--ink); letter-spacing: var(--track-tight);
    max-width: none; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .story-name-line > #allcap-mount .ac-byline::after {
    content: ''; flex: none; width: 16px; height: 16px; background: var(--accent);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
  }
  /* CLEAR ON THE NAME LINE (Task 5j, 2026-10-02). Ritu, on the live app: "I
     Moved to STORY to copy, I had to move back to draft to clear." Of three
     renders he chose "2": Clear at the right end of the story-name line
     (mockups/47-story-layout-2026-10-02/clear-B-name-line-second-run-358.png).
     story-actions.js wraps the name in .story-name-line and puts Clear after
     it: the Draft Clear's twin on the four editing desks, #clearBtn itself on
     the reportage pair. The line is one row with Clear centred on the title;
     the name shrinks first and its title ends in an ellipsis before it can
     reach Clear. Clear is a cell like the other actions: the teal mark over
     the navy word, 52px tall, never shrinking, pushed to the right end, so
     with no name beside it it still sits at the right. The reportage block
     loses Clear and is one row of four: Fact check, Follow up, Copy, Export
     (fix round 1; two half-empty rows read as the lone-button look Ritu
     called "weird"). On the reportage pair the line keeps Clear visible
     while it shows, that is while output exists, even when the reporter
     empties the notes and the tool hides its Clear inline; the tool's own
     visibility still governs the desktop, and before output the line is
     hidden, so Clear is too. The four editing desks do the same. */
  .story-name-line:not([hidden]) { display: flex; align-items: center; gap: var(--s-2); min-width: 0; margin: 0 0 var(--s-1); }
  .story-name-line > :is(#clearBtn, .story-clear) {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0;
    flex: none; width: var(--s-6); min-width: 0; min-height: 52px; margin: 0 0 0 auto; padding: var(--s-2) 2px;
    border: 0; border-radius: var(--pill); background: none; text-align: center; cursor: pointer;
  }
  .story-name-line > :is(#clearBtn, .story-clear) .ico { display: block; width: 20px; height: 20px; margin: 0 auto var(--s-1); line-height: 0; color: var(--accent); }
  .story-name-line > :is(#clearBtn, .story-clear) .ico svg { display: block; width: 20px; height: 20px; }
  .story-name-line > :is(#clearBtn, .story-clear) .btn-label {
    display: block; font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: calc(var(--t-1) - 1px); font-weight: 500;
    letter-spacing: normal; text-transform: none; line-height: calc(var(--s-1) * 3); white-space: nowrap; color: var(--btn-ink);
  }
  .story-name-line > #clearBtn { visibility: visible !important; }
  /* THE REWRITERS' STORY SCREEN, OPTION 2 (2026-10-02). Ritu: "It should be the
     same in the writing tools. This is the optimal and most visually pleasing
     layout for the app", and "This is how it should appear only when the output
     is generated". assets/js/story-actions.js moves the nodes once the output
     container is .visible: the name, the output, one block (the action bar,
     holding the checks), then the results bar and the lists. The name takes the
     place's opening space and shows only in the Story place; the output loses
     its top rule and space under the name. The block is the action bar, three
     columns as on the reportage pair, inside the output directly under the
     story text, the editor's notes after it (ruling, 2026-10-02); its rule
     sits on the two-pane grid's 24px margin and the cells 16px under it. The results sit 16px under it, and a
     results bar with nothing to say takes no space. Before the output the two
     bars do not show, so the place holds only what the tool shows there. A
     status brought into view by a press clears the tab bar.
     Task 5f. THE NAME TAKES NO MARGIN ABOVE IT. At 358 it sat 56px under the
     headline band: the page's own 16px under the band, then 40px with nothing
     in it. FieldReportage's section heading sits 25px under its rule, its text
     27px; with no margin the name's text sits 27.4px under the band. A SCROLL
     TO THE NAME (story-actions.js sends the tool's landing scroll there) stops
     under the pinned masthead and headline band, with the same 16px under the
     band, so a landing shows the name where the place's own top shows it; the
     two heights are 0 where they are not declared. THE WORKING LINE: while a
     run works, working-line.js holds its line directly in front of the
     output, and the name stays in front of the line; the output keeps no top
     rule, margin or padding either way. */
  .main > .story-name-line { scroll-margin-top: calc(var(--phone-mast-h, 0px) + var(--phone-hero-h, 0px) + var(--s-3)); }
  body[data-phone-place]:not([data-phone-place="story"]) .main > .story-name-line { display: none; }
  .main > .story-name-line + :is(#outputArea, #outputContainer), .main > .story-name-line + #workingLine + :is(#outputArea, #outputContainer) { margin-top: 0; border-top: 0; padding-top: 0; }
  .main:has(> :is(#outputArea, #outputContainer):not(.visible)) > :is(.fidelity-bar, .action-bar)[data-place="story"] { display: none; }
  :is(#outputArea, #outputContainer) > .action-bar[data-place] { margin-top: 0; padding-top: var(--s-3); }
  .action-bar[data-place] + .fidelity-bar { margin: var(--s-3) 0 0; }
  .action-bar[data-place] + .fidelity-bar > span:empty, .action-bar[data-place] + .fidelity-bar:not(:has(> :not(:empty))) { display: none; }
  :is(#fidelityStatus, #provStatus, #stylePassStatus) { scroll-margin-bottom: calc(var(--phone-foot-h, 0px) + var(--s-2)); }
  /* INTRODOCTOR'S CHECKS SIT IN STORY (Task 5h, 2026-10-02). Ritu, after
     seeing IntroDoctor at 358 with Fact check and Style pass in its Desk
     rail: "Introductory buttons should sit in the STORY tab, just as the
     other tools do." story-actions.js moves the checks bar whole into the
     output container once it is .visible: after the cards (and Export),
     before Revise, with the findings list under it. The bar is the block:
     the same three-column grid and cells as the other desks, under one
     rule, the action bar's hairline, its cells 16px under it, 24px under
     the cards. The Style pass undo pill is a full row, as on the rewriters.
     Fix round 1: "Export .txt" is the third cell (Ritu: "Pills mixed with
     the new icons. There should be uniformity"), named in the cell rules
     above. Every rule names the bar that came from the rail, which carries
     no data-place: DeskCopy and DeskPop also move their results bar into
     #outputContainer, and these rules must not reach it.
     The Checks cell in the rail takes no space in the app, before the
     output too, as on the other desks, where the checks show only once
     there is output; the Stylesheet cell stays, and from 620px to 699px,
     where the rail is two columns, it takes the whole row instead of half
     of it beside an empty half. The desktop keeps the rail with both
     checks. */
  .main > #outputContainer > .fidelity-bar:not([data-place]) { margin: var(--s-4) 0 0; padding-top: var(--s-3); border-top: var(--hairline) solid var(--rule-soft); }
  .main > #outputContainer > .fidelity-bar:not([data-place]) > .style-undo-pill { grid-column: 1 / -1; justify-self: start; }
  .tool-panel > .panel-cell:has(> .fidelity-bar), .tool-panel > .panel-cell:has(> .toggle-label:only-child) { display: none; }
  .tool-panel > .panel-cell:first-child:has(+ .panel-cell > .fidelity-bar), .tool-panel > .panel-cell:first-child:has(+ .panel-cell > .toggle-label:only-child) { grid-column: 1 / -1; }
  .main:has(> :is(#outputArea, #outputContainer):not(.visible)) > .output-empty[data-place="story"]:not(:has(+ #workingLine)) { display: block; }
  .main > .output-empty[data-place="story"] { text-align: center; padding: var(--s-5) var(--s-4); color: var(--ink-soft); }
  .main > .output-empty .empty-icon { font-family: var(--font-display); font-weight: 400; font-size: var(--t-7); color: var(--rule); margin-bottom: var(--s-3); line-height: calc(var(--s-1) * 10); }
  .main > .output-empty h3 { font-family: var(--font-display); font-weight: 400; font-style: italic; font-size: var(--t-5); line-height: calc(var(--s-1) * 8); letter-spacing: var(--track-tight); color: var(--ink); margin: 0 0 var(--s-2); }
  .main > .output-empty p { font-family: var(--font-body); font-size: 14px; line-height: calc(var(--s-1) * 6); color: var(--ink-soft); max-width: 400px; margin: 0 auto; text-wrap: balance; }
  /* THE STORY HEADLINE SITS BELOW THE HERO (Task 5k, 2026-10-03). Ritu, on the
     app at 358: "the story headline looks larger than the hero. That's wierd
     too. Headline can be light, no?", then "Even the typeface overshadows the
     hero." Shown four renders he chose "4 but tad bigger": the headline in the
     body face (Newsreader) at weight 500, line-height 1.25, a little larger
     than 19px. The size is derived from the hero's own phone rule,
     clamp(var(--t-4), 5.8vw, var(--t-6)), minus 1.25px, and never under 18px,
     so it stays 1 to 1.5px under the hero at every width from 320 to 600
     (19.5px at 358, hero 20.76; 18px at 320, hero 19). Over 600px (min-width: 600.01px, so 600.5 under page zoom is covered; the
   hero's phone rule is max-width: 600px; the file's own min/max idiom, as
   with 699.98px, because range syntax is dropped by Chrome before 104 and
   Safari before 16.4) the hero
     follows the 900 rule, so the band after the panel copy takes that clamp.
     The wght axis setting of the display face is reset so weight 500 means 500.
     Desktop: untouched, outside every gate. */
  .copy-headline, .rewrite-headline { font-family: var(--font-body); font-weight: 500; font-variation-settings: normal; font-optical-sizing: auto; letter-spacing: normal; line-height: 1.25; font-size: max(18px, calc(clamp(var(--t-4), 5.8vw, var(--t-6)) - 1.25px)); }
  .hb-chev { margin-top: -8px; margin-bottom: -8px; } /* the 26px mark sits on the line without enlarging its 1.25 line box (Task 5k fix) */
}

/* The rewriters' empty Story screen (Task 5g). It exists in the markup of
   DeskCopy Pro, DeskCopy and DeskPop and is hidden everywhere; the three ICON
   ROW copies show it, in the app only, while the output container has no
   .visible. This rule sits outside every gate so the desktop never shows it. */
.main > .output-empty[data-place="story"] { display: none; }

@media (display-mode: standalone) {
  /* ICON ROW, installed app. The same rules as the phone layer's ICON ROW
     (inside the phone layer above); the reasons are there. */
  .action-bar[data-place], .fidelity-bar:has(> .fidelity-btn) {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-flow: row dense;
    gap: var(--s-1); align-items: start;
  }
  .action-bar[data-place] > :not(button), .action-bar[data-place] > :is(.bulletise-undo-pill, .style-undo-pill), .fidelity-bar:has(> .fidelity-btn) > :not(button) { grid-column: 1 / -1; justify-self: start; }
  .fidelity-bar:has(> .fidelity-btn) > span:empty { display: none; }
  .panel-cell > .fidelity-bar:has(> .fidelity-btn) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .action-bar[data-place] > .hist-group:not([hidden]) {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s-1); align-items: center; justify-self: stretch;
  }
  .hist-group > #histPrev { grid-area: 1 / 1; }
  .hist-group > #histNext { grid-area: 1 / 2; }
  .hist-group > #histCount { grid-area: 1 / 3; justify-self: start; }
  .hist-group > #histNext .ico { order: -1; }
  .panel-output .panel-header:has(> .btn-clear), .panel-output #outputActions { column-gap: var(--s-1); }
  .panel-output .panel-header > #allcap-mount:empty { display: none; }
  .panel-header .fidelity-btn, .panel-output .panel-header #btnCopy, .panel-output .panel-header #clearBtn, .panel-output #outputActions > button { flex: 0 0 calc((100% - 3 * var(--s-1)) / 4); }
  .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill), .action-bar > .hist-group > button, :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn), .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport, .panel-output .panel-header #btnCopy, .panel-output .panel-header #clearBtn, .panel-output #outputActions > button {
    flex-direction: column; align-items: center; justify-content: center; gap: 0;
    min-width: 0; min-height: 52px; margin: 0; padding: var(--s-2) 2px;
    border: 0; border-radius: var(--pill); background: none; text-align: center;
  }
  .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill) .ico, .action-bar > .hist-group > button .ico, :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn) .ico, .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport .ico, .panel-output .panel-header #btnCopy .ico, .panel-output .panel-header #clearBtn .ico, .panel-output #outputActions > button .ico {
    display: block; width: 20px; height: 20px; margin: 0 auto var(--s-1); line-height: 0; color: var(--accent);
  }
  .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill) .ico svg, .action-bar > .hist-group > button .ico svg, :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn) .ico svg, .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport .ico svg, .panel-output .panel-header #btnCopy .ico svg, .panel-output .panel-header #clearBtn .ico svg, .panel-output #outputActions > button .ico svg { display: block; width: 20px; height: 20px; }
  .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill) .btn-label, .action-bar > .hist-group > button .btn-label, :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn) .btn-label, .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport .btn-label, .panel-output .panel-header #btnCopy .btn-label, .panel-output .panel-header #clearBtn .btn-label, .panel-output #outputActions > button .btn-label {
    display: block; font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: calc(var(--t-1) - 1px); font-weight: 500;
    letter-spacing: normal; text-transform: none; line-height: calc(var(--s-1) * 3); white-space: nowrap; color: var(--btn-ink);
  }
  .action-bar > button.copied:not(.bulletise-undo-pill, .style-undo-pill) .ico, .action-bar > button.copied:not(.bulletise-undo-pill, .style-undo-pill) .btn-label, .panel-output .panel-header #btnCopy.copied .ico, .panel-output .panel-header #btnCopy.copied .btn-label, .panel-output #outputActions > button.copied .ico, .panel-output #outputActions > button.copied .btn-label { color: var(--good); }
  .panel-output > .panel-header { border-bottom: 0; padding-bottom: 0; }
  .panel-output #outputActions { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-1); align-items: start; }
  .panel-output #outputActions > .btn-copy:not([id]) { display: none; }
  .panel-output #outputActions > #exportMenu { grid-column: 1 / -1; }
  .story-name-line > #allcap-mount { display: flex; justify-content: flex-start; align-items: center; flex: 0 1 auto; min-width: 0; margin: 0; padding: 0; border-bottom: 0; overflow: visible; }
  .story-name-line > #allcap-mount:empty { display: none; }
  .story-name-line > #allcap-mount .ac-byline { gap: calc(var(--s-1) + 2px); min-width: 0; min-height: var(--touch-min); }
  .story-name-line > #allcap-mount :is(.ac-dot, .ac-sep, .ac-tool), .story-name-line > #allcap-mount .ac-saved:empty { display: none; }
  .story-name-line > #allcap-mount .ac-title {
    font-family: var(--font-display); font-style: normal; font-size: calc(var(--t-3) + 1px); font-weight: 600; color: var(--ink); letter-spacing: var(--track-tight);
    max-width: none; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .story-name-line > #allcap-mount .ac-byline::after {
    content: ''; flex: none; width: 16px; height: 16px; background: var(--accent);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
  }
  .story-name-line:not([hidden]) { display: flex; align-items: center; gap: var(--s-2); min-width: 0; margin: 0 0 var(--s-1); }
  .story-name-line > :is(#clearBtn, .story-clear) {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0;
    flex: none; width: var(--s-6); min-width: 0; min-height: 52px; margin: 0 0 0 auto; padding: var(--s-2) 2px;
    border: 0; border-radius: var(--pill); background: none; text-align: center; cursor: pointer;
  }
  .story-name-line > :is(#clearBtn, .story-clear) .ico { display: block; width: 20px; height: 20px; margin: 0 auto var(--s-1); line-height: 0; color: var(--accent); }
  .story-name-line > :is(#clearBtn, .story-clear) .ico svg { display: block; width: 20px; height: 20px; }
  .story-name-line > :is(#clearBtn, .story-clear) .btn-label {
    display: block; font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: calc(var(--t-1) - 1px); font-weight: 500;
    letter-spacing: normal; text-transform: none; line-height: calc(var(--s-1) * 3); white-space: nowrap; color: var(--btn-ink);
  }
  .story-name-line > #clearBtn { visibility: visible !important; }
  .main > .story-name-line { scroll-margin-top: calc(var(--phone-mast-h, 0px) + var(--phone-hero-h, 0px) + var(--s-3)); }
  body[data-phone-place]:not([data-phone-place="story"]) .main > .story-name-line { display: none; }
  .main > .story-name-line + :is(#outputArea, #outputContainer), .main > .story-name-line + #workingLine + :is(#outputArea, #outputContainer) { margin-top: 0; border-top: 0; padding-top: 0; }
  .main:has(> :is(#outputArea, #outputContainer):not(.visible)) > :is(.fidelity-bar, .action-bar)[data-place="story"] { display: none; }
  :is(#outputArea, #outputContainer) > .action-bar[data-place] { margin-top: 0; padding-top: var(--s-3); }
  .action-bar[data-place] + .fidelity-bar { margin: var(--s-3) 0 0; }
  .action-bar[data-place] + .fidelity-bar > span:empty, .action-bar[data-place] + .fidelity-bar:not(:has(> :not(:empty))) { display: none; }
  :is(#fidelityStatus, #provStatus, #stylePassStatus) { scroll-margin-bottom: calc(var(--phone-foot-h, 0px) + var(--s-2)); }
  .main > #outputContainer > .fidelity-bar:not([data-place]) { margin: var(--s-4) 0 0; padding-top: var(--s-3); border-top: var(--hairline) solid var(--rule-soft); }
  .main > #outputContainer > .fidelity-bar:not([data-place]) > .style-undo-pill { grid-column: 1 / -1; justify-self: start; }
  .tool-panel > .panel-cell:has(> .fidelity-bar), .tool-panel > .panel-cell:has(> .toggle-label:only-child) { display: none; }
  .tool-panel > .panel-cell:first-child:has(+ .panel-cell > .fidelity-bar), .tool-panel > .panel-cell:first-child:has(+ .panel-cell > .toggle-label:only-child) { grid-column: 1 / -1; }
  .main:has(> :is(#outputArea, #outputContainer):not(.visible)) > .output-empty[data-place="story"]:not(:has(+ #workingLine)) { display: block; }
  .main > .output-empty[data-place="story"] { text-align: center; padding: var(--s-5) var(--s-4); color: var(--ink-soft); }
  .main > .output-empty .empty-icon { font-family: var(--font-display); font-weight: 400; font-size: var(--t-7); color: var(--rule); margin-bottom: var(--s-3); line-height: calc(var(--s-1) * 10); }
  .main > .output-empty h3 { font-family: var(--font-display); font-weight: 400; font-style: italic; font-size: var(--t-5); line-height: calc(var(--s-1) * 8); letter-spacing: var(--track-tight); color: var(--ink); margin: 0 0 var(--s-2); }
  .main > .output-empty p { font-family: var(--font-body); font-size: 14px; line-height: calc(var(--s-1) * 6); color: var(--ink-soft); max-width: 400px; margin: 0 auto; text-wrap: balance; }
  /* THE STORY HEADLINE SITS BELOW THE HERO (Task 5k, 2026-10-03). Ritu, on the
     app at 358: "the story headline looks larger than the hero. That's wierd
     too. Headline can be light, no?", then "Even the typeface overshadows the
     hero." Shown four renders he chose "4 but tad bigger": the headline in the
     body face (Newsreader) at weight 500, line-height 1.25, a little larger
     than 19px. The size is derived from the hero's own phone rule,
     clamp(var(--t-4), 5.8vw, var(--t-6)), minus 1.25px, and never under 18px,
     so it stays 1 to 1.5px under the hero at every width from 320 to 600
     (19.5px at 358, hero 20.76; 18px at 320, hero 19). Over 600px (min-width: 600.01px, so 600.5 under page zoom is covered; the
   hero's phone rule is max-width: 600px; the file's own min/max idiom, as
   with 699.98px, because range syntax is dropped by Chrome before 104 and
   Safari before 16.4) the hero
     follows the 900 rule, so the band after the panel copy takes that clamp.
     The wght axis setting of the display face is reset so weight 500 means 500.
     Desktop: untouched, outside every gate. */
  .copy-headline, .rewrite-headline { font-family: var(--font-body); font-weight: 500; font-variation-settings: normal; font-optical-sizing: auto; letter-spacing: normal; line-height: 1.25; font-size: max(18px, calc(clamp(var(--t-4), 5.8vw, var(--t-6)) - 1.25px)); }
  .hb-chev { margin-top: -8px; margin-bottom: -8px; } /* the 26px mark sits on the line without enlarging its 1.25 line box (Task 5k fix) */
}
/* The story name's hover, for the installed app and the extension panel; the
   phone layer's copy and its reason sit above the phone ICON ROW. */
@media (display-mode: standalone) and (hover: hover) {
  .story-name-line > #allcap-mount .ac-byline:hover .ac-title { color: var(--ink); }
}
@media (hover: hover) {
  html.panel .story-name-line > #allcap-mount .ac-byline:hover .ac-title { color: var(--ink); }
}
/* ICON ROW, extension panel. The same rules again, under html.panel, which
   assets/js/panel-bridge.js sets when the page runs inside the extension. */
html.panel .action-bar[data-place], html.panel .fidelity-bar:has(> .fidelity-btn) {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-flow: row dense;
  gap: var(--s-1); align-items: start;
}
html.panel .action-bar[data-place] > :not(button), html.panel .action-bar[data-place] > :is(.bulletise-undo-pill, .style-undo-pill), html.panel .fidelity-bar:has(> .fidelity-btn) > :not(button) { grid-column: 1 / -1; justify-self: start; }
html.panel .fidelity-bar:has(> .fidelity-btn) > span:empty { display: none; }
html.panel .panel-cell > .fidelity-bar:has(> .fidelity-btn) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
html.panel .action-bar[data-place] > .hist-group:not([hidden]) {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-1); align-items: center; justify-self: stretch;
}
html.panel .hist-group > #histPrev { grid-area: 1 / 1; }
html.panel .hist-group > #histNext { grid-area: 1 / 2; }
html.panel .hist-group > #histCount { grid-area: 1 / 3; justify-self: start; }
html.panel .hist-group > #histNext .ico { order: -1; }
html.panel .panel-output .panel-header:has(> .btn-clear), html.panel .panel-output #outputActions { column-gap: var(--s-1); }
html.panel .panel-output .panel-header > #allcap-mount:empty { display: none; }
html.panel .panel-header .fidelity-btn, html.panel .panel-output .panel-header #btnCopy, html.panel .panel-output .panel-header #clearBtn, html.panel .panel-output #outputActions > button { flex: 0 0 calc((100% - 3 * var(--s-1)) / 4); }
html.panel .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill), html.panel .action-bar > .hist-group > button, html.panel :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn), html.panel .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport, html.panel .panel-output .panel-header #btnCopy, html.panel .panel-output .panel-header #clearBtn, html.panel .panel-output #outputActions > button {
  flex-direction: column; align-items: center; justify-content: center; gap: 0;
  min-width: 0; min-height: 52px; margin: 0; padding: var(--s-2) 2px;
  border: 0; border-radius: var(--pill); background: none; text-align: center;
}
html.panel .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill) .ico, html.panel .action-bar > .hist-group > button .ico, html.panel :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn) .ico, html.panel .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport .ico, html.panel .panel-output .panel-header #btnCopy .ico, html.panel .panel-output .panel-header #clearBtn .ico, html.panel .panel-output #outputActions > button .ico {
  display: block; width: 20px; height: 20px; margin: 0 auto var(--s-1); line-height: 0; color: var(--accent);
}
html.panel .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill) .ico svg, html.panel .action-bar > .hist-group > button .ico svg, html.panel :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn) .ico svg, html.panel .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport .ico svg, html.panel .panel-output .panel-header #btnCopy .ico svg, html.panel .panel-output .panel-header #clearBtn .ico svg, html.panel .panel-output #outputActions > button .ico svg { display: block; width: 20px; height: 20px; }
html.panel .action-bar > button:not(.bulletise-undo-pill, .style-undo-pill) .btn-label, html.panel .action-bar > .hist-group > button .btn-label, html.panel :is(.fidelity-bar, .panel-header) > :is(.fidelity-btn, .bulletise-btn) .btn-label, html.panel .main > #outputContainer > .fidelity-bar:not([data-place]) > #btnExport .btn-label, html.panel .panel-output .panel-header #btnCopy .btn-label, html.panel .panel-output .panel-header #clearBtn .btn-label, html.panel .panel-output #outputActions > button .btn-label {
  display: block; font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: calc(var(--t-1) - 1px); font-weight: 500;
  letter-spacing: normal; text-transform: none; line-height: calc(var(--s-1) * 3); white-space: nowrap; color: var(--btn-ink);
}
html.panel .action-bar > button.copied:not(.bulletise-undo-pill, .style-undo-pill) .ico, html.panel .action-bar > button.copied:not(.bulletise-undo-pill, .style-undo-pill) .btn-label, html.panel .panel-output .panel-header #btnCopy.copied .ico, html.panel .panel-output .panel-header #btnCopy.copied .btn-label, html.panel .panel-output #outputActions > button.copied .ico, html.panel .panel-output #outputActions > button.copied .btn-label { color: var(--good); }
html.panel .panel-output > .panel-header { border-bottom: 0; padding-bottom: 0; }
html.panel .panel-output #outputActions { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-1); align-items: start; }
html.panel .panel-output #outputActions > .btn-copy:not([id]) { display: none; }
html.panel .panel-output #outputActions > #exportMenu { grid-column: 1 / -1; }
html.panel .story-name-line > #allcap-mount { display: flex; justify-content: flex-start; align-items: center; flex: 0 1 auto; min-width: 0; margin: 0; padding: 0; border-bottom: 0; overflow: visible; }
html.panel .story-name-line > #allcap-mount:empty { display: none; }
html.panel .story-name-line > #allcap-mount .ac-byline { gap: calc(var(--s-1) + 2px); min-width: 0; min-height: var(--touch-min); }
html.panel .story-name-line > #allcap-mount :is(.ac-dot, .ac-sep, .ac-tool), html.panel .story-name-line > #allcap-mount .ac-saved:empty { display: none; }
html.panel .story-name-line > #allcap-mount .ac-title {
  font-family: var(--font-display); font-style: normal; font-size: calc(var(--t-3) + 1px); font-weight: 600; color: var(--ink); letter-spacing: var(--track-tight);
  max-width: none; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html.panel .story-name-line > #allcap-mount .ac-byline::after {
  content: ''; flex: none; width: 16px; height: 16px; background: var(--accent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
}
html.panel .story-name-line:not([hidden]) { display: flex; align-items: center; gap: var(--s-2); min-width: 0; margin: 0 0 var(--s-1); }
html.panel .story-name-line > :is(#clearBtn, .story-clear) {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0;
  flex: none; width: var(--s-6); min-width: 0; min-height: 52px; margin: 0 0 0 auto; padding: var(--s-2) 2px;
  border: 0; border-radius: var(--pill); background: none; text-align: center; cursor: pointer;
}
html.panel .story-name-line > :is(#clearBtn, .story-clear) .ico { display: block; width: 20px; height: 20px; margin: 0 auto var(--s-1); line-height: 0; color: var(--accent); }
html.panel .story-name-line > :is(#clearBtn, .story-clear) .ico svg { display: block; width: 20px; height: 20px; }
html.panel .story-name-line > :is(#clearBtn, .story-clear) .btn-label {
  display: block; font-family: var(--font-display); font-size-adjust: var(--x-adjust); font-size: calc(var(--t-1) - 1px); font-weight: 500;
  letter-spacing: normal; text-transform: none; line-height: calc(var(--s-1) * 3); white-space: nowrap; color: var(--btn-ink);
}
html.panel .story-name-line > #clearBtn { visibility: visible !important; }
html.panel .main > .story-name-line { scroll-margin-top: calc(var(--phone-mast-h, 0px) + var(--phone-hero-h, 0px) + var(--s-3)); }
html.panel body[data-phone-place]:not([data-phone-place="story"]) .main > .story-name-line { display: none; }
html.panel .main > .story-name-line + :is(#outputArea, #outputContainer), html.panel .main > .story-name-line + #workingLine + :is(#outputArea, #outputContainer) { margin-top: 0; border-top: 0; padding-top: 0; }
html.panel .main:has(> :is(#outputArea, #outputContainer):not(.visible)) > :is(.fidelity-bar, .action-bar)[data-place="story"] { display: none; }
html.panel :is(#outputArea, #outputContainer) > .action-bar[data-place] { margin-top: 0; padding-top: var(--s-3); }
html.panel .action-bar[data-place] + .fidelity-bar { margin: var(--s-3) 0 0; }
html.panel .action-bar[data-place] + .fidelity-bar > span:empty, html.panel .action-bar[data-place] + .fidelity-bar:not(:has(> :not(:empty))) { display: none; }
html.panel :is(#fidelityStatus, #provStatus, #stylePassStatus) { scroll-margin-bottom: calc(var(--phone-foot-h, 0px) + var(--s-2)); }
html.panel .main > #outputContainer > .fidelity-bar:not([data-place]) { margin: var(--s-4) 0 0; padding-top: var(--s-3); border-top: var(--hairline) solid var(--rule-soft); }
html.panel .main > #outputContainer > .fidelity-bar:not([data-place]) > .style-undo-pill { grid-column: 1 / -1; justify-self: start; }
html.panel .tool-panel > .panel-cell:has(> .fidelity-bar), html.panel .tool-panel > .panel-cell:has(> .toggle-label:only-child) { display: none; }
html.panel .tool-panel > .panel-cell:first-child:has(+ .panel-cell > .fidelity-bar), html.panel .tool-panel > .panel-cell:first-child:has(+ .panel-cell > .toggle-label:only-child) { grid-column: 1 / -1; }
html.panel .main:has(> :is(#outputArea, #outputContainer):not(.visible)) > .output-empty[data-place="story"]:not(:has(+ #workingLine)) { display: block; }
html.panel .main > .output-empty[data-place="story"] { text-align: center; padding: var(--s-5) var(--s-4); color: var(--ink-soft); }
html.panel .main > .output-empty .empty-icon { font-family: var(--font-display); font-weight: 400; font-size: var(--t-7); color: var(--rule); margin-bottom: var(--s-3); line-height: calc(var(--s-1) * 10); }
html.panel .main > .output-empty h3 { font-family: var(--font-display); font-weight: 400; font-style: italic; font-size: var(--t-5); line-height: calc(var(--s-1) * 8); letter-spacing: var(--track-tight); color: var(--ink); margin: 0 0 var(--s-2); }
html.panel .main > .output-empty p { font-family: var(--font-body); font-size: 14px; line-height: calc(var(--s-1) * 6); color: var(--ink-soft); max-width: 400px; margin: 0 auto; text-wrap: balance; }
/* THE STORY HEADLINE SITS BELOW THE HERO (Task 5k, 2026-10-03). Ritu, on the
   app at 358: "the story headline looks larger than the hero. That's wierd
   too. Headline can be light, no?", then "Even the typeface overshadows the
   hero." Shown four renders he chose "4 but tad bigger": the headline in the
   body face (Newsreader) at weight 500, line-height 1.25, a little larger
   than 19px. The size is derived from the hero's own phone rule,
   clamp(var(--t-4), 5.8vw, var(--t-6)), minus 1.25px, and never under 18px,
   so it stays 1 to 1.5px under the hero at every width from 320 to 600
   (19.5px at 358, hero 20.76; 18px at 320, hero 19). Over 600px (min-width: 600.01px, so 600.5 under page zoom is covered; the
   hero's phone rule is max-width: 600px; the file's own min/max idiom, as
   with 699.98px, because range syntax is dropped by Chrome before 104 and
   Safari before 16.4) the hero
   follows the 900 rule, so the band after the panel copy takes that clamp.
   The wght axis setting of the display face is reset so weight 500 means 500.
   Desktop: untouched, outside every gate. */
html.panel .copy-headline, html.panel .rewrite-headline { font-family: var(--font-body); font-weight: 500; font-variation-settings: normal; font-optical-sizing: auto; letter-spacing: normal; line-height: 1.25; font-size: max(18px, calc(clamp(var(--t-4), 5.8vw, var(--t-6)) - 1.25px)); }
html.panel .hb-chev { margin-top: -8px; margin-bottom: -8px; } /* the 26px mark sits on the line without enlarging its 1.25 line box (Task 5k fix) */

/* ============================================================
   THE STORY HEADLINE, OVER 600px (Task 5k, 2026-10-03)
   ============================================================
   Above 600px the hero follows clamp(var(--t-5), 3.4vw, var(--t-7)) (the 900px
   ramp), so the headline's size follows it too, 1.25px under, never under
   18px. Face, weight and line-height come from the three ICON ROW copies
   above. These sit after the panel copy so the width band wins the cascade at
   equal specificity. */
@media (min-width: 600.01px) and (max-width: 699.98px) {
  .copy-headline, .rewrite-headline { font-size: max(18px, calc(clamp(var(--t-5), 3.4vw, var(--t-7)) - 1.25px)); }
}
@media (display-mode: standalone) and (min-width: 600.01px) {
  .copy-headline, .rewrite-headline { font-size: max(18px, calc(clamp(var(--t-5), 3.4vw, var(--t-7)) - 1.25px)); }
}
@media (min-width: 600.01px) {
  html.panel .copy-headline, html.panel .rewrite-headline { font-size: max(18px, calc(clamp(var(--t-5), 3.4vw, var(--t-7)) - 1.25px)); }
}

/* ============================================================
   THE NARROWEST SCREEN, 2026-09-13.
   ============================================================
   A 24px gutter on a 320px screen spends 15 per cent of the width on
   nothing. On a 390px screen it spends 12 per cent, which is the number the
   floor was set for.

   --gutter is clamp(1.5rem, 5vw, 5rem), and below 480px the FLOOR binds, not
   the slope: 320, 360 and 412 all get the same 24px. That is the fault this
   step corrects. It is not a new idea about spacing; it is the slope allowed
   to keep going for one more step at the end where the screen runs out.

   WHO IS AT 320. Not only the old small phone. Ritu's Chrome draws pages at
   about 115%, which lays his 412px phone out near 358; a reader at 125% on a
   390px phone lays out at 312. Zoom is what makes this width live, and zoom
   is why the phone gates now measure it at all.

   MEASURED at 320, with the pair Ritu ruled must stay side by side
   (2026-09-13, holding his 2026-09-12 ruling at every width): the content
   column goes 272 -> 288, each of the two pinned buttons goes 132 -> 140, and
   Build Blueprint's label, which wants 138, stops overshooting its own pill
   by 5.81px. Nothing at 358 or above moves: the query stops at 340. */
@media (max-width: 349.98px) {
  :root { --gutter: 1rem; }

  /* AND THE PAIR GETS ITS LAST FOUR PIXELS FROM THE TWO QUANTITIES, not from
     the words, the marks or the pill.

     Ritu ruled on 2026-09-13 that Test the idea stays beside Build Blueprint,
     which holds his 2026-09-12 ruling at every width, and he took the cost
     with it: at 358, his own phone, Build Blueprint's content already runs
     4.4px into its own 8px of side padding. INSIDE the pill is the line. At
     320 it was 5.81px OUTSIDE the pill, mark and all, and the wider gutter
     above leaves 1.8px of it still outside.

     Sizing the two tracks to their own words does not close it: MEASURED, the
     pair wants 308px of content at 320 and the row has 288, so the grid
     shrinks both tracks back to equal halves and clips the longer label
     anyway. The 20px has to come out of the content.

     It comes out of the two quantities and nothing else: the tracking that is
     already halved on this pair goes to 0.04em, the 6px between a mark and its
     word goes to 4, and the 8px between the two pills goes to 4. MEASURED at
     320 after: Build Blueprint holds 141.5px of content in a 142px pill, so
     the mark and the last letter are inside it, as they are at 358.
     Nothing at 350 and above moves. */
  body.has-phone-tabs .action-bar:not([data-place]) .action-left:has(> button:nth-of-type(2)) {
    column-gap: var(--s-1);
  }
  body.has-phone-tabs .action-bar:not([data-place]) .action-left:has(> button:nth-of-type(2)) > button:nth-of-type(1) .btn-label,
  body.has-phone-tabs .action-bar:not([data-place]) .action-left:has(> button:nth-of-type(2)) > button:nth-of-type(2) .btn-label {
    letter-spacing: 0.04em;
  }
  body.has-phone-tabs .action-bar:not([data-place]) .action-left:has(> button:nth-of-type(2)) > button:nth-of-type(1),
  body.has-phone-tabs .action-bar:not([data-place]) .action-left:has(> button:nth-of-type(2)) > button:nth-of-type(2) {
    gap: var(--s-1);
  }
}



/* ═══════════════════════════════════════════════════════════════════════════
   THE DESK IS A FRAME ON THE DESKTOP, NOT ONE LONG SCROLL
   Ritu, 2026-09-22: "On a smaller laptop screen than my MacBook, the writing
   tools open, and I can't even see the controls at the bottom because they
   don't fit into the screen. Like we have done on the app, why can't we have a
   static masthead and hero? The rest of the screen can move."

   HE IS DESCRIBING THE APP, AND THE APP IS RIGHT. Measured on the phone at
   358x620 on 2026-09-22, assets/js/phone-places.js already builds exactly
   this (since 2026-10-07 the band is 71px, so h1.display runs 60..131 for a
   one-line headline, by the declared --phone-hero-h):
     .tool-masthead  sticky  0..60
     h1.display      fixed   60..122
     .action-bar     fixed   429..558
     .phone-tabs     fixed   557..620
   The masthead, the desk's name and the action row hold still; the work
   scrolls between them. The desktop never got it and stayed one tall document.

   WHAT THAT COST, measured before this block:
     FieldReportage  Write the piece  needs 757px   off screen below 757
     DeskCopy Pro    Rewrite          needs 795px   off screen below 795
   DeskCopy Pro's document is a fixed 1265px tall, so Rewrite was 15px under
   the fold on a 1440x780 MacBook and 95px under it on a 1280x700 laptop. Fact
   check and Style pass sat at 909, Copy and Export at 1018.

   WHY NO GATE CAUGHT IT. scripts/notes_box_fit_check.mjs tests fifteen widths
   and exactly three heights: 800, 900 and 1000. Every laptop shorter than the
   shortest case it knows about was outside the gate. Heights added there.

   STICKY, NOT FIXED. The phone layer computes top and bottom offsets by hand
   because phone-places.js has moved the elements into places. Nothing moves on
   the desktop, so each element can hold its own place in the ordinary document
   and nothing has to be measured against anything else.

   WHAT THIS DOES NOT FIX, and it is the third thing he named: "There is a
   scroller only for the right side of the page where the output will land,
   which I find strange." The extra 53vh cap on that pane is culled further up
   this file, which gave it back 59px at a 700px window (292 -> 351). The
   second scrollbar itself is structural and survives: .output-scroll is
   flex:1 inside .panel-output, which is a grid item stretched to the row, so
   the pane is bounded by the column beside it and scrolls inside a page that
   also scrolls. MEASURED at 1280x700 with a story in it: pane 351px against
   2944px of story, page 938px against a 700px window. Curing that means the
   desk becoming a real fixed-height frame with no page scroll at all, which
   is a change to each desk's own body and is not in this block.

   ONLY ABOVE 700px. assets/js/phone-places.js attaches nothing at or below
   699.98px, so this query and the app layer never meet.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 700px) {

  /* THE HERO IS NOT PINNED, AND THAT IS A DECISION, not an omission. He asked
     for "a static masthead and hero", which is what the app does — but the
     app's masthead is 60px and carries the wordmark and the gear alone, so
     there the pinned h1 is the only thing naming the desk. This masthead is
     99px and already ends with FIELDREPORTAGE · EDITORIAL. Pinning the h1 as
     well spends another 62px of a 660px window restating a name that is on
     screen either way. Shown to him rendered both ways before this was left
     out; the arithmetic is in the session note. */
  /* ── THE WHOLE ROW SITS ON ONE LINE ──────────────────────────────────────
     Ritu, 2026-09-22, looking at live prod: "Look at the hamburger and the
     text on the left. Looks misaligned. Not sure if it's aligned with the
     pills. Recurring issue."

     It is recurring because the row aligns on TEXT BASELINES
     (.masthead-utility, align-items: baseline). Every item in it has a
     different type size and a different box, so each one's baseline lands
     somewhere else and nothing agrees. MEASURED on live DeskCopy V3 at 1512,
     by the vertical centre of each item's ink:

       DAY / NIGHT / PRESS   box 32..76   centre 53.5
       SETTINGS              box 30..74   centre 52
       TEACH                 box 30..74   centre 52
       the menu mark         box 30..74   centre 52
       DESKCOPY · EDITORIAL  box 44..63   centre 53.6

     So the three pills he compared against were themselves 1.5px apart from
     the theme group, and the desk's name was 1.6px below the marks. Centring
     the row puts ALL FIVE at 52.8 and leaves the masthead 99px tall, measured.
     It also retires the baseline anchor this session first wrote into
     site-menu.css: with centres there is no baseline to match, and the row
     measures identically with the anchor and without it.

     scripts/masthead_row_check.mjs now holds every item in the row against
     every other, not just the menu against one neighbour, which is what let a
     1.5px drift live here long enough to become a recurring complaint. */
  .masthead-utility { align-items: center; }

  .tool-masthead {
    position: sticky;
    top: 0;
    /* Over the page. UNDER the site menu's scrim and panel (70 / 71) and under
       the settings drawer (998 / 999), both of which must cover the masthead. */
    z-index: 60;
  }

  /* THE ROW THAT STARTS THE WORK HOLDS THE FOOT OF THE WINDOW.
     This is the fault he actually reported: not that the page was long, but
     that the button was unreachable. The work passes behind a band that spans
     the window, with a rule along its top so the cut reads as the foot of the
     frame and not as text that failed to render.

     TWO CONTAINERS, NOT ONE, and the first version of this block pinned the
     wrong one on five desks out of seven. MEASURED at 1280x700:

       .action-row   DeskCopy Pro (Rewrite 793), DeskCopy V3 (725),
                     DeskPop (725), IntroDoctor (657), DeskTriage (735)
       .action-bar   FieldReportage (Test the idea / Build Blueprint /
                     Write the piece), ReporterAlly (Refine)

     DeskCopy Pro carries BOTH, and its .action-bar is #dcpActionBar — Copy,
     Export and Bulletise, which are what you do with a finished story, not
     what you press to write one. Pinning it left Rewrite off screen and put
     the wrong three buttons in the one place that is always visible. So the
     .action-bar half is named by the two desks where that row IS the primary
     one, and DeskCopy Pro's output row stays in the page where it belongs.

     THE BAND IS ONE LAYER, AND TWO EARLIER TRIES SHOW WHY IT HAS TO BE.
     A box-shadow cannot do it: an OUTER shadow never paints under the
     element's own border box, so it leaves a hole exactly the width of the
     row. That hole is invisible on DeskCopy Pro, where the row sits in the
     right-hand column and everything needing cover lies outside it, and it is
     the whole defect on FieldReportage, where the row sits over its own
     accordion. MEASURED at 1366x660: the FREEFORM NOTES row showed through the
     buttons with no band at all, while DeskCopy Pro looked finished on the
     same rule. One desk of two passing is not the rule working.
     A shadow cannot carry the rule either. The spread grows the silhouette
     upwards as well, far past the clip, so both shadows reach the top edge and
     the fill simply covers the line: the band drew its rule across the row's
     own box and nothing across the bleed. */
  [data-tool="fieldreportage"] .action-bar,
  [data-tool="reporterally"] .action-bar,
  .action-row {
    position: sticky;
    bottom: 0;
    z-index: 56;
    padding-bottom: var(--s-3);
    background: var(--paper);
  }

  /* THE BAND IS THE ROW'S OWN WIDTH, AND A FULL-BLEED ONE HAD TO BE TAKEN OUT
     AGAIN. A band spanning the window looks right while the row is pinned, and
     `position: sticky` stops pinning the moment the page scrolls down to where
     the row actually lives. RENDERED at 1280x700 scrolled 200px: the row sat
     at its natural 555..631 with the config column continuing below it, and
     the full-width band painted straight across that column and cut it in two.
     CSS cannot ask whether an element is currently stuck, so a band that is
     only correct while stuck is a band that is wrong half the time. The row's
     own box is correct in both states, and what it costs is a 25px gutter
     between DeskCopy Pro's two columns where the page still shows through.

     A full band belongs to the frame this desk does not have yet: masthead,
     work area and action row as three boxes of a fixed-height window, with the
     row outside the scrolling part so it never un-pins. That is what
     assets/js/phone-places.js builds on a phone, by MOVING the elements, and
     it is a change to each desk's body rather than a rule in this file. */

  /* .action-row carries no padding and no rule of its own (measured: padding
     0, border-top 0, margin-top 24), which is why the five desks that use it
     needed the air and the line adding and the two that use .action-bar did
     not: content passing behind a pinned row with neither was sliced through
     the middle of its letters. */
  .action-row {
    padding-top: var(--s-3);
    border-top: var(--hairline) solid var(--rule-soft);
  }
}

/* ACTION TIPS (2026-10-02). One line under an action, on hover, focus or a held
   press. It appears at once and goes at once: no transition. */
.action-tip {
  position: absolute; z-index: 950; max-width: 240px; padding: var(--s-2) var(--s-3);
  background: var(--ink); color: var(--paper); border-radius: var(--s-2);
  font-family: var(--font-body); font-size: var(--t-2); line-height: 1.35; pointer-events: none;
}
