/* ==========================================================================
   CW Innovation System · the project stylesheet
   ==========================================================================

   THIS FILE IS THE ONE PRODUCTION SERVES (D66). It is not a mockup artefact
   and it is not thrown away at the end of Phase 4: the design crosses the
   phase boundary as this file rather than as a description, so that Phase 5
   COPIES rather than REINTERPRETS. Every difference an interpretation would
   introduce is a judgment call nobody signed.

   Layers, in the order they are written:
     1. @font-face        - self-hosted Inter (SPEC-4 ruling 3)
     2. :root tokens      - colour, type, spacing, radius, shadow, layout
     3. Base              - the token layer applied to the document
     4. The shell         - frozen once, inherited by all eleven surfaces
     5. Page surfaces     - plain, and the Oxford library scene
     6. Bootstrap bridge  - Bootstrap's own variables mapped onto our tokens
     7. The pattern set   - buttons, card, badge, chip, filters, table, empty
                            state, form field states
     8. Library furniture - the shelf, spines, detail panel, video carousel
     9. The form surface  - the measure, prose fields, the track fork
    10. The board surface - lanes, the journey track, the ranking list
    11. The record surface- the record header, page sections, the snapshot
                            marker, the quality-flow spine, dormant sections
    12. The gate surface  - the verdict group, the consequence statement, a
                            criterion being set, a criterion being tested
    13. The governance    - the month grid, a scheduled review in a day cell,
        surface             the tab count, the reveal panel
    14. The portfolio     - the headline gauge, the confidence mix, the split
        surface             rows, the top-10 table cap, the two-track bands
    15. The explainer     - the pipeline rail, the gate markers and their
        surface             travelling light, the confidence ladder, the two
                            track rows, the reveal panel's body, the step list,
                            the role cards, the narrative plate
    16. The home surface  - the banner and its stage ramp, the activity feed,
                            the plain video shelf
    17. The health        - the active More, the dark console, the history table
        surface             and its trend charts, the targets table and form,
                            cross-team, forecast accuracy, freshness, pace

   Nothing enters this file until Ben has seen it rendered and ruled on it. Every
   component below carries the decision point that settled it.

   Owning docs: docs/design-targets.md (what each token means and what is
   checkable), docs/static-assets.md (where the vendored files came from).

   Changelog:
     v18 · 26 Aug 2026 Phase 5 surface 1 (the Tool Shed), FREEZE AMENDMENT A-7,
          Ben ruling. THREE DECLARATIONS, ALL THE SAME ONE, AND THE FIRST CHANGE
          TO THIS FILE MADE BECAUSE A BUILT PAGE NEEDED IT RATHER THAN BECAUSE A
          DESIGN ROUND ASKED FOR IT: `text-decoration: none` on `.chip-cw` and
          `.activebar__clear` in section 7, and on `.spine` in section 8.
          THE OMISSION WAS INVISIBLE UNTIL A PAGE COULD BE CLICKED. All three are
          `<div>` or `<span>` in every frozen artefact, because a static mockup has
          nothing to click, and all three already carried `cursor: pointer` - the
          design saying it always meant them as controls. Phase 5 makes them real
          `<a>` elements, which `docs/design-targets.md` requires of the Tool Shed
          by name so `?tool=X&video=Y` can compose, and Bootstrap's reboot
          underlines a link. On `.spine` the label is `writing-mode: vertical-rl`,
          so the underline ran VERTICALLY and read as an extra hairline beside
          every tool name.
          NO MECHANICAL LAYER COULD SEE IT. The token check reads colour, radius,
          font size and typeface and does not read `text-decoration`, so the Tool
          Shed passed all four D67 layers at 64 of 64 assertions and zero off-token
          while drawing it. Found by looking at the page, then proven by probing
          computed style on the frozen artefact against the Django page: `none`
          there, `underline` here. Recorded as a gap in the ORACLE, not only as a
          defect in a page.
          IT CANNOT MOVE A SIGNED PAGE AND THAT WAS CHECKED, NOT ARGUED: no frozen
          artefact carries an `<a>` on any of the three, so the full sweep returns
          the same thirteen element counts (12, 30, 64, 91, 85, 104, 147, 155, 100,
          170, 186, 78, 170) and the same twelve negative-control counts (2, 12, 51,
          22, 28, 50, 49, 43, 48, 64, 55, 65) before and after. NO NEW TOKEN and NO
          new contrast pair, so `contrast.py` is untouched at 77 pairs.
          The file already did this seven times over - `.shell__nav a`, `.btn-cw`,
          `.trackcard`, `.crumb a` and three more - so this is the file's own
          practice reaching three components that had never been links.
     v17 · 22 Aug 2026 wave 2i, surface 11 (System health). SECTION 17 ADDED, and
          it is the LAST surface section: the active `More` (the first page in the
          build that lives under the overflow control), the dark console, the
          history table with a trend chart per row, the targets table and the
          System Architect's targets form, the cross-team pair, forecast accuracy,
          tracking freshness and triage pace. TWO NEW COLOUR TOKENS, and both are a
          MEASUREMENT rather than a choice: Ben asked for green and red direction
          arrows on the console and the frozen pair CANNOT be read on
          `--itk-black` - `--text-success` is 1.99 and `--text-danger` is 1.84
          against D68's 4.5 floor. `--text-success-dk` (#739987) is `--itk-sage`,
          already a brand token, at 6.11; `--text-danger-dk` (#CD6F88) is
          oxblood's exact hue and saturation lifted in lightness, at 5.73. BOTH
          ARE DECLARED INSIDE `.csl` RATHER THAN `:root`, which is a guard and not
          tidiness: both FAIL on white, so a future session reaching for them on a
          light surface resolves to nothing instead of to an unreadable pass.
          THIS IS ALSO THE FIRST SURFACE THAT BOTH REPORTS AND EDITS WHAT IT
          REPORTS AGAINST (D73's targets, Ben ruling 22 Aug) - the Dashboard's
          "the page writes nothing" gate is deliberately NOT inherited here, and
          the two halves are separated by the frozen `.btabs`.
     v16 · 21 Aug 2026 wave 2h, O52. SECTION 4, TWO DECLARATIONS, AND IT IS A
          DEFECT FIX RATHER THAN A DESIGN: `.shell__nav` gains `overflow-x: auto`
          with `overflow-y: hidden` written explicitly, because the topbar nav
          OVERLAPPED the account block below a 1106px viewport - `More` painted
          on top of `Ben Bielby` - and had done since the wave-1 freeze on 6 Aug.
          Ben ruling DP-108 A, taken against a first-layout-breakpoint option he
          declined. THE SAME RULING NARROWS DP-16 C: More being "always present"
          means always IN the nav, not always on screen. NO SIGNATURE MOVED AND
          THAT WAS PROVEN RATHER THAN ARGUED - above the breakpoint there is no
          overflow, so the full eleven-artefact `compare.py` sweep is byte-equal
          before and after (every element count identical, all PASS), `render.py`
          passes all eleven, and the topbar measures 52px at every width from
          1400 down to 900. One Phase 5 consequence is recorded at the rule: a
          scroll container clips, so More's future dropdown must not live inside
          `.shell__nav`.
     v15 · 19 Aug 2026 wave 2g, surface 9 (How-It-Works). SECTION 15 ADDED: the
          pipeline rail and its stage card, the gate markers with the travelling
          light that lights each one as it passes, the confidence ladder aligned
          under the stages it belongs to, both track rows and their titles, the
          progression arrows, the hover tip, the reveal panel's body, the step
          list, the role cards and the narrative plate. NO NEW TOKEN. TWO NEW
          CONTRAST PAIRS, both declared before they were drawn rather than after:
          --itk-gold on --itk-charcoal at 5.40 (the narrative plate, Ben's ruled
          colour) and --itk-smoke on --itk-charcoal at 7.21. 68 pairs pass.
          THE MODAL IS REUSED, NOT REDEFINED: section 13's .gvpop__hd, __t and
          __x are exactly what this surface's nine panels wear, so the only new
          rule for them is the body layout and ONE WIDTH, and that width is
          SCOPED to .hiw-pop-dlg rather than applied to .modal-dialog - because
          .modal-dialog is Bootstrap's own selector and the SIGNED governance.html
          renders through it, so a global rule would have redrawn a frozen artefact
          from another page's instruction.
          Also here, and unrelated to this surface: the header's layer list stopped
          at 13 while section 14 existed, and two changelog entries both claimed
          v13. Both corrected above.
     v13 · 18 Aug 2026, Ben ruling closing O49: "extend the charcoal badge to project
          detail too". NO RULE CHANGED - `.badge-cw--ended` already existed - but TWO
          COMMENTS DID, and both had stated a superseded position as settled. Section
          7 carried a flagged note saying the badge was deliberately NOT extended;
          section 11 said `--off` is the Stopped fill. Freeze amendment A-3, four
          files, and it reached the PATTERN SET as well as Project Detail because the
          catalogue's own sample decision log drew a `Stop` in oxblood - the identical
          case, on the one page whose job is to be the reference.
          THE PALETTE'S LINE IS NOW CLEAN AND FITS IN A SENTENCE: oxblood means
          something is WRONG - late, off track, in error - and charcoal means
          something has ENDED. Oxblood did not shrink to nothing: `--status-off` is
          still the RAG fill, still the overdue fill, still `--text-danger`.
          Section 11's correction KEEPS THE EARLIER HISTORY on purpose. The concern
          that an oxblood Stop contradicted DP-18 was wrong and is still wrong, since
          DP-18 draws its line at button-versus-badge. The colour moved for a reason
          DP-18 never addressed: one fill carrying two meanings on one screen.
     v14 · 19 Aug 2026 wave 2f, surface 8 (Portfolio Dashboard). SECTION 14 ADDED:
          RENUMBERED FROM v13 AT THE HOW-IT-WORKS FREEZE, because two entries
          carried that label: this one and the 18 Aug O49 entry below. Both were
          real changes on different days and neither is wrong; only the label
          collided. This is the later change, so it takes the higher number, and
          the happy result is that section N now matches version N from 13
          onward. The decisions register at v64 refers to this entry as "app.css
          v13", and that reference is corrected in the register entry landed with
          this freeze rather than left to drift.
          the headline gauge and its year toggle, the confidence mix with its
          under-segment labels and vertical legend, the rung-stacked split rows
          with their verified Total rows, the confidence chip, the centred cell,
          the width-independent top-10 table cap with a permanent scrollbar, the
          two-track count bands, and the dashboard grid. THE FIRST PALETTE
          EXPANSION SINCE --text-eg: three value-confidence rungs join the token
          set (--conf-verified, --conf-evidenced, --conf-banded; --conf-tracked
          aliases --itk-teal), one hue at four strengths per DP-87 A, adopted by
          Ben's own instructions. FIVE new rows in tools/contrast-pairs.tsv: the
          four rung text pairs (11.13 / 8.04 / 12.53 / 16.37) and the band label
          pair --itk-charcoal on --sec-band (6.88), which had been used unpaired
          since the Idea Board.
     v12 · 18 Aug 2026 wave 2e, surface 7 (Governance). SECTION 13 ADDED: the month
          grid and its day cells, a scheduled review inside a cell, the today
          marker, the overdue tab count and DP-69's reveal panel. No new token and
          no new contrast pair, the fourth surface running to manage both.
          THESE RULES SPENT A ROUND LIVING IN THE MOCKUP'S OWN `<style>` BLOCK,
          marked "candidate components - they go into section 13 once Ben confirms
          this render", and moving them here is what makes the freeze mean
          anything: D66 says the design crosses the phase boundary as THIS FILE,
          so a component that stays in the artefact's inline block is a component
          Phase 5 would have to reinvent from a picture. Caught at the freeze,
          before signing, by checking the claim in the artefact's own header
          comment rather than trusting it.
          `.modal-content` and `.modal-backdrop` ARE BOOTSTRAP'S OWN SELECTORS and
          this file only wins because every template links it second. Written down
          because it is invisible until it breaks.
          ONE REJECTED COMPONENT IS DELIBERATELY ABSENT and says so in place:
          DP-75 B's overdue-pulled-onto-today chip.
     v11 · 18 Aug 2026 wave 2e, surface 6 (Gate capture). SECTION 12 ADDED: the
          verdict group, the consequence statement, a criterion being set and one
          being tested. Two rules landed OUTSIDE it, and both are recorded where
          they sit rather than here. A BRIDGE RULE INTO SECTION 6: Bootstrap draws
          a radio at `border-radius: 50%`, a percentage and not a rung on the
          scale, mapped onto `--radius-pill` so the control is identical and the
          token layer stays strict - the same call `.modal-backdrop` gets in v12.
          `.formcol .dorm` releases the 78ch measure, SCOPED rather than lifted:
          removing the cap outright would widen every dormant box on the signed
          project-detail.html, and a freeze amendment to fix a form is the wrong
          trade.
          THE FIFTH STATUS BADGE, `.badge-cw--ended` on --itk-charcoal, added to
          section 7 on Ben's 18 Aug ruling. His second reason was a defect nobody
          had caught: on Governance oxblood ALREADY meant "overdue", so one fill
          was carrying two unrelated meanings on one screen. Flagged and NOT
          extended - DP-54 A ruled oxblood for a Stopped record on the signed
          project-detail.html, so extending it is a freeze amendment and his call.
     v10 · 17 Aug 2026 wave 2d round 4, Ben's second annotation batch. `.pdnote` and
          `.pdsec__d` LOSE THEIR MEASURE, ruled three times in one batch: a caption
          on a table should share the table's edges, and one that stops short reads
          as a mistake. The measure rule above `.qflow` stands for PROSE and this is
          its one carve-out, written down because the next surface meets the same
          question and the answer depends on what the text is FOR.
          DP-55 RULED "leave it": a dormant tab is distinguished by weight alone,
          which is a KNOWN and accepted exception to the never-one-signal rule
          rather than an oversight, and the comment on the tab rules says why it is
          defensible - a dormant tab is a courtesy, not a control.
          `.qf__a--none a` REMOVED and the sentence it linked from is gone: trying
          to build the round-3 hyperlink surfaced that NO MVP SURFACE lets anyone add
          to an idea after submission, and Ben ruled the promise out rather than
          leaving it unkeepable. The capability is now a row in
          plans/future-upgrades.md. O44's shape, one surface later.
          ONE OF HIS OWN DELETIONS REVERSED BY HIM, and it sharpens the convention
          this page follows: the Stop-is-a-normal-outcome sentence is back, because
          it teaches a concept the funnel depends on. So the rule is not "no prose"
          - it is no prose explaining HOW THE SYSTEM IS IMPLEMENTED. Prose that
          teaches a reader something the design rests on stays.
     v9 · 17 Aug 2026 wave 2d round 3 feedback: `.pdsec__d` added (a section
          description), `.qf__a--none a`, and `.btab__w` removed as rejected.
     v8 · 17 Aug 2026 wave 2d round 2, six more rulings into section 11. DP-49 C
          (the record is TABBED, reusing the frozen .btabs), DP-50 B (a `computed`
          label on the column), DP-51 B (a breadcrumb). DP-52 A, DP-53 A and
          DP-54 A add NO CSS AT ALL - they keep the frozen zebra table, the
          already-written .qf__a--none, and the frozen status badges. STILL NO NEW
          TOKEN AND NO NEW CONTRAST PAIR.
          FOUR CANDIDATE COMPONENTS WERE REJECTED AND ARE DELIBERATELY ABSENT: the
          sticky rail (.pdlay/.pdrail), the vertical gate timeline (.gtl), the
          page-level computed notice (.pdcomp) and the previous/next lane control
          (.recnav). They stay in design/sketches/p4-project-detail-v2.html as the
          record of the round. A rejected component has no business in the file
          production serves.
          DP-49 C AND DP-48 B RECONCILE rather than conflict, and the comment on
          the tab rules says how: the tab bar carries the skeleton, so a Stage 0
          record shows all five sections and the stage that unlocks each in about
          40px. Ben got to that; the two options offered did not.
          ONE CORRECTION IS RECORDED IN THE DP-54 COMMENT: a concern was raised
          that an oxblood Stopped badge contradicts DP-18, and DP-18's own text
          says the opposite - "a Stop wears a status badge". The distinction is
          button versus badge. Read the ruling at source before asserting a
          conflict with it.
     v7 · 17 Aug 2026 wave 2d, surface 5 (Project Detail). Section 11 added: the
          way back, the record header, the page section, the frozen-snapshot
          marker, the quality-flow spine, the I-Revenue headline, and DP-48 B's
          dormant section. **NO NEW TOKEN AND NO NEW CONTRAST PAIR** - the second
          surface to manage both (Submit was the first), because this page is
          assembled entirely from the frozen palette.
          DP-48 B was ruled AGAINST the recommendation, and the losing option's
          component - a single ordered "what comes next" strip - is deliberately
          ABSENT from this file. A rejected component has no business in the
          stylesheet production serves; it stays in the sketch that recorded the
          round.
          THE THIRD CONSECUTIVE RULING OF ONE SHAPE, and section 11's comment says
          so: with DP-43 and DP-44, Ben has now three times chosen to show a
          structure that has not happened yet. The remaining six surfaces should
          assume it rather than re-argue it.
          One rule was earned rather than chosen: a prose container gets its
          MEASURE at the moment it is created. This surface is the third instance
          of that fault (after DP-30's 1250px form and the Idea Board's silently
          lost 660px), and no mechanical check can catch it, because a page that
          is too wide is built entirely from frozen tokens.
     v6 · 11 Aug 2026 wave 2c, surface 4 (the Idea Board). Section 10 added: the
          lane grid, the closed-work strip, the rank ordinal, the seven-segment
          journey track, the dashed empty-lane slot, the two tabs, the page header
          row, Track 2's ranking list with its banded section label, the arrival
          message and DP-32 B's new-card highlight - per DP-36 to DP-47, of which
          SEVEN went against the recommendation. **ONE TOKEN ADDED, --sec-band,
          and no existing token changed.** It exists because Ben caught a fault by
          looking: the band was first built on --itk-smoke, which is 1.085:1
          against the page and so nearly the same colour that it was a box you
          could not see. Three shades were rendered at real size with their
          measured ratios and he took the LIGHTEST, #E6E8EA at 1.137. Two rows
          added to tools/contrast-pairs.tsv and the second is the one that
          mattered: the label's own contrast was never in doubt, so the property
          the colour was CHOSEN for - being visible at all - is the one nothing
          would have checked had it not been declared. 61 text pairs pass.
     v5 · 8 Aug 2026 wave 2b, surface 3 (Submit). Section 9 added: the measure,
          the multi-line field, the question group, the track fork, the
          optional-questions disclosure, the team checkbox grid and the action
          row, per DP-28 B, DP-29 A, DP-30 A, DP-31 B and DP-33 B. **NO NEW
          TOKEN and no existing token changed**, which is the first surface to
          manage that: the Tool Shed needed six additions and the Video Library
          one component, and Submit needed neither, because it is assembled
          entirely from the frozen palette. The same fact shows up in
          tools/contrast-pairs.tsv, where Submit adds not one row - every
          foreground/background pair it uses was already declared.
     v4 · 8 Aug 2026 wave 2b, opening the Submit surface. ONE COMPONENT RULE
          BROADENED AND NO TOKEN TOUCHED: `.hint` and `.err` were styled as
          descendants of `.fieldwrap`, so help text placed anywhere else silently
          rendered as 16px body copy, and the token check could not see it
          because 16px and the inherited colour are both frozen tokens. Now
          styled by class. Proven non-regressive rather than assumed: the
          computed font size, colour, margin and weight of every `.hint` and
          `.err` in the frozen pattern set are byte-identical before and after,
          and all four signed artefacts still compare green. Found by rendering
          the Submit variants and looking at them, which is the fourth
          consecutive surface where that step caught what the checks did not.
     v3 · 6 Aug 2026 SPEC-4 step 9, wave 2 surface 1 (the Tool Shed). Section 8
          added: the shelf, the spines, the detail panel, the tool tag and the
          sideways video carousel, per DP-1 to DP-9 as already ruled. Six token
          ADDITIONS (--spine-tn-lightest, --spine-gloss, --fade-vid,
          --shadow-shelf, --shadow-spine, --glow-open) and NO change to any
          existing token, because extending this file with new components is
          expected and changing a token is a design amendment that needs Ben.
     v2 · 6 Aug 2026 SPEC-4 step 5. Bootstrap bridge and the pattern set added
          per DP-17 (empty state B, with C on the library pages), DP-18 (four
          button levels) and DP-19 (zebra tables). Nine literal hex values that
          had crept into the component rules were tokenised as support values -
          not for tidiness but because the step 6 token check verifies computed
          colours against the frozen set, and a literal buried in a component is
          either a check failure or a reason to weaken the check.
     v1 · 6 Aug 2026 SPEC-4 step 4. Base layer, the shell and the two page
          surfaces, per DP-12 to DP-16.
     v0 · 6 Aug 2026 SPEC-4 step 3. Token layer only. Palette fixed per DP-10 B
          and DP-11 (three levels of brown text); all 37 declared text pairs
          verified at WCAG 2.1 AA (D68).
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. Inter, self-hosted
   --------------------------------------------------------------------------
   Measured at the Phase 4 blindspot pass: Inter is ABSENT from Ben's machine,
   along with Neo Sans Std and Poppins. Every sketch he had judged rendered in
   Calibri. Self-hosting settles the typeface permanently, because if the
   freeze happened in Calibri and anyone later added Inter as a webfont, every
   frozen page would change AFTER the freeze - the one thing a freeze exists
   to prevent.

   Variable font, weight axis only: one file covers 100-900, so the whole type
   scale needs no separate weight downloads.

   Paths are RELATIVE, not absolute. That is load-bearing twice over: it lets
   the frozen mockup load this same file straight from a local server with no
   Django in the path, and it keeps exit row 13 (no external references)
   honestly clean.

   unicode-range means a subset is fetched ONLY when a character in that range
   is actually rendered, so latin-ext and italic cost nothing on a page that
   does not use them. Provenance: docs/static-assets.md.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Inter";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin-wght-italic.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin-ext-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Inter";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin-ext-wght-italic.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}


/* --------------------------------------------------------------------------
   2. Tokens
   --------------------------------------------------------------------------
   Two colour families that MEET rather than clash, and the reason is worth
   stating because it is not a coincidence: the Oxford interior's tan and sage
   are Intertek's OWN supplementary colours (#C09E78 Warm Tan, #739987 Sage),
   so they are aliased below rather than redefined. Intertek branding lives on
   the outer edges of the page; the Oxford library scene lives further in.

   Every value below that carries text has been checked against WCAG 2.1 AA
   (D68). Re-run after ANY change here:

       py tools/contrast.py --css static/css/app.css --pairs tools/contrast-pairs.tsv

   A colour added here without a row in contrast-pairs.tsv is unchecked, and
   the checker cannot know that. Adding a new foreground/background combination
   to a page means adding its row to that file first.
   -------------------------------------------------------------------------- */

:root {

  /* --- Colour · Intertek chrome, the outer edges ------------------------ */

  --itk-black:     #130C0E;   /* the signature warm near-black, not pure black */
  --itk-gold:      #FFC700;   /* the accent. Never body text: 1.56:1 on white */
  --itk-amber:     #FFB80C;
  --itk-orange:    #F26B43;
  --itk-teal:      #21B6D7;
  --itk-navy:      #1F2C77;
  --itk-charcoal:  #474E54;
  --itk-silver:    #A3A6A9;   /* text ONLY on black (7.90). 2.45 on white */
  --itk-white:     #FFFFFF;
  --itk-offwhite:  #F6F6F6;
  --itk-smoke:     #ECEDEE;

  /* Intertek's own supplementary colours. The Oxford interior borrows these
     two, which is why the two palettes meet instead of clashing. */
  --itk-tan:       #C09E78;   /* brand "Warm Tan" */
  --itk-sage:      #739987;   /* brand "Sage Green" */

  /* --- Colour · Oxford interior, further in ----------------------------- */

  --ox-paper:      #F4EEE4;   /* the page of the scene */
  --ox-paper-top:  #efe7db;   /* darker stop of the scene's two-stop gradient */
  --ox-paper-vid:  #EAE6DC;   /* paper under the video shelf's 5% green wash */
  --ox-card:       #FFFDF8;   /* the detail panel, a shade above paper */
  --ox-oxblood:    #6E2639;
  --ox-green:      #2F4A3C;
  --ox-walnut:     #5A3E28;
  --ox-brass:      #B08D57;   /* metal furniture. Non-text: 3.04 on card */
  --ox-tan:        var(--itk-tan);
  --ox-sage:       var(--itk-sage);

  /* THREE levels of brown text, not four (DP-11). ox-action folded into
     ox-body and ox-quiet folded into ox-muted, because two of the original
     four were near-identical doing near-identical jobs and nobody would have
     remembered which was which. Each rung left has a job you can say aloud. */
  --ox-ink:        #2A2118;   /* what matters: serif headings and body */
  --ox-body:       #5F5142;   /* what is read: panel copy, quiet button label */
  --ox-muted:      #6F5D48;   /* what is glanced at: meta, labels, counts,
                                 italic subtitles, unselected chip text.
                                 DP-10 B. Was #7d6b55, which failed AA at
                                 4.43 on paper, 4.17 on the gradient band and
                                 4.10 under the video wash. Now 5.46/5.13/5.05. */

  /* Text that sits ON the coloured objects */
  --ox-spine-ink:  #F6EFE4;   /* light label on a dark spine */
  --ox-spine-dark: #3D2C1C;   /* dark label on a tan spine */
  --ox-free:       #3F6151;   /* the free-tool affirmative */

  /* Worst-case stops of the spine gradients, tokenised so the contrast
     checker can read them. A gradient is only as legible as its worst point:
     the LIGHTEST stop when the text is light, the DARKEST when it is dark.
     Testing the midpoint is how a gradient passes on paper and fails on
     screen. */
  --spine-ox-lightest: #7D2B40;
  --spine-gr-lightest: #3A5A49;
  --spine-nv-lightest: #2C3A86;
  --spine-tn-darkest:  var(--itk-tan);

  /* --- Colour · status ---------------------------------------------------
     No new hex values: status reuses existing primitives as FILLS, because
     every brand colour that reads as a status fails as text on white
     (gold 1.56, orange 3.02, sage 2.62). A filled chip with black or white
     text passes comfortably; coloured text on white does not. Selection and
     status must never rest on colour alone in any case - the pattern set
     carries fill, weight and a glyph as well. */

  --status-on:     var(--ox-green);     /* on track      · white text, 9.70 */
  --status-risk:   var(--itk-gold);     /* at risk       · black text, 12.35 */
  --status-off:    var(--ox-oxblood);   /* off track     · white text, 10.50 */
  --text-danger:   var(--ox-oxblood);   /* form errors   · 10.50 on white */
  --text-success:  var(--ox-green);     /* confirmations · 9.70 on white */

  /* --- Colour · surfaces and gradients ---------------------------------- */

  --scene-bg:  linear-gradient(180deg, var(--ox-paper-top) 0%, var(--ox-paper) 42px, var(--ox-paper) 100%);
  --plank-bg:  linear-gradient(180deg, var(--ox-tan), var(--ox-walnut));

  /* --- Type · families ---------------------------------------------------
     Sans for the Intertek chrome, serif for the Oxford interior. Georgia is
     installed on Ben's machine and on every Windows build, so the serif needs
     no webfont. */

  --font-sans:  "Inter", Calibri, "Segoe UI", Arial, sans-serif;
  --font-serif: Georgia, "Times New Roman", serif;
  --font-data:  Calibri, "Inter", "Segoe UI", Arial, sans-serif;
  --font-mono:  Consolas, "Cascadia Mono", monospace;

  /* --- Type · scale ------------------------------------------------------
     Derived from docs/showcase/showcase.css rather than copied from it, and
     the distinction is a finding rather than a quibble: SPEC-4 says to borrow
     the showcase's type scale, but the showcase HAS no scale. It has twenty
     one-off sizes (0.78, 0.8, 0.82, 0.84, 0.85, 0.9, 0.92, 0.95, 1, 1.04,
     1.06, 1.1, 1.42, 1.85, clamp...). Those cluster into a ladder, and this
     is that ladder made explicit, staying close to the sizes Ben has been
     reading approvingly for a fortnight.

     The four sketches likewise use ad-hoc sizes (10.5, 11, 11.5, 12.5, 13.5).
     Mapping them onto these rungs happens in WAVE 2, surface by surface, and
     anywhere the snap is visible it is Ben's call, not a silent tidy. */

  /* 10px is the floor. Added at step 5 when the comparison tool flagged five
     off-scale sizes carried over from the sketches (9.5, 10, 10.5, 11.5px).
     Rather than granting each one an exception, ONE rung was added and the rest
     snapped UP to the nearest rung - upward because every one of them was small
     label text, where bigger is the safer error. The check finding this in our
     own frozen output on its first run is the check working. */
  --fs-4xs: 0.625rem;   /* 10px · badge text, avatar initials, card reference */
  --fs-3xs: 0.6875rem;  /* 11px · shelf labels, spine text, micro-meta */
  --fs-2xs: 0.75rem;    /* 12px · counts, captions, chip text */
  --fs-xs:  0.8125rem;  /* 13px · dense table cells, synopses */
  --fs-sm:  0.875rem;   /* 14px · secondary copy, form help text */
  --fs-md:  1rem;       /* 16px · body. The base everything is relative to */
  --fs-lg:  1.125rem;   /* 18px · lead paragraphs, card titles */
  --fs-xl:  1.375rem;   /* 22px · section headings */
  --fs-2xl: 1.75rem;    /* 28px · page titles */
  --fs-3xl: clamp(1.9rem, 4.4vw, 2.6rem);  /* hero, the showcase's own clamp */

  /* --- Type · weight, leading, tracking --------------------------------- */

  --fw-regular:  400;
  --fw-medium:   500;
  --fw-semibold: 600;
  --fw-bold:     700;

  --lh-tight: 1.2;    /* headings */
  --lh-snug:  1.35;   /* card titles, multi-line labels */
  --lh-base:  1.55;   /* body. The sketches' own value */
  --lh-loose: 1.7;    /* long-form prose blocks */

  --ls-tight:  -0.01em;  /* large headings only */
  --ls-normal: 0;
  --ls-label:  0.08em;   /* small caps-ish labels */
  --ls-caps:   0.11em;   /* uppercase shelf labels */

  /* --- Spacing -----------------------------------------------------------
     A 4px grid. Two reasons rather than convention: the showcase's own
     spacing clusters on even numbers around these values (4, 8, 12, 16, 20,
     40 are its most frequent), and 4px increments are exactly Bootstrap's
     0.25rem spacer steps, so our spacing and the framework's agree instead of
     fighting - which matters because the grid is Bootstrap's (D66). */

  --sp-1:  4px;
  --sp-2:  8px;
  --sp-3:  12px;
  --sp-4:  16px;
  --sp-5:  20px;
  --sp-6:  24px;
  --sp-8:  32px;
  --sp-10: 40px;
  --sp-12: 48px;
  --sp-16: 64px;

  /* --- Radius ------------------------------------------------------------
     3px is the showcase's own --radius and the sketches' default. Small on
     purpose: the Intertek brand reads as authoritative rather than friendly,
     and heavy rounding fights that. */

  --radius-sm:   2px;   /* spines, planks, tight chips */
  --radius:      3px;   /* the default: buttons, inputs, badges */
  --radius-md:   6px;   /* cards and panels */
  --radius-pill: 999px; /* filter chips */

  /* --- Shadow ------------------------------------------------------------
     Warm-tinted rather than neutral black: rgba(42,33,24,...) is the Oxford
     ink, so shadows on paper look like shadows in the scene rather than grey
     smudges over it. */

  --shadow-xs: 0 1px 3px rgba(42, 33, 24, 0.22);
  --shadow-sm: 0 2px 6px rgba(42, 33, 24, 0.24);
  --shadow-md: 0 3px 8px rgba(42, 33, 24, 0.26);
  --shadow-lg: 0 4px 12px rgba(42, 33, 24, 0.16);

  /* The one inset shadow: the soft recess where the Intertek chrome hands
     over to the Oxford scene (DP-1, settled). */
  --shadow-recess: inset 0 9px 14px -9px rgba(42, 33, 24, 0.45);

  /* Two DIRECTIONAL shadows, added at wave 2 for the shelf. The four above are
     symmetrical and cannot say "the light is above and slightly left", which is
     the whole illusion a shelf of upright objects depends on. */
  --shadow-shelf: 0 8px 8px -8px rgba(42, 33, 24, 0.35);  /* objects onto plank */
  --shadow-spine: 2px 2px 5px rgba(42, 33, 24, 0.28);     /* spine onto its neighbour */

  /* The open tool's halo. Gold, and deliberately NOT the green the filter chips
     use: DP-9 makes those two independent controls, so they must not look like
     one control (design/sketches/p4-enablement-surfaces-v4.html, DP-9 B). */
  --glow-open: 0 0 0 6px rgba(255, 199, 0, 0.16);

  /* --- Layout ------------------------------------------------------------ */

  --wrap: 1180px;   /* the content measure the four sketches were judged at */
  --focus-ring: 0 0 0 3px rgba(255, 199, 0, 0.45);

  --topbar-rule: #372e31;  /* the hairline between navigation groups */
  --avatar-bg:   #3A3134;  /* the account initials disc */

  /* --- Support values ----------------------------------------------------
     Every one of these was originally written as a literal inside a component
     rule, and every one is tokenised here instead. The reason is mechanical
     rather than tidiness: the step 6 token check verifies that a rendered page's
     computed colours all come from the frozen token set, so a literal buried in
     a component is either a check failure or a reason to weaken the check. The
     first time this mattered was here, at step 5, with nine of them. */

  --edge-gold:    #e0ae00;   /* the darker edge of a gold button */
  --edge-chip:    #c3b39a;   /* unselected chip border on paper */
  --edge-quiet:   #b9a789;   /* the clear-filter control's border */
  --edge-empty:   #cdc4b6;   /* the dashed border of an empty state, plain */
  --edge-empty-s: #d9cbb4;   /* the same on the library paper, and the solid
                                edge of the Tool Shed's detail panel. One value,
                                two uses, deliberately not duplicated under a
                                second name */
  --press-danger: #5c1f2f;   /* destructive button, pressed or hovered */

  /* Added at wave 2, surface 1 (the Tool Shed). Additions only: no existing
     token above was changed, because changing one is a design amendment. */

  --spine-tn-lightest: #CDAB86;  /* the LIGHT stop of the tan spine's gradient.
                                    Tokenised for completeness rather than for
                                    the contrast checker: a tan spine carries
                                    DARK text, so its worst case is the DARKEST
                                    stop, which is --spine-tn-darkest above and
                                    is already a declared pair. */
  --spine-gloss: rgba(255, 255, 255, 0.28);  /* the hairline of light down a
                                    spine's hinge. A pseudo-element, so no
                                    checker reaches it - tokenised anyway so it
                                    is not the one literal left in the file */
  --fade-vid: linear-gradient(90deg, rgba(234, 230, 220, 0), rgba(234, 230, 220, 0.96));
                                 /* the right-hand fade telling you the carousel
                                    continues. rgb(234,230,220) IS --ox-paper-vid,
                                    so the fade ends in the surface it sits on */
  --fade-plain: linear-gradient(90deg, rgba(246, 246, 246, 0), rgba(246, 246, 246, 0.96));
                                 /* THE SAME FADE FOR A PLAIN SURFACE, added at
                                    DP-112 B (wave 2h). rgb(246,246,246) IS
                                    --itk-offwhite, which `.surface` paints, so
                                    this one ends in ITS surface on exactly the
                                    reasoning above. Two fades exist because a
                                    fade must end in the colour behind it, and
                                    the carousel now sits on two grounds. */

  /* --- the example-text grey, and it is LEGAL IN EXACTLY ONE PLACE ----------
     Added at wave 2b for DP-35 A: the "e.g." examples inside form fields.
     Ben's choice, from four rendered candidates at real size.

     4.73:1 on --itk-white, which PASSES WCAG 2.1 AA and therefore D68. That
     number holds because a placeholder only ever sits INSIDE an input, and
     `.inp` is white.

     THE CONSTRAINT IS THE WHOLE REASON THIS COMMENT IS LONG. The same value on
     --itk-offwhite, the page background, is 4.37 and FAILS. So this token is
     legal on white and nowhere else, and in particular it must NEVER be used for
     `.hint`, `.fset__note`, `.actions__note` or anything else sitting on the
     surface - those keep --itk-charcoal at 7.82. Using it one element outside an
     input turns a passing page into a failing one, and the token check would not
     catch it, because the value IS a token: only tools/contrast-pairs.tsv knows
     which backgrounds it is declared against.

     This is DP-10's lesson as a live constraint rather than a war story: a colour
     is not checked by checking the colour, it is checked by checking the pairs.

     "EXACTLY ONE PLACE" MEANS ONE PLACE AS *TEXT*, and that needed saying once
     `--border-control` below arrived carrying the identical hex. The two are not
     the same rule and they are not in conflict:

         as TEXT on --itk-offwhite     4.37, and SC 1.4.3 asks 4.5. FAILS.
         as a BORDER on --itk-offwhite 4.37, and SC 1.4.11 asks 3.0. PASSES.

     So the value is legal as a control outline anywhere and legal as text only on
     white. Two tokens rather than one alias, deliberately: they mean different
     things, they answer to different thresholds, and either could move without
     the other. */
  --text-eg: #6E747A;

  /* --- the control outline, and it is O48's answer (D-number pending; SPEC-5
         ruling 10, Ben, 25 Aug 2026) -------------------------------------------
     The resting border of every text input, select, checkbox, radio and
     input-group addon in the application.

     WHAT WAS WRONG, MEASURED ON RENDERED PAGES RATHER THAN REASONED. Two
     families were failing, and the O48 row named only one of them:

         section 6 bridges --bs-border-color to --itk-smoke, which is 1.08:1 on
         the off-white page ground - live on production since Phase 2, on the
         login page every pilot user meets first;
         and `.inp`, this project's OWN text input, drew --itk-silver at 2.26:1.

     Both are under the 3:1 that SC 1.4.11 asks of a control outline. A ruling
     that had moved only the Bootstrap bridge would have fixed the radios and left
     every text field failing - `.inp` is used 41 times on gate-capture, 31 on
     system-health, 21 on submit and 4 in the pattern set.

     WHY THIS VALUE. The whole frozen token set was swept for the lightest neutral
     clearing 3:1. #6E747A is 4.37:1 on off-white and 4.73:1 on white, and it is
     the only neutral between silver, which fails, and charcoal, which clears at
     7.82:1. Both figures are proved rather than asserted: they are rows in
     tools/contrast-pairs.tsv, so contrast.py computes them on every run.

     CHARCOAL WAS REJECTED FOR A DECISIVE REASON RATHER THAN AN AESTHETIC ONE.
     `.inp:focus` below already sets border-color to --itk-charcoal. Charcoal at
     REST would make resting and focused borders identical and destroy the focus
     affordance on the form carrying the most consequential decision in the
     system. That is why the lightest passing neutral is the right answer here and
     the darkest is not.

     NOT-COLOUR-ALONE IS NOT OFFENDED, and this was checked rather than waved: a
     SELECTED control is filled --ox-green, white-ticked and bold, so selection
     never rested on the outline. What was thin is the UNSELECTED state, which is
     the one that tells a reviewer a choice is still open. */
  --border-control: #6E747A;

  /* Message tints. Pale enough that the text sitting on them keeps its
     contrast; each pair is in tools/contrast-pairs.tsv. */
  --tint-success: #EAF1ED;
  --tint-warning: #FFF8E0;
  --tint-danger:  #F7ECEF;

  /* --- the section band, added at wave 2c (the Idea Board) ----------------
     The ground behind a section label on the Idea Board's leverage-point list
     (DP-47 B). An ADDITION: no existing token is changed, which is what keeps
     this out of freeze-amendment territory.

     IT EXISTS BECAUSE BEN CAUGHT A REAL FAULT BY LOOKING. The band was first
     built on --itk-smoke, and --itk-smoke against the page's --itk-offwhite is
     1.085:1 - very nearly the same colour, so it was a box you could not see.
     His words: make it "slightly darker so it contrasts a little more with the
     background".

     THE DISCRIMINATING NUMBER IS NOT TEXT CONTRAST, and saying so matters
     because the obvious check is the wrong one here. Black on any candidate grey
     is about 14:1 and passes trivially; the question is whether the BAND is
     distinguishable from the PAGE. So both pairs are declared in
     contrast-pairs.tsv - the label as a text pair at 15.73, and the band against
     the page as a nontext row at 1.137, informational but recorded, because a
     value chosen for its visibility should have that visibility written down.

     BEN CHOSE THIS SHADE FROM THREE RENDERED AT REAL SIZE with their measured
     ratios beside them, 11 Aug 2026, and he took the LIGHTEST of the three. Worth
     recording because the recommendation was the middle one at 1.262: he wanted
     the band visible, not weighty. The other two were #DADDE0 at 1.262 and
     #D1D5D8 at 1.366.

     For scale: --itk-silver would be 2.26 and reads as a grey slab, and the nav
     separator Ben left alone measures 1.47. */
  --sec-band: #E6E8EA;

  /* --- the value-confidence ladder (surface 8, DP-87 A) -------------------
     ONE HUE AT FOUR STRENGTHS - darker reads as more certain, which is the
     thing being shown - and never the status palette: gold already means "at
     risk" and green already means "on track", and a dashboard is exactly where
     a RAG badge and a confidence rung sit in one row. That is the collision
     Ben ruled on at Governance, arriving from a different direction. The
     legacy chart convention ("plan/forecast grey or yellow; actuals cyan") is
     half-kept: --itk-teal IS that cyan and serves as the tracked rung; the
     gold half is deliberately not followed, for the reason above.
     The first palette expansion since Submit added --text-eg. Adopted by
     Ben's own instructions, 18-19 Aug 2026 (the segmented gauge and bars both
     name these colours). Text pairs measured, in tools/contrast-pairs.tsv:
     white on verified 11.13 · black on teal 8.04 · black on evidenced 12.53 ·
     black on banded 16.37. The banded chip against white is 1.18 - a SHAPE
     that vanishes, not a D68 failure - so everything drawn in it carries a
     hairline in its component rule. */
  --conf-verified:  #12414C;   /* Verified - realised I-Revenue  (white text) */
  --conf-tracked:   var(--itk-teal);   /* High - tracked actuals */
  --conf-evidenced: #8FDCEC;   /* Medium - evidence-backed range */
  --conf-banded:    #D6F1F7;   /* Low - banded hypothesis · hairline required */

/* --- the stage ramp (DP-113 A) --------------------------------------------
   Ben ruling, 21 Aug 2026: *"each box should be coloured and the colour sjould
   get darker as it progress along the boxes (but remian the same type of colour
   - e.g. light blue to dark blue ... use ITK colors)"*, and then **option A**,
   which REUSES THE CONFIDENCE LADDER'S OWN TEAL.

   THE COST WAS PUT TO HIM AND HE ACCEPTED IT, so it is recorded rather than
   re-argued: these values also mean *banded / evidence-backed / tracked /
   verified* on the Portfolio Dashboard and on How-It-Works, so one teal ramp now
   carries two meanings across the system - which is the shape of the fault A-3
   fixed for the status badges. What makes it tolerable, and it is his reasoning
   rather than mine: **the ladder never appears on Home**, so nothing collides on
   any single screen. The collision is between pages, not within one.

   THE TOKENS ARE ALIASED RATHER THAN USED DIRECTLY, and that is the whole of the
   care here. A Phase 5 template reading `--conf-tracked` on a stage box would
   reasonably conclude the box means "tracked actuals". Aliasing to `--stage-2`
   keeps the VALUE shared and the MEANING local, and it is also what makes the
   ruling reversible: pointing five aliases somewhere else is one edit, where
   hunting `--conf-*` out of the banner would be five.

   ONE NEW VALUE ONLY. The ladder has four rungs and there are five stages, so
   `--stage-3` is new; the other four are aliases.

   TEXT COLOUR IS SET PER STEP FROM A MEASUREMENT, AND THIS TOOK TWO PASSES. The
   first pass measured only the stage TITLE and missed that the small STAGE label
   is `--itk-charcoal`, which FAILS from step 2 onward - 3.51 on `--stage-2`
   against D68's 4.5 floor. Caught by looking at the render, not by the check,
   because `compare.py` verifies that a colour IS a token and never that a
   token PAIR is legible. Every ratio below is in `tools/contrast-pairs.tsv`. */
  --stage-0: var(--conf-banded);      /* #D6F1F7 · title black, label charcoal 7.15 */
  --stage-1: var(--conf-evidenced);   /* #8FDCEC · title black, label charcoal 5.48 */
  --stage-2: var(--conf-tracked);     /* #21B6D7 · title black, label BLACK    8.04 */
  --stage-3: #1B7C93;                 /* NEW     · title white, label WHITE    4.83 */
  --stage-4: var(--conf-verified);    /* #12414C · title white, label SMOKE    9.50 */
}


/* --------------------------------------------------------------------------
   3. Base
   --------------------------------------------------------------------------
   Deliberately thin. Bootstrap's reboot has already normalised the document;
   this only applies the token layer to it. Anything here that is not simply
   "use the token" belongs in a component below.
   -------------------------------------------------------------------------- */

body {
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  line-height: var(--lh-base);
  color: var(--itk-black);
  background: var(--itk-offwhite);
  -webkit-font-smoothing: antialiased;
}

/* One visible focus style for the whole application. Keyboard navigation is NOT
   claimed under D68, whose scope is text contrast only - but removing focus
   outlines without replacing them would be an active harm, so the ring exists
   even though nothing audits it yet. */
:focus-visible {
  outline: 2px solid var(--itk-gold);
  outline-offset: 2px;
  box-shadow: var(--focus-ring);
}


/* --------------------------------------------------------------------------
   4. The shell
   --------------------------------------------------------------------------
   FROZEN ONCE (SPEC-4 step 4). Every one of the eleven surfaces inherits this
   from templates/base.html, so it is written here exactly once. Freezing it per
   page would guarantee eleven slightly different targets for one shared
   template, which is blindspot finding 12.

   Ruled by Ben, 6 Aug 2026, through the annotation loop:
     DP-13  grouped navigation WITH a More overflow. Primary pages grouped so
            they read as the system's spine; auxiliary pages in More.
            Order: Home | Idea Board Submit | Dashboard Governance |
                   Tool Shed Videos Training | More
     DP-14  active state is white text on a gold underline - the signal carries
            position and weight as well as colour, never colour alone, which is
            the same rule DP-9 applied to the filter chips
     DP-15  account control is an initials circle plus the name
     DP-16  option C: the four primary GROUPS filter by role, and More is
            ALWAYS present. Ben's reasoning: More will fill with other pages as
            the system ages, so a stable shape is worth more than an empty menu
            is a cost. NOTE FOR PHASE 5 - the filtering itself is template
            logic, not CSS. This stylesheet only guarantees the shape survives
            a group being absent.
   -------------------------------------------------------------------------- */

.shell__top {
  background: var(--itk-black);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  min-height: 44px;
}

/* The Intertek mark. The REVERSED (white) asset is used here because the topbar
   is near-black: the supplied dark asset is #140F10 ink, which against the
   #130C0E bar is invisible except for its two gold dots. Measured, not guessed.
   Provenance and how the reversed file was derived: docs/static-assets.md. */
.shell__logo {
  height: 26px;
  width: 26px;
  flex: 0 0 auto;
  display: block;
}

.shell__wm {
  color: var(--itk-white);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}

.shell__nav {
  margin-left: auto;
  display: flex;
  align-items: stretch;
  min-width: 0;

  /* O52, DP-108 A. Ben ruling, 21 Aug 2026. WITHOUT THIS THE NAV OVERLAPS THE
     ACCOUNT BLOCK below a 1106px viewport - text printing through text, not
     truncation - because `min-width: 0` lets this flex item shrink below its
     content while its links carry `white-space: nowrap` and nothing clipped or
     scrolled. Measured on the signed `_shell.html` at 980px: 120px of overflow
     and `More` rendered on top of `Ben Bielby`. Present since the wave-1 freeze
     on 6 Aug and missed because no artefact had ever been rendered below the
     width it was reviewed at.

     ABOVE 1106px THIS CHANGES NOTHING, and that is the property that let it be
     applied to an unfrozen stylesheet that eleven signed artefacts serve: with
     no overflow there is no scrollbar and no layout effect. Proven by the full
     eleven-artefact sweep before and after, not by reading this rule.

     `overflow-y` IS WRITTEN EXPLICITLY rather than left out. A single
     `overflow-x: auto` would promote the computed `overflow-y` from `visible` to
     `auto` per the CSS overflow spec, which is a second scroll axis nobody asked
     for in a 44px bar. Stating both keeps the promotion from happening by
     accident.

     DP-16 C IS NARROWED BY THE SAME RULING, NOT BROKEN BY IT. Ben: More being
     "always present" means it is always IN the nav, never that it is always on
     screen - "it is there, you just need to scroll to it". Recorded because
     `More` is the last item and therefore the FIRST to scroll out of view, so a
     Phase 5 session reading the old wording would reasonably call this rule a
     violation of it.

     ONE PHASE 5 CONSEQUENCE, named here because it is cheap now and a debugging
     session later: a scroll container CLIPS its overflow, so when More becomes a
     real dropdown its panel must NOT be a descendant of `.shell__nav` - it needs
     to hang off `.shell__top` or be positioned outside the scroller, or it will
     be cut off by this rule. Carried on the O52 register row too. */
  overflow-x: auto;
  overflow-y: hidden;
}

/* A group is a set of destinations that belong together. The hairline is the
   only thing separating them, so it must survive being looked at quickly. */
.nav-group { display: flex; align-items: stretch; gap: 2px; }
.nav-group + .nav-group {
  margin-left: 7px;
  padding-left: 9px;
  border-left: 1px solid var(--topbar-rule);
}

.shell__nav a {
  color: var(--itk-silver);
  text-decoration: none;
  font-size: var(--fs-2xs);
  padding: 7px 9px;
  white-space: nowrap;
  display: flex;
  align-items: center;
  border-bottom: 3px solid transparent;
}
.shell__nav a:hover { color: var(--itk-white); }

/* DP-14. Three signals at once: colour, weight, and a position marker. */
.shell__nav a.is-active {
  color: var(--itk-white);
  font-weight: var(--fw-bold);
  border-bottom-color: var(--itk-gold);
}

.nav-more {
  color: var(--itk-silver);
  font-size: var(--fs-2xs);
  padding: 7px 9px;
  white-space: nowrap;
  display: flex;
  align-items: center;
  cursor: pointer;
  background: none;
  border: 0;
}
.nav-more::after { content: "\25be"; margin-left: 5px; font-size: var(--fs-4xs); }

.shell__account {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-left: var(--sp-3);
  padding-left: var(--sp-3);
  border-left: 1px solid var(--topbar-rule);
  color: var(--itk-silver);
  font-size: var(--fs-2xs);
  white-space: nowrap;
}
.shell__account::after { content: "\25be"; font-size: var(--fs-4xs); }

.shell__avatar {
  width: 24px;
  height: 24px;
  border-radius: var(--radius-pill);
  background: var(--avatar-bg);
  color: var(--itk-white);
  font-size: var(--fs-4xs);
  font-weight: var(--fw-bold);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}

.shell__foot {
  background: var(--itk-black);
  color: var(--itk-silver);
  font-size: var(--fs-3xs);
  padding: var(--sp-2) var(--sp-4);
  display: flex;
  gap: var(--sp-4);
}
.shell__foot .shell__foot-right { margin-left: auto; }

/* --- the footer sits at the BOTTOM on a short page (amendment A-9) --------
   Ben ruling, 26 Aug 2026, at the Phase 5 surface-2 sign-off. Found at surface 1
   and deliberately left out of A-7, because he ruled the underline and not this;
   surface 2 made it worth landing, since three of its six states are short pages
   and on each the footer floated mid-screen with page background below it.

   `app.css` had NO `.shell` rule at all before this. That is not an oversight in
   the freeze: a mockup stacks its states inside `.mock-frame` blocks, so no
   frozen artefact has ever had a `.shell` that owns the viewport, and the
   question could not arise until a real page did.

   WHY THE MODIFIER RATHER THAN `.shell` OR `body > .shell`, AND THE OBVIOUS
   ANSWER WAS MEASURED AND REJECTED RATHER THAN REASONED ABOUT. Putting this on
   `.shell` makes every state block in all thirteen artefacts a full viewport
   tall. `body > .shell` looks safe because twelve artefacts nest the shell
   inside a frame - and it is NOT, because `dashboard.html` and
   `system-health.html` put three `.shell` blocks each STRAIGHT INTO `<body>`,
   which a probe across all thirteen found and reading two of them did not.
   `.shell--app` appears in zero frozen artefacts, so the rule is a no-op on
   everything signed by construction rather than by luck.

   `main` has to grow or the column has nothing to push the footer down with. */
.shell--app { display: flex; flex-direction: column; min-height: 100vh; }
.shell--app > main { flex: 1 0 auto; }


/* --------------------------------------------------------------------------
   5. Page surfaces
   --------------------------------------------------------------------------
   DP-12 B, ruled by Ben: the Oxford library reaches ONLY the library pages.
   Tool Shed, Video Library and Training get `.surface--scene`; the other eight
   surfaces get `.surface`. His words: "to keep it cleaner for the pages outside
   the library."

   His standing SECOND choice is paper everywhere with serif only in the
   library, logged in docs/design-targets.md. Reversing to it is a change to
   `.surface`'s background and heading font and nothing else - which is only
   true for as long as no page hardcodes a colour instead of using a token.
   That is what the step 6 token check protects.
   -------------------------------------------------------------------------- */

.surface { background: var(--itk-offwhite); padding: var(--sp-5); }

.surface--scene {
  background: var(--scene-bg);
  box-shadow: var(--shadow-recess);   /* DP-1: the soft recess at the handover */
  padding: var(--sp-5);
}

.surface__title { margin: 0 0 3px; font-size: var(--fs-xl); font-weight: var(--fw-semibold); }
.surface__sub   { margin: 0 0 var(--sp-4); font-size: var(--fs-xs); color: var(--itk-charcoal); }

.surface--scene .surface__title { font-family: var(--font-serif); color: var(--ox-ink); }
.surface--scene .surface__sub {
  font-family: var(--font-serif);
  color: var(--ox-muted);
  font-style: italic;
}


/* --------------------------------------------------------------------------
   6. The Bootstrap bridge
   --------------------------------------------------------------------------
   Bootstrap is vendored and its grid is ours to use (D3, D66). But its DEFAULTS
   are literal hex values, and an un-overridden Bootstrap component is the exact
   drift the Phase 5 token check exists to catch. This was not hypothetical: at
   step 2 the real sign-in page rendered its submit button in Bootstrap blue
   #0d6efd, measured in the live DOM.

   So this section maps Bootstrap's own variables onto our token layer, rather
   than fighting the framework with overrides. Anything Bootstrap renders that we
   have not explicitly designed still comes out brand-coloured - which matters
   because base.html already renders `.alert`, and Phase 5 will render forms.

   The Intertek brand skill asks for exactly this: "apply the palette and type
   scale through Bootstrap's own variables rather than fighting the framework."
   -------------------------------------------------------------------------- */

:root {
  --bs-body-font-family: var(--font-sans);
  --bs-body-font-size: var(--fs-md);
  --bs-body-line-height: var(--lh-base);
  --bs-body-color: var(--itk-black);
  --bs-body-bg: var(--itk-offwhite);
  --bs-border-color: var(--itk-smoke);
  --bs-border-radius: var(--radius);
  --bs-emphasis-color: var(--itk-black);
  --bs-secondary-color: var(--itk-charcoal);
}

/* LINKS, and the previous version of this was DEAD CONFIGURATION that read as
   working. It set `--bs-link-color` and `--bs-link-hover-color`, and Bootstrap
   5.3 reads NEITHER: its rule is

       a { color: rgba(var(--bs-link-color-RGB), var(--bs-link-opacity, 1)); }

   so it resolves from an RGB TRIPLET variable, and the two we were setting were
   simply unread. Every plain <a> in this application therefore rendered
   Bootstrap blue #0d6efd, measured in the live DOM at wave 2b.

   THIS IS THE SECOND INSTANCE OF THE SAME DEFECT and it is why the token check
   exists: at SPEC-4 step 2 the real sign-in page rendered its submit button in
   that same blue. Nothing found by reading our own stylesheet could catch either,
   because the offending value is not in our stylesheet.

   Why it survived wave 1 and wave 2a: no frozen surface until Submit carried a
   plain Bootstrap-styled <a>. The shell's nav links are painted by
   `.shell__nav a`, and the library pages' links are `.btn-cw` or `.vid`, so all
   four signed artefacts compared clean at 0 off-token values - correctly, because
   the fault genuinely was not on them. Re-verified as unchanged after this fix.

   Fixed by overriding the element rule with the TOKEN rather than by writing the
   triplet Bootstrap wants. A triplet cannot be computed from a hex custom
   property in CSS, so the alternative was `47, 74, 60` duplicated beside
   `--ox-green` and free to drift from it silently, which is exactly what the
   support-value tokens in section 2 exist to prevent. Class-based components
   still win on specificity, so `.btn-cw--quiet` and friends are untouched.

   ONE RESIDUAL, named rather than left to be discovered: Bootstrap's `.link-*`
   utility classes still read the `-rgb` variables. No surface uses one, and if
   one ever does it needs the triplet form adding here. */
a { color: var(--ox-green); }
a:hover { color: var(--ox-ink); }

/* Bootstrap's semantic names do not map cleanly onto ours - its "primary" is a
   link-blue, its "danger" a fire-engine red - so the three we actually use are
   pinned to our tokens through Bootstrap's own per-component variables. */
.btn-primary {
  --bs-btn-bg: var(--itk-gold);
  --bs-btn-border-color: var(--edge-gold);
  --bs-btn-color: var(--itk-black);
  --bs-btn-hover-bg: var(--itk-amber);
  --bs-btn-hover-border-color: var(--edge-gold);
  --bs-btn-hover-color: var(--itk-black);
  --bs-btn-active-bg: var(--itk-amber);
  --bs-btn-active-color: var(--itk-black);
  --bs-btn-disabled-bg: var(--itk-gold);
  --bs-btn-disabled-color: var(--itk-black);
  --bs-btn-disabled-border-color: var(--edge-gold);
}
.btn-secondary {
  --bs-btn-bg: transparent;
  --bs-btn-border-color: var(--itk-charcoal);
  --bs-btn-color: var(--itk-black);
  --bs-btn-hover-bg: var(--itk-smoke);
  --bs-btn-hover-border-color: var(--itk-charcoal);
  --bs-btn-hover-color: var(--itk-black);
}
.btn-danger {
  --bs-btn-bg: var(--ox-oxblood);
  --bs-btn-border-color: var(--ox-oxblood);
  --bs-btn-color: var(--itk-white);
  --bs-btn-hover-bg: var(--press-danger);
  --bs-btn-hover-border-color: var(--press-danger);
  --bs-btn-hover-color: var(--itk-white);
}

/* THE RESTING BORDER OF THE WHOLE FORM-CONTROL FAMILY (O48, SPEC-5 ruling 10).
   Bootstrap draws all four of these from --bs-border-color, which this section
   bridges to --itk-smoke: 1.08:1 on the off-white page ground, which is a control
   outline you cannot see. Measured on a Django-rendered <select> through the
   project's own render_field filter, not on a mockup.

   THE BRIDGE VARIABLE ITSELF IS DELIBERATELY LEFT ALONE. Twenty-two Bootstrap
   rule blocks read --bs-border-color - tables, cards, dropdowns, list groups -
   and only the four below are controls in the SC 1.4.11 sense of "identifying a
   control". Darkening the variable would have darkened every hairline in the
   framework to fix four of them, which is a design change wearing an
   accessibility fix's clothes. So the ruling is applied to the four by name.

   THESE ARE PLAIN-CLASS SELECTORS AND THAT WAS VERIFIED RATHER THAN ASSUMED,
   because this exact file has been caught twice writing a bridge override that
   silently lost the cascade: Bootstrap declares the checkbox's RADIUS as
   `.form-check-input[type=checkbox]`, class-plus-attribute, so a bare class
   override loses. Its BORDER, however, comes from the plain `.form-control` /
   `.form-select` / `.form-check-input` / `.input-group-text` rules, and app.css is
   linked after bootstrap.min.css, so equal specificity plus later position wins.
   Confirmed by reading the computed border colour off a rendered control, which is
   the only evidence that settles a cascade question. */
.form-control,
.form-select,
.form-check-input,
.input-group-text {
  border-color: var(--border-control);
}

/* The focus ring on inputs, so a keyboard user gets gold rather than blue. */
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
  border-color: var(--itk-charcoal);
  box-shadow: var(--focus-ring);
}
.form-check-input:checked {
  background-color: var(--ox-green);
  border-color: var(--ox-green);
}
/* Two more un-overridden Bootstrap defaults on the checkbox, both found by the
   token check on Submit's team grid rather than by reading anything: its radius
   is `.25em`, which computes to 4px and is not a rung on our radius scale, and it
   carries the user agent's `color: black` rather than our warm near-black. Small
   values, and the check is right to refuse them: 4px beside 3px everywhere else
   is the kind of drift nobody sees and nobody can unsee once told.

   THE RADIUS SELECTOR MATCHES BOOTSTRAP'S OWN SPECIFICITY DELIBERATELY. Its rule
   is `.form-check-input[type=checkbox]`, which is a class plus an attribute, so a
   bare `.form-check-input` override loses the cascade silently. The first attempt
   here did exactly that, and the token check reported the identical 4px again,
   which is the second time in one surface that a fix of mine was caught not
   taking effect. A bridge override has to be written against what the framework
   actually declares, not against what it seems to declare. */
.form-check-input {
  color: var(--itk-black);
}
.form-check-input[type=checkbox] {
  border-radius: var(--radius-sm);
}
/* THE RADIO'S RADIUS, AND IT IS THE THIRD MEMBER OF THE SAME FAMILY. Bootstrap
   declares `.form-check-input[type=radio] { border-radius: 50% }`, and 50% is a
   PERCENTAGE, so it is not a rung on our radius scale and `compare.py` reads it
   as an off-token length. Gate capture (DP-58 B, ruled 17 Aug 2026) is the first
   surface in the build to carry a radio at all, which is why nine surfaces went
   by without meeting it - exactly how O42's `<option>` arrived.

   FIXED BY MAPPING IT ONTO A TOKEN RATHER THAN BY WIDENING WHAT THE CHECK WILL
   ACCEPT. `--radius-pill` is 999px, and on a 16px control 999px IS a circle, so
   the radio looks identical and the token check stays strict. Adding "50%" to a
   surface's allowedLengths would have been the other route and it is worse: it
   would license any 50% radius anywhere on that page.

   The selector matches Bootstrap's own specificity - class plus attribute - for
   the reason the checkbox comment above records: a bare `.form-check-input`
   override loses the cascade silently, and that mistake was made once already. */
.form-check-input[type=radio] {
  border-radius: var(--radius-pill);
}

/* base.html renders these four on every page that carries a Django message. */
.alert-info    { --bs-alert-bg: var(--itk-smoke);  --bs-alert-border-color: var(--itk-silver); --bs-alert-color: var(--itk-black); }
.alert-success { --bs-alert-bg: var(--tint-success);           --bs-alert-border-color: var(--ox-green);   --bs-alert-color: var(--ox-green); }
.alert-warning { --bs-alert-bg: var(--tint-warning);           --bs-alert-border-color: var(--itk-gold);   --bs-alert-color: var(--itk-black); }
.alert-danger  { --bs-alert-bg: var(--tint-danger);           --bs-alert-border-color: var(--ox-oxblood); --bs-alert-color: var(--ox-oxblood); }


/* --------------------------------------------------------------------------
   7. The pattern set
   --------------------------------------------------------------------------
   FROZEN ONCE (SPEC-4 step 5). Every surface is assembled from these, so each
   page becomes an arrangement of known parts rather than a fresh set of
   decisions. Ruled by Ben through the annotation loop, 6 Aug 2026:

     DP-17  empty state B everywhere, with C (the object) on the three LIBRARY
            pages only - they already speak the object language, the eight plain
            surfaces would need one invented
     DP-18  B, four button levels: primary, secondary, quiet, destructive
     DP-19  B, zebra-striped tables - the Intertek brand system's own table rule,
            and easier to track across a wide row like the Decision Log

   Named `-cw` rather than reusing Bootstrap's class names, deliberately: a page
   must be able to carry a plain Bootstrap component AND a branded one without
   one silently restyling the other. The bridge above handles the former.
   -------------------------------------------------------------------------- */

/* --- buttons, four levels (DP-18 B) ------------------------------------- */
.btn-cw {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-sans);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  line-height: 1;
  padding: 9px 15px;
  border-radius: var(--radius);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.btn-cw--primary   { background: var(--itk-gold); color: var(--itk-black); border-color: var(--edge-gold); }
.btn-cw--primary:hover { background: var(--itk-amber); color: var(--itk-black); }
.btn-cw--secondary { background: transparent; color: var(--itk-black); border-color: var(--itk-charcoal); }
.btn-cw--secondary:hover { background: var(--itk-smoke); color: var(--itk-black); }
.btn-cw--quiet     { background: transparent; color: var(--itk-charcoal); text-decoration: underline; }
.btn-cw--quiet:hover { color: var(--itk-black); }

/* Destructive. For REMOVING DATA, and not for judging an idea: a gate Stop is a
   normal and valuable outcome, so it wears a status badge, never this. Written
   down because the mistake is easy and the message it sends is wrong. */
.btn-cw--danger    { background: var(--ox-oxblood); color: var(--itk-white); border-color: var(--ox-oxblood); }
.btn-cw--danger:hover { background: var(--press-danger); color: var(--itk-white); }

.btn-cw:disabled, .btn-cw--disabled { opacity: .45; cursor: not-allowed; }

/* --- card --------------------------------------------------------------- */
.card-cw {
  background: var(--itk-white);
  border: 1px solid var(--itk-smoke);
  border-radius: var(--radius);
  padding: var(--sp-3);
}
.card-cw--accent { border-left: 3px solid var(--itk-gold); }
.card-cw__ref  { font-size: var(--fs-4xs); letter-spacing: .06em; color: var(--itk-charcoal); margin: 0 0 3px; }
.card-cw__t    { font-size: var(--fs-2xs); line-height: var(--lh-snug); margin: 0 0 var(--sp-2);
                 color: var(--itk-black); font-weight: var(--fw-medium); }
.card-cw__meta { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }

/* --- status badge ------------------------------------------------------
   Fills reuse existing primitives, so status introduces no new hex. Every
   brand colour that reads as a status FAILS as text on white (gold 1.56,
   orange 3.02, sage 2.62), which is why status is a filled chip and never
   coloured text. */
.badge-cw {
  display: inline-block;
  font-size: var(--fs-4xs);
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  font-weight: var(--fw-semibold);
}
.badge-cw--on      { background: var(--status-on);   color: var(--itk-white); }
.badge-cw--risk    { background: var(--status-risk); color: var(--itk-black); }
.badge-cw--off     { background: var(--status-off);  color: var(--itk-white); }
.badge-cw--neutral { background: var(--itk-smoke);   color: var(--itk-charcoal); }

/* A FIFTH BADGE, ADDED 18 Aug 2026 BY BEN RULING AT GOVERNANCE, and it exists to
   say "we decided not to continue" WITHOUT saying "something went wrong".

   HIS TWO REASONS, AND THE SECOND IS A DEFECT I HAD MISSED. First, oxblood reads as
   negative, and this system's whole message about the funnel is that a Stop is a
   normal and valuable outcome - DP-18 says so in the stylesheet, the pattern set's
   own note says so, and Ben restored the Stop-is-not-a-failure sentence at Project
   Detail because it "enforced a key concept the system is built on". A red badge
   was quietly arguing the opposite. Second, and this is the sharper one: on the
   Governance page oxblood ALREADY means "this review is overdue", so the same fill
   was carrying two unrelated meanings on one screen - one a deliberate decision,
   the other a problem. His words: "it's the same as the overdue colour which can be
   confusing when on the same page."

   USED FOR AN ENDING, WHEREVER ONE IS SHOWN. A gate `Stop` and a Track 2
   `Rejected` in the Governance log's Decision column, and - since 18 Aug 2026 -
   the `Stopped` STATUS badge on a stopped record. `--itk-charcoal` as a fill at
   white text is 8.45:1 and the pair was ALREADY declared in
   tools/contrast-pairs.tsv ("totals row, muted fill button"), so this adds no
   contrast row and needs no new token.

   EXTENDED THE SAME DAY IT WAS ADDED, BY A SECOND BEN RULING: "extend the charcoal
   badge to project detail too" - closing O49, which was opened at the Governance
   freeze precisely because the first ruling left one idea wearing two colours on
   two signed pages. That is freeze amendment A-3, and it reached FOUR files rather
   than the one he named, for a reason worth stating: the pattern set's sample
   decision log ALSO drew a `Stop` in oxblood, and that page is the catalogue Phase
   5 copies from, so leaving it would have moved the contradiction rather than
   removed it.

   OXBLOOD KEEPS ITS OWN MEANING AND DID NOT SHRINK TO NOTHING. `--status-off` is
   still the RAG "off track" fill, still the overdue fill on the Governance
   calendar, and still `--text-danger`. THE LINE IS NOW CLEAN: oxblood means
   something is WRONG - late, off track, in error - and charcoal means something has
   ENDED. A Stop is an ending, not a fault, which is the whole point. */
.badge-cw--ended   { background: var(--itk-charcoal); color: var(--itk-white); }

/* --- filter chip and row (DP-9, already settled) -----------------------
   Selection carries fill, white text, bold weight AND a tick. Never colour
   alone, so it survives a colour-blind reader and a bad monitor.

   `text-decoration: none` IS AMENDMENT A-7 (Ben ruling, 26 Aug 2026) AND IT IS
   THE FIRST RULE IN THIS FILE ADDED BECAUSE A BUILT PAGE NEEDED IT RATHER THAN
   BECAUSE A DESIGN ROUND ASKED FOR IT. Every frozen artefact draws a chip as a
   `<span>`, because a static mockup has nothing to click; the `cursor: pointer`
   two lines up is the design saying it always meant a control. Phase 5 makes it
   a real `<a>` - `docs/design-targets.md` requires exactly that of the Tool Shed
   by name, so the open tool and the video filter can compose as `?tool=X&video=Y`
   - and at that moment Bootstrap's reboot underlines it.

   WHY IT HAD TO BE THE STYLESHEET RATHER THAN THE TEMPLATE. The alternative that
   was in the tree for one iteration was an inline `style` attribute on each
   control, which works and is invisible to `check-freeze.py`. It also meant the
   drawn appearance of a frozen component living in a template, which is the one
   thing D66 exists to prevent: this file is what production serves and what the
   token check reads, so a component whose real appearance is somewhere else is a
   component the next surface reinvents from a picture.

   THE MEASUREMENT THAT FOUND IT, because no mechanical check could. The token
   layer reads colour, radius, font size and typeface. It does not read
   `text-decoration`, so the Tool Shed passed all four D67 layers - 64 of 64
   assertions, zero off-token - while drawing an extra rule beside every tool name
   on the shelf. Found by rendering the page and looking at it, then measured
   properly by probing computed style on the frozen artefact and on the Django
   page: `none` there, `underline` here. Recorded as a gap in the ORACLE and not
   only as a defect in a page.

   IT CANNOT CHANGE ANY SIGNED PAGE, and that is checked rather than argued: no
   frozen artefact carries an `<a class="chip-cw">`, an `<a class="spine">` or an
   `<a class="activebar__clear">` - all 141 chips across seven artefacts are
   `<span>`, and a span was never underlined. So the sweep at A-7 returns the
   same thirteen element counts and the same twelve control counts as before it.

   This file already does exactly this seven times over - `.shell__nav a`,
   `.btn-cw`, `.trackcard`, `.crumb a` and three more - so the amendment is the
   file's own practice reaching three components that were never links before. */
.chip-cw {
  display: inline-block;
  font-size: var(--fs-3xs);
  letter-spacing: .04em;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--edge-chip);
  color: var(--ox-muted);
  background: var(--itk-white);
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;   /* A-7 */
}
.chip-cw--on { background: var(--ox-green); border-color: var(--ox-green);
               color: var(--itk-white); font-weight: var(--fw-bold); }
.chip-cw--on::before { content: "\2713\00a0"; }

.filterrow { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 0 0 var(--sp-2); }
.filterrow__lb { font-size: var(--fs-2xs); color: var(--ox-muted); margin-right: 4px;
                 min-width: 52px; font-weight: var(--fw-semibold); }

/* Appears ONLY when something is genuinely filtered. A bar that is always there
   stops being noticed, and the state that causes support tickets is the one
   where a page looks empty and nobody remembers why. */
.activebar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--itk-white);
  border: 1px solid var(--ox-green);
  border-left: 4px solid var(--ox-green);
  border-radius: var(--radius-sm);
  padding: 7px 12px;
  margin: 2px 0 var(--sp-2);
  font-size: var(--fs-2xs);
  color: var(--ox-green);
}
/* `text-decoration: none` is amendment A-7, for the reason recorded in full on
   `.chip-cw` above: this is a `<span>` in every frozen artefact and a real link in
   every built page, and Bootstrap underlines the second one. Clearing a filter is
   the most link-shaped control on the page, so it was always going to be an `<a>`. */
.activebar__clear {
  margin-left: auto; border: 1px solid var(--edge-quiet); color: var(--ox-body);
  background: var(--itk-white); padding: 2px 9px;
  border-radius: var(--radius-pill); font-size: var(--fs-3xs); cursor: pointer;
  text-decoration: none;   /* A-7 */
}
/* So a short shelf is never mistaken for a short library. */
.resultcount { font-size: var(--fs-2xs); color: var(--ox-muted); font-style: italic; }

/* --- table (DP-19 B, zebra) ---------------------------------------------
   The brand system's own table rule: black header row, alternating white and
   off-white body rows. Wide rows like the Decision Log are easier to track
   across, which is the page tables exist for here.

   A table NEVER becomes cards at narrow widths - it scrolls inside its own
   container. Ruled with DP-19: turning rows into cards halves the rows per
   screen on the one page whose whole job is scanning a long history. */
.tbl { width: 100%; border-collapse: collapse; font-size: var(--fs-2xs); background: var(--itk-white); }
.tbl th {
  text-align: left;
  background: var(--itk-black);
  color: var(--itk-white);
  padding: 8px 10px;
  font-size: var(--fs-3xs);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  font-weight: var(--fw-bold);
}
.tbl td { padding: 8px 10px; vertical-align: middle; color: var(--itk-black); }
.tbl tbody tr:nth-child(even) { background: var(--itk-offwhite); }
.tbl-scroll { overflow-x: auto; }

/* --- empty state (DP-17) -----------------------------------------------
   ONE pattern, applied everywhere. At launch the database holds eight teams,
   one user, one tool and zero ideas, so every data-bearing surface opens empty
   and a first-time visitor cannot tell an empty system from a broken one.

   The copy answers three questions in order: is this broken (no - the border is
   deliberate), what appears here, and what do I do next. The middle sentence
   also explains the surface's MECHANISM, which is a job the surface otherwise
   has to do with a populated example nobody has yet. */
.empty {
  border: 1px dashed var(--edge-empty);
  border-radius: var(--radius-md);
  padding: var(--sp-8) var(--sp-6);
  text-align: center;
  background: var(--itk-white);
}
.empty__t { font-size: var(--fs-lg); font-weight: var(--fw-semibold);
            margin: 0 0 var(--sp-2); color: var(--itk-black); }
.empty__p { font-size: var(--fs-sm); color: var(--itk-charcoal);
            margin: 0 auto var(--sp-4); max-width: 52ch; }

/* DP-17 C, on the THREE LIBRARY PAGES ONLY. They already speak the object
   language, so the illustration is nearly free there; the eight plain surfaces
   would need one invented, which is why they use `.empty` unadorned. */
.surface--scene .empty { background: var(--ox-card); border-color: var(--edge-empty-s); }
.surface--scene .empty__t { font-family: var(--font-serif); color: var(--ox-ink); }
.surface--scene .empty__p { color: var(--ox-body); }
.empty__art { margin: 0 auto var(--sp-4); display: block; }

/* --- form field states -------------------------------------------------
   The error state carries a THICKER BORDER as well as a colour, for the same
   reason DP-14 chose an underline over gold text: never colour alone. */
.fieldwrap { margin-bottom: var(--sp-4); }
.fieldwrap > label { display: block; font-size: var(--fs-2xs);
                     font-weight: var(--fw-semibold); margin-bottom: 5px; }
/* Styled BY CLASS, not as a descendant of `.fieldwrap`, and the change is worth
   its comment because the previous form was a silent trap. These two rules read
   `.fieldwrap .hint` and `.fieldwrap .err` until 8 Aug 2026, so help text placed
   anywhere else - inside a card, beside a summary, under a heading - fell back to
   16px body copy with no warning anywhere.

   The half that made it dangerous: THE TOKEN CHECK CANNOT SEE IT. 16px is
   --fs-md and the inherited colour is --itk-black, so both are in the frozen
   token set and `compare.py` returns PASS on a visibly wrong page. It was found
   by rendering the Submit variants and looking at them, and a probe over every
   `.hint` and `.err` on that page then measured eight at 11px and one at 16px.

   Fixed at the cause rather than asserted per surface, because the trap applies
   to every remaining surface that carries help text, and because a defect made
   IMPOSSIBLE beats one that has to be detected nine more times. Non-regressive by
   construction: these are the only two rules targeting either class in this
   stylesheet and Bootstrap defines neither, so computed values inside a
   `.fieldwrap` are unchanged. Verified as such against the frozen pattern set,
   before and after. */
.hint { font-size: var(--fs-3xs); color: var(--itk-charcoal); margin-top: 4px; }
.err  { font-size: var(--fs-3xs); color: var(--text-danger);
        margin-top: 4px; font-weight: var(--fw-semibold); }
/* THE RESTING BORDER IS --border-control, NOT --itk-silver (O48, SPEC-5 ruling
   10). Silver computes 2.26:1 against this field's own white fill and 2.45:1 on
   white, both under the 3:1 SC 1.4.11 asks of a control outline. The token's own
   comment in section 2 carries the reasoning and why charcoal was refused - it is
   what `:focus` below uses, so charcoal at rest would erase the focus state. */
.inp {
  width: 100%;
  font-family: var(--font-sans);
  font-size: var(--fs-2xs);
  padding: 8px 10px;
  border: 1px solid var(--border-control);
  border-radius: var(--radius);
  background: var(--itk-white);
  color: var(--itk-black);
}
/* The in-field example (DP-35 A, ruled 10 Aug 2026 against the two alternatives:
   the example below the field in the hint, or both).

   One rung SMALLER than the field's own text and italic, both on Ben's ruling, so an
   example cannot be mistaken for a value that is already filled in. It carries an
   explicit "e.g." prefix for the same reason, which is his wording.

   `opacity: 1` is load-bearing rather than tidy: Firefox applies opacity .54 to
   placeholders by default, which would quietly multiply the 4.73:1 we just measured
   down to roughly 2.6 and undo the whole colour decision on one browser. Chrome does
   not, so this is exactly the kind of difference that ships unnoticed.

   THE ACCEPTED COST, recorded rather than re-argued: a placeholder VANISHES on the
   first keystroke, so the example is gone the moment somebody acts on it. That was put
   to Ben as the main argument for putting the example below the field instead, and he
   ruled A anyway. The hint under each field still carries the standing instruction, so
   what disappears is the illustration and not the guidance. */
.inp::placeholder {
  font-style: italic;
  font-size: var(--fs-3xs);
  color: var(--text-eg);
  opacity: 1;
}

.inp:focus { outline: 2px solid var(--itk-gold); outline-offset: 1px; border-color: var(--itk-charcoal); }
.inp--err  { border-color: var(--text-danger); border-width: 2px; }
.inp--ok   { border-color: var(--ox-green); }
.inp:disabled { background: var(--itk-smoke); color: var(--itk-charcoal); cursor: not-allowed; }


/* --------------------------------------------------------------------------
   8. The library furniture · wave 2, surfaces 1 and 2
   --------------------------------------------------------------------------
   The shelf, the spines, the detail panel and the video carousel. Used by the
   Tool Shed and, in part, by the Video Library - the only two surfaces of the
   eleven that get the Oxford treatment plus objects (DP-12 B, DP-17 C).

   Ruled 4-5 Aug 2026 on the four pre-freeze sketches and entering the freeze
   automatically (SPEC-4, "the nine ruled sketch decisions"):
     DP-1  a soft recess where the Intertek chrome hands over. Already in
           `.surface--scene` above; nothing to add here
     DP-2  a BUILT shelf: a plank with a shadow, and no wood texture anywhere
     DP-3  tools are book SPINES, and the detail opens on CLICK, not hover,
           because click also works on a phone
     DP-5  ONE video shelf, scrolling sideways, filtered by tool. No second
           shelf, and no objects on it
     DP-6  the eight shelf objects are automatic SPACERS - code, not content -
           at roughly one per two or three real items, on TOOL shelves only
     DP-7  the detail panel sits BELOW the shelf, so the shelf stays put and you
           can click along a row and compare
     DP-8  the padlock means the tool is PAID, which is a property of the tool
           and identical for everyone. It never means "locked to you"
     DP-9  the open tool and the video filter are INDEPENDENT controls, so they
           are given different colours in different regions: gold on the shelf,
           green in the video rail

   Two things this section deliberately does NOT do.
   It does not restyle `.chip-cw`, `.filterrow`, `.activebar`, `.resultcount`,
   `.btn-cw` or `.empty`: those are frozen, and the Tool Shed uses them as they
   are. And it introduces `.tag-cw` rather than another `.chip-cw` modifier,
   because a filter chip means "you can pick this" and a tool tag means "this is
   a fact about the tool" - which is the same confusion DP-9 spent a round
   avoiding between the two controls.
   -------------------------------------------------------------------------- */

/* --- the legend, which is the page's whole permission model in one line --- */
.shedkey {
  font-size: var(--fs-3xs);
  color: var(--ox-muted);
  margin: 0 0 var(--sp-5);
}
.shedkey svg { vertical-align: -2px; margin-right: 3px; }

/* --- the shelf (DP-2: built, not textured) -------------------------------
   The plank is a pseudo-element rather than a border, so the objects standing
   on it can cast `--shadow-shelf` DOWN onto it. A border would sit on top of
   the shadow and the illusion would collapse. */
.shelf { position: relative; padding: 0 4px 13px; margin-bottom: var(--sp-12); } /* A-14: shelves ARE sections - Ben's judgment, taken shown the reach on a second surface */
.shelf::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 5px;
  height: 8px;
  background: var(--plank-bg);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
}
.shelf__label {
  font-family: var(--font-serif);
  font-size: var(--fs-3xs);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--ox-muted);
  margin: 0 0 var(--sp-2) 2px;
}
.shelf__row {
  display: flex; align-items: flex-end; flex-wrap: wrap;
  gap: 13px;
  min-height: 100px;
  padding-bottom: 7px;
  box-shadow: var(--shadow-shelf);
}

/* --- a tool, as a spine (DP-3) -------------------------------------------
   38px is wide enough for 11px vertical type and narrow enough that a dozen
   fit across the measure. `padding-bottom` is what the padlock stands in. */
.spine {
  position: relative; flex: 0 0 auto;
  width: 38px; height: 122px;
  padding-bottom: 22px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 2px 3px 3px 2px;
  box-shadow: var(--shadow-spine);
  cursor: pointer;
  /* The spine clips its own label. Without this a long name does not merely
     truncate, it ESCAPES: the span is a flex item sized to its content, so the
     tail renders over the shelf label above and the plank below. Measured, not
     reasoned about - see design/sketches/p4-tool-shed-scale.html section C.
     Outline and box-shadow are unaffected by overflow, so the open tool's gold
     ring still shows. */
  overflow: hidden;
  /* `text-decoration: none` is amendment A-7, and THIS is the component where the
     omission was actually visible. DP-3 makes a tool a spine whose detail opens on
     CLICK, so the built page renders it as an `<a href="?tool=N">` while the frozen
     artefact draws a `<div>`. Underlined, and because `.spine > span` below is
     `writing-mode: vertical-rl`, the underline runs VERTICALLY - so it reads as an
     extra hairline beside every tool name rather than as a link. Two shelf crops
     side by side is what made it obvious; a probe of computed style is what proved
     it. Full reasoning on `.chip-cw` in section 7. */
  text-decoration: none;   /* A-7 */
}
/* `ToolShedItem.name` is max_length=200 and an administrator types it, so a name
   longer than the spine is not a hypothetical. Without `text-overflow` the tail
   is SILENTLY clipped mid-word and the page looks correct while lying; with it,
   a truncated name announces itself. MEASURED at 122px of spine, after the fix
   and not before it: about 16 characters fit ("Semantic Scholar" is exactly the
   limit). The target records that as a note for whoever writes the admin help
   text, because 16 is short enough that real product names will hit it. */
.spine > span {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-family: var(--font-serif);
  font-size: var(--fs-3xs);
  letter-spacing: .06em;
  white-space: nowrap;
  /* max-height is what makes the ellipsis work. Without it the span is sized to
     its content and never overflows ITSELF, so `text-overflow` has nothing to
     act on and the clipping happens one level up, silently and from both ends -
     which shows the MIDDLE of a long name and neither its start nor its end. */
  max-height: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ox-spine-ink);
}
.spine::before {
  content: "";
  position: absolute; left: 3px; top: 6px; bottom: 6px; width: 1px;
  background: var(--spine-gloss);
}
.spine--ox { background: linear-gradient(90deg, var(--spine-ox-lightest), var(--ox-oxblood)); }
.spine--gr { background: linear-gradient(90deg, var(--spine-gr-lightest), var(--ox-green)); }
.spine--nv { background: linear-gradient(90deg, var(--spine-nv-lightest), var(--itk-navy)); }
.spine--tn { background: linear-gradient(90deg, var(--spine-tn-lightest), var(--spine-tn-darkest)); }
.spine--tn > span { color: var(--ox-spine-dark); }

/* The OPEN tool. Gold, and a ring rather than a fill, so the spine keeps its
   own colour and the reader can still see which shelf it came from. */
.spine--open {
  outline: 3px solid var(--itk-gold);
  outline-offset: 2px;
  box-shadow: var(--glow-open), var(--shadow-spine);
}
/* DP-8. Paid, not personal. Free tools carry no padlock and no ask route. */
.spine__lock { position: absolute; bottom: 5px; left: 50%; transform: translateX(-50%); }

/* DP-6. A spacer the page inserts, thinned as a shelf fills. Nobody maintains
   these and nobody can forget to: they are code, not content. TOOL shelves
   only - never on the video shelf, which is cards that already fill their row. */
.obj { flex: 0 0 auto; display: block; }

/* --- the detail panel (DP-7: below the shelf) ---------------------------- */
.detail {
  background: var(--ox-card);
  border: 1px solid var(--edge-empty-s);
  border-left: 4px solid var(--itk-gold);
  border-radius: var(--radius);
  padding: 15px 17px;
  box-shadow: var(--shadow-lg);
  margin: -8px 0 var(--sp-12); /* A-14: sections join the 48px rhythm (was 24px) */
}
.detail__close { float: right; color: var(--ox-muted); font-size: var(--fs-md); cursor: pointer; }
.detail__lbl {
  display: block;
  font-size: var(--fs-4xs);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ox-muted);
  margin-bottom: 2px;
}
.detail__t { font-family: var(--font-serif); font-size: var(--fs-lg); color: var(--ox-ink); margin: 0 0 5px; }
.detail__p { font-size: var(--fs-xs); color: var(--ox-body); line-height: var(--lh-base);
             margin: 0 0 var(--sp-3); max-width: 74ch; }
.detail__acts { display: flex; gap: 7px; flex-wrap: wrap; margin-top: var(--sp-3); }

/* --- a tool tag: a FACT about the tool, never a control ------------------
   Deliberately not a `.chip-cw`. A chip is something you can pick; these are
   read-only. `cursor: default` is the second signal, so the difference survives
   a reader who is not looking closely. */
.tag-cw {
  display: inline-block;
  font-size: var(--fs-3xs);
  letter-spacing: .04em;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--edge-chip);
  background: var(--itk-white);
  color: var(--ox-muted);
  cursor: default;
  white-space: nowrap;
}
.tag-cw--paid { border-color: var(--ox-oxblood); color: var(--ox-oxblood); }
.tag-cw--free { border-color: var(--ox-sage);    color: var(--ox-free); }

/* --- the video shelf (DP-5) ----------------------------------------------
   A flat tint, NOT a translucent wash. The sketch used rgba(47,74,60,.05) over
   paper; flattened that is exactly --ox-paper-vid, which is the value the
   contrast checker holds and the token check will accept. A translucent
   background computes as rgba() and is off-token by construction. */
.videoshelf {
  background: var(--ox-paper-vid);
  border-left: 3px solid var(--ox-green);
  border-radius: 2px 0 0 0;
  padding: var(--sp-3) 0 0 13px;
}
.videoshelf__head { display: flex; align-items: baseline; gap: 11px; flex-wrap: wrap; margin: 0 0 3px 2px; }
.videoshelf__t { font-family: var(--font-serif); font-size: var(--fs-md); color: var(--ox-green);
                 margin: 0; font-weight: var(--fw-semibold); }
.videoshelf__all { margin-left: auto; }
.videoshelf__note { font-size: var(--fs-3xs); color: var(--ox-muted); font-style: italic;
                    margin: 0 0 11px 2px; }

/* --- the sideways carousel ------------------------------------------------
   Scroll snapping so a half-cut card never ends up as the resting state. The
   arrows and the fade both exist to say "this continues", because a row that
   simply stops at the edge reads as the end of the list. */
.carousel { position: relative; }
.carousel__row {
  display: flex; align-items: flex-end; gap: 15px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding: 0 44px 9px 2px;
  scrollbar-color: var(--ox-tan) transparent;
  scrollbar-width: thin;
}
.carousel__row > * { scroll-snap-align: start; flex: 0 0 auto; }
.carousel__row::-webkit-scrollbar { height: 7px; }
.carousel__row::-webkit-scrollbar-thumb { background: var(--ox-tan); border-radius: 4px; }
.carousel__arrow {
  position: absolute; top: 36%; z-index: 3;
  width: 29px; height: 29px;
  border-radius: var(--radius-pill);
  background: var(--ox-card);
  border: 1px solid var(--ox-brass);
  color: var(--ox-ink);
  font-size: var(--fs-sm);
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-sm);
}
.carousel__arrow--next { right: 2px; }
.carousel__arrow--prev { left: -6px; opacity: .35; }
.carousel__fade {
  position: absolute; right: 0; top: 0; bottom: 9px; width: 52px;
  pointer-events: none;
  background: var(--fade-vid);
}

/* --- a video card --------------------------------------------------------
   The thumbnail is a placeholder gradient in the mockup. In the built page it
   is an <img> derived from the video URL (O36), so nothing here depends on a
   stored image and a broken thumbnail cannot break the row's geometry. */
.vid { width: 264px; }
.vid__th {
  height: 148px;
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  display: flex; align-items: flex-end;
  padding: 7px;
}
.vid__dur {
  font-size: var(--fs-4xs);
  color: var(--itk-white);
  background: var(--itk-black);
  padding: 1px 6px;
  border-radius: var(--radius-sm);
}
.vid__t { font-family: var(--font-serif); font-size: var(--fs-sm); color: var(--ox-ink);
          line-height: var(--lh-snug); margin: var(--sp-2) 0 3px; }
/* THE TITLE IS THE ROUTE TO THE ITEM (amendment A-9). Ben ruling, 26 Aug 2026, at
   the Phase 5 surface-2 sign-off, taken over leaving the card inert and over
   making the whole card a link.

   THIS IS A-7 ARRIVING A SECOND TIME AND IT IS THE REASON A-7 WAS RIGHT. A
   library of thirty-nine items with no route to any of them is hard to call
   finished - D64 (c) makes the MVP "a library of LINKS" - but `.vid` is a `<div>`
   in every frozen artefact and carries no `cursor: pointer`, so unlike `.spine`
   the design never said the CARD was a control. The title is the smallest thing
   that can be one.

   The moment it becomes an `<a>`, Bootstrap's reboot gives it a link colour AND
   an underline, and `compare.py` reads neither property - so without these two
   declarations the page would draw a blue underlined serif title and every
   mechanical layer would pass it. That is the A-7 defect exactly, and it is now
   the second time on the second surface that turning a frozen component into a
   link needed the stylesheet.

   `color: inherit` is NOT a widening of the ruling. Without it the title changes
   colour, which is the opposite of what an amendment whose whole claim is "the
   drawn appearance does not move" can afford. It inherits `--ox-ink` from
   `.vid__t`, so the computed value stays a frozen token and the token layer
   still has something to check.

   NO HOVER RULE, deliberately. The pointer cursor comes from the browser and is
   affordance enough; a hover treatment is a design decision nobody has ruled,
   and widening a ruled amendment is how an amendment stops meaning what was
   ruled. Zero `<a>` elements exist inside `.vid__t` anywhere in the freeze, so
   this cannot move a signed page. */
.vid__t a { color: inherit; text-decoration: none; }
.vid__m { font-size: var(--fs-3xs); color: var(--ox-muted); margin: 0; }
.vid__syn { font-size: var(--fs-2xs); color: var(--ox-body); line-height: 1.5; margin: 6px 0 0; }

/* --- the DOCUMENT card (DP-109 A, D71's merge, wave 2 surface 10) ---------
   A document uses THE SAME `.vid` card as a video, at the same 148px band, and
   these two selectors are the whole difference. Ben ruling DP-109 A, taken
   against a recommendation for the short-band option C: three treatments were
   rendered interleaved, and A is the one with a perfectly regular grid.

   THE ARGUMENT AGAINST IT IS RECORDED BECAUSE IT WAS MADE AND OVERRULED: a
   148px band is a lot of space for something that is a link to a PDF, and a
   video's still is information where this is illustration. Ben's counter is the
   better half of it and it is why this needs no variation: a video's thumbnail
   differs per video because it is a real frame, whereas a document's band is a
   TYPE MARKER, so every document sharing one treatment is the point rather than
   repetition. That is also why there is no `--doc` colour series to match
   `--1` to `--6`.

   NEITHER RULE TOUCHES AN EXISTING SELECTOR, deliberately. `.vid__th` is frozen
   on two signed surfaces, so the positioning context goes on a new modifier
   instead of being added to it - which is what let the eleven-artefact sweep
   come back identical.

   WHY `position` AT ALL, WHICH IS THE PART A LATER READER WILL WANT: `.vid__th`
   is a flex row with `align-items: flex-end`, so an in-flow illustration would
   sit on the baseline beside the pill and push it off the left edge. Taking the
   illustration out of flow keeps `.vid__dur` as the only in-flow child, so the
   pill lands bottom-left exactly where a video's duration lands. Measured in the
   DP-109 sketch, where the in-flow version put the pill bottom-RIGHT on
   documents and bottom-LEFT on videos - a difference nobody would have chosen
   and which no check would have caught. */
.vid__th--doc { position: relative; }
.vid__art {
  position: absolute;
  left: 50%;
  top: 46%;
  transform: translate(-50%, -50%);
}

/* --- the video GRID (DP-20, wave 2 surface 2) ----------------------------
   The Video Library is a grid and the carousel is the Tool Shed's alone. Ben's
   ruling, and the reason is the page rather than the taste: a page whose whole
   job is browsing videos, laid out as one sideways row, is "a very short,
   non-intuitive page". The carousel works on the Tool Shed precisely because
   the videos there are subordinate to something else.

   Same `.vid` card in both places, so a video looks identical wherever it is
   met. The only difference is that the carousel fixes the card's width and the
   grid lets the column decide, which is what makes the grid reflow on a narrow
   window without a media query. */
.vidgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
  gap: var(--sp-5) 15px;
  margin-top: var(--sp-3);
}
.vidgrid .vid { width: auto; }

/* Placeholder thumbnail fills. Illustration standing in for a fetched image,
   which is why they are gradients of existing brand colours rather than tokens
   of their own - the same reasoning that exempts `.empty__art`. */
.vid__th--1 { background: linear-gradient(135deg, var(--spine-gr-lightest), var(--ox-oxblood)); }
.vid__th--2 { background: linear-gradient(135deg, var(--itk-navy), var(--itk-teal)); }
.vid__th--3 { background: linear-gradient(135deg, var(--itk-tan), var(--ox-walnut)); }
.vid__th--4 { background: linear-gradient(135deg, var(--itk-charcoal), var(--itk-black)); }
.vid__th--5 { background: linear-gradient(135deg, var(--itk-sage), var(--ox-green)); }
.vid__th--6 { background: linear-gradient(135deg, var(--ox-oxblood), var(--ox-ink)); }


/* --------------------------------------------------------------------------
   9. The form surface · wave 2, surface 3 (Submit)
   --------------------------------------------------------------------------
   The first surface in the build made of PROSE FIELDS rather than of objects or
   rows, so it is the first to need a measure, a multi-line field and a way to
   group questions. Every part below is either new for that reason or a reuse of
   something the pattern set already froze.

   NO NEW TOKEN, and no existing token changed. Worth stating because the two
   library surfaces each needed additions and this one needed none: Submit is
   built entirely from the frozen palette, which is also why it introduced not a
   single new row in tools/contrast-pairs.tsv.

   Ruled by Ben through the annotation loop, 8 Aug 2026, on nineteen rendered
   options at design/sketches/p4-submit-v1.html:
     DP-28 B  five required questions, and the three the schema marks blank=True
              sit behind ONE disclosure. Not two steps, and not all eight
              required, which would contradict the schema's own recorded intent
     DP-29 A  a FORK PAGE, then the form. D30 fixes one entry point and says
              nothing about the mechanism. Under D3 this app is server-rendered,
              so the rejected one-page option's cards were links that reloaded
              anyway - its advantage was appearance rather than mechanism
     DP-30 A  one column at 680px. `--wrap` stays for full-width surfaces
     DP-31 B  the team is asked and defaulted to the submitter's, and several
              teams are selectable where a project benefits more than one (O43)
     DP-33 B  the no-obligation wording sits beside the submit button
   -------------------------------------------------------------------------- */

/* --- the measure (DP-30 A) -----------------------------------------------
   A form field is not a shelf: at the 1300px this design is judged at, an
   unconstrained input is about 1250px wide, which is neither readable nor
   comfortable to type into. 680px is the measure Ben judged.

   `--wrap` is deliberately NOT used here. It exists for surfaces that spread
   (the fork page below uses it), and a form column is a different job. */
.formcol { max-width: 680px; }
.formcol--wide { max-width: var(--wrap); }

/* --- a multi-line field -------------------------------------------------
   The pattern set froze `.inp` for one line. Five of Submit's questions are
   prose, and a one-line box for a pain point tells the submitter to be brief
   about the one thing the form most wants them to think about. */
.inp--multi { min-height: 84px; resize: vertical; line-height: var(--lh-base); }
.inp--multi-sm { min-height: 60px; resize: vertical; line-height: var(--lh-base); }

/* --- a question group ---------------------------------------------------
   The reset on the legend is part of the component and not optional: Bootstrap's
   reboot floats a legend, gives it 100% width and sizes it with a viewport calc
   (`calc(1.275rem + .3vw)`), so a legend left alone is both the wrong size and
   out of flow. Found by rendering it. */
.fset { border: 0; padding: 0; margin: 0 0 var(--sp-6); min-width: 0; }
.fset__lg {
  float: none; width: auto; line-height: inherit;
  font-size: var(--fs-3xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--itk-charcoal); font-weight: var(--fw-bold); margin: 0 0 var(--sp-1);
}
.fset__note { font-size: var(--fs-3xs); color: var(--itk-charcoal); margin: 0 0 var(--sp-4); }

/* --- the track fork (DP-29 A) -------------------------------------------
   Two destinations, not a selected state: this page's whole job is to send you
   somewhere, so nothing here is ever "chosen" and there is no --on variant. The
   rejected one-page option needed one; this does not, which makes it the smaller
   component as well as the ruled one. */
.trackpick { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); margin: 0 0 var(--sp-5); }
.trackcard { display: block; text-decoration: none; padding: var(--sp-4); }
.trackcard:hover { border-color: var(--itk-charcoal); box-shadow: var(--shadow-xs); }
.trackcard__t { font-size: var(--fs-lg); font-weight: var(--fw-semibold);
                color: var(--itk-black); margin: 0 0 var(--sp-2); }
.trackcard__p { font-size: var(--fs-sm); color: var(--itk-charcoal); margin: 0; }

/* --- the optional-questions disclosure (DP-28 B) ------------------------
   A real <details> element, which is the only reason this option was available
   at all: D3 makes the MVP server-rendered, so anything needing JavaScript to
   open was not on the table until the interactivity phase.

   Dashed rather than solid, deliberately borrowing the empty state's edge: both
   say "this is deliberate, there is nothing wrong here". */
.disc { border: 1px dashed var(--edge-empty); border-radius: var(--radius-md);
        padding: var(--sp-4); margin: 0 0 var(--sp-6); background: var(--itk-white); }
.disc > summary { font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
                  color: var(--itk-black); cursor: pointer; }
.disc__p { font-size: var(--fs-3xs); color: var(--itk-charcoal); margin: var(--sp-2) 0 var(--sp-4); }

/* --- several teams (DP-31 B, and O43 is the schema half) ----------------
   Checkboxes rather than a <select multiple>, for two reasons that are both
   mechanical: a multiple-select needs a modifier key nobody discovers, and there
   are only eight teams (D11), so every option fits on screen at once and the set
   never grows. The inputs are Bootstrap's own `.form-check-input`, which section
   6's bridge already paints in --ox-green when checked, so this introduces no new
   control and no new colour.

   O43 is the open half: whether these teams are informational or co-owning
   decides whether `Idea.team` stays a ForeignKey. Nothing in this component
   depends on the answer. */
.checkgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px var(--sp-4); }
.checkgrid label { font-size: var(--fs-2xs); color: var(--itk-black); margin: 0 0 0 6px; }
.checkgrid > div { display: flex; align-items: center; }

/* --- the action row (DP-18, DP-33 B) -----------------------------------
   The note sits BESIDE the primary action, which is where Ben ruled D32's
   no-obligation wording belongs: at the moment of commitment rather than in a
   subtitle nobody reads. */
.actions { display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap;
           margin-top: var(--sp-5); }

/* The note takes the width the row actually leaves it, rather than a fixed measure.
   Ben, on the rendered page: "extend the text box so that this text fits neatly into
   two rows rather than three". It was capped at 46ch, which wrapped the no-obligation
   sentence onto three lines beside a button that was leaving plenty of room.
   The line count was MEASURED after the change rather than eyeballed, by comparing
   the element's height against its line-height, and the first attempt is why that
   matters: `flex: 1 1 auto` did give two lines, but it also made the note 680px wide,
   which means it had WRAPPED ONTO ITS OWN ROW BELOW THE BUTTON. Two lines and the
   wrong place. DP-33 B ruled this copy sits BESIDE the primary action, so
   `flex: 1 1 0` with `min-width: 0` is the correct form: a zero flex-basis makes it
   shrink into the space the button leaves instead of demanding its own line. */
.actions__note { font-size: var(--fs-3xs); color: var(--itk-charcoal); margin: 0;
                 flex: 1 1 0; min-width: 0; }


/* --------------------------------------------------------------------------
   10. The board surface · wave 2, surface 4 (the Idea Board)
   --------------------------------------------------------------------------
   The densest page in the build, and the only one made of COLUMNS OF OBJECTS
   rather than shelves, rows or fields. Fifteen design points settled it, DP-36
   to DP-47, and SEVEN of them went against the recommendation - which is worth
   noting in the stylesheet because several rules below would look arbitrary
   without it and are in fact ruled.

     DP-36 B  FOUR lanes, not six. Submitted, Ranked, In Progress, Completed,
              with Parked and Stopped on a "Not moving" strip beneath. The canon
              has six STATUS VALUES and that is untouched (pipeline/enums.py);
              what four lanes decides is how many of them get a COLUMN. Measured:
              four lanes give 306px each at 1300px, against 200px for six, and a
              card title fits two lines instead of three.
     DP-37 A  Track 2's execution ranking list on its own TAB, not a rail and not
              a section below. D61 makes its ladder disjoint from Track 1's, so it
              is emphatically NOT a seventh lane.
     DP-38 B  one meta row per card, carrying what the lane needs.
     DP-39 B  the journey track, and Ben EXTENDED it to every card in every
              column rather than only to In Progress.
     DP-40 A  an idea appears only on its OWNING team's board (O43, half one).
     DP-41 A  benefiting teams do not appear on a card at all (O43, half two).
     DP-42    a team chip row for the mechanism, and "All teams" renders the
              summary table rather than sixty-three cards in four columns.
     DP-43 A  the empty board keeps its lane headings, with the empty state below.
     DP-44 B  an empty lane on a POPULATED board gets a dashed slot - and the two
              reconcile by the slot appearing only when the board holds a card,
              which Ben agreed as proposed. Both directions are gated in the
              frozen target.
     DP-45 A  the new-card highlight is a gold ring, DP-32 B's component.
     DP-46 A  seven EQUAL segments in the track, not the grouped form.
     DP-47 B  the ranking list's section label is a band with a gold accent.

   ONE TOKEN WAS ADDED FOR THIS SURFACE AND NO EXISTING TOKEN CHANGED:
   --sec-band, and it exists because Ben caught a real fault by looking. The band
   was first built on --itk-smoke, which is 1.085:1 against the page's
   --itk-offwhite - so close to the same colour that it was a box you could not
   see. He asked for slightly darker, three shades were rendered at real size with
   their measured ratios, and he took the LIGHTEST at 1.137:1.
   -------------------------------------------------------------------------- */

/* --- the lane grid (DP-36 B) ---------------------------------------------
   `minmax(0, 1fr)` rather than `1fr`, and it is load-bearing rather than
   defensive: a bare `1fr` track takes its minimum from its content, so one long
   unbroken card title would widen its own lane and steal width from the other
   three. `min-width: 0` on the lane does the same job one level down. Without
   both, a title a person types can change the board's geometry. */
.board { display: grid; gap: var(--sp-3); align-items: start; }
.board--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.lane { min-width: 0; }

/* The lane heading. The 2px black rule is this design system's "a section starts
   here" mark, and it is deliberately the same rule DP-19 froze for a table
   header, so a labelled group of things looks the same wherever it appears. */
.lane__hd {
  display: flex; align-items: baseline; gap: 6px;
  padding-bottom: 6px; margin-bottom: var(--sp-3);
  border-bottom: 2px solid var(--itk-black);
}
.lane__t {
  font-size: var(--fs-3xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-bold); color: var(--itk-black);
}
/* The count STAYS on a lane, and that is a narrow reading of a broader
   instruction. Ben removed the counts from the ranking list's section labels -
   "people can see for themselves" - and a lane is the case that argument does not
   cover: a list of three is wholly visible, where a lane can be scrolled past. */
.lane__n {
  margin-left: auto; font-size: var(--fs-3xs);
  font-weight: var(--fw-semibold); color: var(--itk-charcoal);
}
.lane__body { display: grid; gap: var(--sp-2); }

/* --- the "Not moving" strip (DP-36 B) -----------------------------------
   Where Parked and Stopped live now that they are not columns. It degrades to
   one quiet line at zero of each, which is most of the pilot, instead of two
   permanently empty columns taking a third of the board's width. */
.closed {
  border-top: 1px solid var(--itk-smoke);
  margin-top: var(--sp-6); padding-top: var(--sp-4);
  display: flex; gap: var(--sp-6); align-items: baseline; flex-wrap: wrap;
}
.closed__t {
  font-size: var(--fs-3xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-bold); color: var(--itk-charcoal);
}
.closed__i { font-size: var(--fs-2xs); color: var(--itk-black); }

/* --- the Gate 0 rank ordinal (D33) --------------------------------------
   AN ORDINAL, NEVER A SCORE. D33 rules Gate 0 ranking as a manual rank order by
   the Team Manager with no scored rubric, re-confirmed against the deck's
   scored-ranking mockup on 3 Aug 2026. A number in a black chip reads as a
   position; anything with a scale behind it would read as a grade. */
.rankno {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 15px; height: 15px; padding: 0 4px; margin-right: 5px;
  background: var(--itk-black); color: var(--itk-white);
  border-radius: var(--radius-sm);
  font-size: var(--fs-4xs); font-weight: var(--fw-bold);
}

/* --- the journey track (DP-39 B, DP-46 A) -------------------------------
   SEVEN forward positions - Submitted, Ranked, Stages 1-4, Completed - on every
   card in every column, so a card says where it is in the whole journey rather
   than only within its lane. Ben's extension: the board collapses Stages 1-4
   into one column, and the track is what puts that information back.

   Under DP-36 B nothing else has a card, so seven is the whole set and NO CARD IS
   EVER OFF THE LADDER. That is the two rulings fitting together: with six lanes a
   Parked card would have needed a position `Idea.status` cannot supply, derivable
   from Decision rows under D59 but only at a real Phase 5 cost.

   IT IS NEVER THE ONLY SIGNAL, which is DP-14's rule that a signal never rests on
   colour alone: the lane heading names the state, an In Progress card carries its
   stage in words beside the track, and the whole thing takes an aria-label.

   13x4 with 2px gaps was arrived at by rendering four geometries and looking:
   at 11x3 the track was too faint to read at a glance. Seven segments come to
   103px, measured. */
.jt { display: inline-flex; align-items: center; gap: 2px; }
.jt__s { width: 13px; height: 4px; border-radius: var(--radius-sm); background: var(--itk-smoke); }
.jt__s--on { background: var(--ox-green); }

/* The stage in words, In Progress only - every other lane's heading already names
   the state. The right margin is Ben's: the stage label and the category tag sat
   7px apart and read as one lump, so 4px on top of the meta row's own gap takes
   them to 11px. Measured after, in a fresh browser: 28px still spare on the
   widest card and no meta row wraps. */
.card-cw__pos {
  font-size: var(--fs-4xs); color: var(--itk-charcoal); letter-spacing: .04em;
  margin-right: var(--sp-1);
}

/* --- an empty lane on a POPULATED board (DP-44 B) -----------------------
   It borrows the empty state's own dashed edge, so "this is deliberate, not
   broken" is one visual idea rather than two. It appears ONLY when the board
   holds at least one card: on a board with nothing, DP-43 A's headings plus the
   frozen empty state do the work, and four dashed slots underneath a large
   dashed empty state would be the same thing said five times. Both directions
   are asserted in idea-board.target.json. */
.lane__slot {
  border: 1px dashed var(--edge-empty); border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-2); text-align: center;
  font-size: var(--fs-3xs); color: var(--itk-charcoal);
}

/* --- the two tabs (DP-37 A) ---------------------------------------------
   Deliberately the same language as DP-14's active navigation item: dark text,
   bold weight AND a gold underline. Three signals, so selection never rests on
   colour alone here either. */
.btabs { display: flex; gap: 2px; border-bottom: 2px solid var(--itk-smoke); margin: 0 0 var(--sp-4); }
.btab {
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold); color: var(--itk-charcoal);
  padding: 8px 14px; text-decoration: none;
  border-bottom: 3px solid transparent; margin-bottom: -2px;
}
.btab--on { color: var(--itk-black); font-weight: var(--fw-bold); border-bottom-color: var(--itk-gold); }

/* --- the page header row ------------------------------------------------
   Title and subtitle left, primary action right. `.surfhd__a` takes the margin
   rather than the button so the action can be a link or a button without the
   layout caring. */
.surfhd { display: flex; align-items: flex-start; gap: var(--sp-4); }
.surfhd__a { margin-left: auto; flex: 0 0 auto; }

/* --- Track 2's execution ranking list (DP-37 A, DP-47 B) ---------------
   A LEVERAGE POINT IS A CARD, not a row, and O45 (b) is what made that possible
   rather than merely tidier. `LeveragePoint` had no reference and no title - its
   own `__str__` returns `problem_statement[:60]` - so the first build of this list
   could only show a truncated sentence in a bordered table. Ben ruled reading (b),
   so it gains a minted reference and a short title in Phase 5, which gives it the
   same three parts as an idea: reference, title, meta. It can therefore BE the
   frozen `.card-cw`, and a leverage point looks like the object it is in both
   places it appears.

   THE SINGLE BORDERED CONTAINER WITH HAIRLINE ROWS IS GONE, and so are the 2px
   black section rules an earlier round added, both on Ben's word: the rows read as
   a connected table rather than separate cards, and he preferred white space to
   dark lines. */
/* THE LIST HAS A MEASURE, and it is not decoration. A ranking list is prose a
   Team Manager reads down, so a 1195px card with four words on it strands its own
   content; 660px is the width Ben reviewed and approved. Caught by rendering the
   frozen artefact and comparing it against the sketch he signed off, which is the
   sort of difference that survives a green check: every value was a token and
   every element was present, and the page was still not the page he approved. */
.lpcol { max-width: 660px; }

.lpgroup { margin: 0 0 var(--sp-6); }
.lpgroup:last-of-type { margin-bottom: 0; }
.lpcards { display: grid; gap: var(--sp-2); }
.lp__intro { font-size: var(--fs-3xs); color: var(--itk-charcoal); margin: 0 0 var(--sp-5); }
.lp__m { font-size: var(--fs-4xs); color: var(--itk-charcoal); margin: 0; }

/* The section label (DP-47 B). Four things about it are Ben's, and the last one
   is the least obvious:
     the ground is --sec-band, because --itk-smoke was invisible against the page;
     there is NO count, because a three-item list needs none;
     the separator is an EN DASH, not a comma - which is also the one en-dash use
       this project's writing rule permits, after a label introducing elaboration;
     ONLY THE WORD LEFT OF THE DASH IS BOLD, so the rule itself is regular weight
       and the <b> carries the emphasis, rather than the whole label being bold
       and the elaboration competing with it. */
.lpsec--band {
  background: var(--sec-band); color: var(--itk-black);
  border-left: 3px solid var(--itk-gold);
  font-size: var(--fs-3xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-regular); padding: 6px var(--sp-3);
  margin: 0 0 var(--sp-3);
}
.lpsec--band b { font-weight: var(--fw-bold); }

/* --- the arrival message (DP-32 B) -------------------------------------
   The message half of "back to the Idea Board with a message". 12px: Ben first
   asked for a couple of rungs above the lane labels' 11px, which gave 13px, then
   asked for 1-2 points less. THE TWO INSTRUCTIONS ARE CONSISTENT and the reason
   is worth keeping, because it explains a whole class of feedback: the first was
   judged on a CALIBRI render, since lavish serves its artefact in a sandboxed
   frame and the self-hosted font was being refused by CORS. Inter runs visibly
   larger at the same size. So the second instruction corrected for a font, not
   for a size. */
.boardflash { font-size: var(--fs-2xs); }

/* --- the new-card highlight (DP-32 B, DP-45 A) -------------------------
   Ruled at Submit, drawn here, because Submit's job ends at the redirect.

   NO JAVASCRIPT, which matters because D3 makes this server-rendered and the
   interactivity phase is later. The requirement is met twice over and the cheaper
   half does the real work: THE CLASS IS ONLY RENDERED WHEN THE REQUEST CARRIES
   THE NEW IDEA'S REFERENCE, so the highlight cannot outlive the visit whether or
   not a timer runs. The animation is a nicety on top of that, not the mechanism -
   so if it were ever dropped the design still honours what Ben asked for.

   BUILT ON A PSEUDO-ELEMENT, AND THAT IS AN ORACLE DECISION RATHER THAN A STYLING
   ONE. `tools/compare.py` walks real elements and reads their computed values, so
   animating the CARD's own background or border would make its computed value
   depend on WHEN the check ran - exact at the start, an interpolated non-token
   near the end. A pseudo-element is not in that walk, so the card's own values
   stay constant and the check is deterministic.

   `inset: 0`, never a negative inset: a pseudo-element pulled outside its
   parent's padding box makes the card's scrollWidth exceed its clientWidth, which
   `tools/render.py` correctly reports as clipped content. It did, on the first
   run. A box-shadow paints outside the box WITHOUT joining layout, so the ring
   still sits proud of the card edge and nothing overflows.

   No new token: --itk-gold and --glow-open were both already frozen, so this came
   in under the "roughly ten lines and one new token" it was costed at when DP-32 B
   was ruled.

   Three caveats recorded when it was costed, all still true: the timer counts time
   ON THE PAGE rather than time since submitting, and a background tab may throttle
   it, so 35 seconds is approximate by nature; `prefers-reduced-motion` keeps the
   highlight for the page view instead of animating it, which still satisfies "mark
   the transition, do not become permanent"; and a refresh with the marker still in
   the URL restarts it. */
.card-cw--new { position: relative; }
.card-cw--new::after {
  content: ""; position: absolute; inset: 0; border-radius: var(--radius);
  pointer-events: none;
  box-shadow: 0 0 0 3px var(--itk-gold), var(--glow-open);
  animation: newcard 35s linear forwards;
}
@keyframes newcard { 0%, 82% { opacity: 1; } 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .card-cw--new::after { animation: none; }
}


/* --------------------------------------------------------------------------
   11. The record surface · wave 2, surface 5 (Project Detail)
   --------------------------------------------------------------------------
   THE FIRST SURFACE WHOSE EMPTY STATE IS A PROGRESSION RATHER THAN A PRESENCE.
   Every surface before this one had two states, populated and empty. A project
   has SEVEN positions on one ladder, and a Stage 0 record and a Stage 4 record
   are the same page showing wildly different amounts: no gates, no KPIs, no
   value model, no I-Revenue and no documents at Stage 0; all of them at Stage 4.

     DP-48 B  THE WHOLE SKELETON, DORMANT. Every section is always present, and
              an unreached one is dimmed and carries one sentence saying what
              fills it and when. Ruled AGAINST the recommendation, which was to
              collapse the unreached sections into a single ordered strip - and
              that option's component is deliberately NOT in this file, because a
              rejected component has no business in the stylesheet production
              serves.

              THIRD CONSECUTIVE RULING OF THE SAME SHAPE, which is why it is
              worth a paragraph rather than a line: DP-43 kept the lane headings
              on a completely empty board and DP-44 put a dashed slot in an empty
              lane, both also against the recommendation. On this system a
              structure that has not happened yet is worth showing. The remaining
              surfaces should assume that rather than re-argue it.

              The cost is accepted and named: at pilot launch the database holds
              zero ideas, so EVERY project page is one real section and six
              dormant boxes, and the Stage 0 page measures 1522px against 623px
              for the show-only-what-exists option.

   NO NEW TOKEN AND NO NEW CONTRAST PAIR. Every value below was already in the
   frozen set, and every foreground/background combination was already declared
   in tools/contrast-pairs.tsv - the same thing Submit managed at section 9 and
   for the same reason: this surface is assembled from the frozen palette.

   THE TWO SNAPSHOT MARKERS ARE A D60 REQUIREMENT, not decoration. Every derived
   financial figure on this page is computed in Python and never stored, EXCEPT
   two deliberate immutable snapshots - the Gate 2 locked baseline and a pinned
   I-Revenue checkpoint. So the page must not imply ROI, payback, RAG or drift
   were typed, and the two snapshots must read as frozen records carrying a date.
   `.frz` is the second half of that; the first half is prose, and which device
   announces a computed figure is still open as DP-50.
   -------------------------------------------------------------------------- */

/* --- the way back ---------------------------------------------------------
   Project Detail has no navigation item of its own: it is reached from an Idea
   Board card, from the Governance log, or from a dashboard, so the route back is
   part of the page rather than part of the shell.

   THE SINGLE BACK LINK THAT USED TO LIVE HERE IS GONE. DP-51 B ruled a breadcrumb
   instead, so `.rec__bk` was a rejected option and is removed for the same reason
   the rail, the timeline, the page-level notice and the lane control are absent:
   this is the file production serves. The breadcrumb's rules are with the other
   round-2 components further down. One consequence, stated so it is not read as a
   defect: the round-1 and round-2 sketches still carry `.rec__bk` markup and now
   render that one element unstyled, which is correct - they are the record of a
   superseded option. */

/* --- the record header ----------------------------------------------------
   The identity D59 says is ONE record: reference, title, and the facts that
   belong to no section.

   THE JOURNEY TRACK HERE IS THE FROZEN `.jt`, AT ITS FROZEN SIZE, and that is a
   continuity decision rather than a convenience. It is the same graphic every
   Idea Board card carries (DP-39 B as Ben extended it, DP-46 A), so a card and
   the page it links to agree about what an idea looks like. The card's own rule
   applies unchanged: the track is never the only signal, and here the status
   badge beside it names the state in words. */
.rec {
  background: var(--itk-white); border: 1px solid var(--itk-smoke);
  border-radius: var(--radius-md); padding: var(--sp-4); margin: 0 0 var(--sp-12); /* A-14 */
}
.rec__top { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.rec__ref {
  font-size: var(--fs-2xs); letter-spacing: .06em;
  color: var(--itk-charcoal); font-weight: var(--fw-semibold);
}
.rec__t {
  font-size: var(--fs-xl); font-weight: var(--fw-semibold);
  line-height: var(--lh-snug); margin: var(--sp-2) 0 var(--sp-4); max-width: 60ch;
}
.rec__a { margin-left: auto; display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* FOUR FIXED COLUMNS, NOT `auto-fit`, AND THIS WAS CHANGED AFTER LOOKING AT IT.
   `repeat(auto-fit, minmax(150px, 1fr))` put seven of the full record's eight
   facts on one row and orphaned the eighth, and made the first row taller
   because one value wrapped - so the header's geometry became a function of how
   many facts a record happens to have. Four columns give 4+4 at Stage 4 and 4+2
   at Stage 0. Same lesson as DP-36's `minmax(0, 1fr)` on the lane grid, one
   level down: content must not decide the layout. */
.rec__kv {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--itk-smoke); padding-top: var(--sp-3);
}
.rec__k {
  display: block; margin-bottom: 2px;
  font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--itk-charcoal); font-weight: var(--fw-bold);
}
.rec__v { font-size: var(--fs-2xs); color: var(--itk-black); }
/* A later-stage field that is legitimately empty. D59: "later-stage fields are
   nullable until their stage is reached", so an absent I-Lead is a NORMAL state,
   and this says WHEN it fills rather than looking like a fault. */
.rec__v--none { color: var(--itk-charcoal); font-style: italic; }

/* --- a page section -------------------------------------------------------
   The 2px black rule is this system's existing "a section starts here" mark: the
   same rule DP-19 froze for a table header and DP-36 gave a lane heading, so a
   labelled group of things looks the same wherever it appears. */
/* THE CROSS-PAGE SECTION RHYTHM (A-14, Ben ruling 1 Sep 2026: "option C, FULL
   SCOPE" - his answer to "can we enforce this spacing for all sections across all
   pages?"). Five components do section-level work; all five now sit at --sp-12
   (48px), from 32px here and 24px on the Tool Shed family. Everything else at
   main's top level is page chrome and deliberately does not move. */
.pdsec { margin: 0 0 var(--sp-12); }
.pdsec:last-of-type { margin-bottom: 0; }
.pdsec__hd {
  display: flex; align-items: baseline; gap: var(--sp-2);
  /* A-14: header air scales with the 48px rhythm (6px/12px until 1 Sep 2026).
     `.lane__hd` below keeps 6px/12px on purpose - a kanban lane is a column
     header inside a board, not a section, and the ruling named sections. */
  padding-bottom: 8px; margin-bottom: var(--sp-4);
  border-bottom: 2px solid var(--itk-black);
}
.pdsec__t {
  font-size: var(--fs-3xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-bold); color: var(--itk-black);
}
.pdsec__n { margin-left: auto; font-size: var(--fs-3xs); color: var(--itk-charcoal); }

/* NO MEASURE ON EITHER OF THESE TWO, AND THAT IS A DELIBERATE EXCEPTION TO THE RULE
   WRITTEN ABOVE `.qflow` - Ben ruled it three times in one round, on the rendered
   page: "expand this text box so it is the same legnth as the table below" / "the
   same legnth as the table above" / "the same length as the table it sits below".

   THE EXCEPTION IS NARROW AND HAS A REASON. `.qf__a` is PROSE a reader reads down -
   five paragraphs of somebody's submission - and prose at 1250px is the DP-30 fault.
   These two are a CAPTION on a table: one or two lines whose left and right edges
   reading as the table's edges is the point, because that is what makes them belong
   to it rather than float above the page. A caption that stops short of its table
   looks like a mistake, which is what he was reacting to.

   So the rule stands as written for prose and this is its one carve-out. Recorded
   rather than left implicit, because the next surface will meet the same question
   and the honest answer is "it depends what the text is FOR", not "always cap it". */
.pdnote { font-size: var(--fs-3xs); color: var(--itk-charcoal); margin: var(--sp-2) 0 0; }

/* A section DESCRIPTION, between the heading rule and the content. Added at the
   round-3 annotation pass on Ben's instruction: "add a concise description of what
   the value model is underneath 'Value model' title and separator black line".

   IT SAYS WHAT THE SECTION IS, AGNOSTICALLY, and that is the distinction from
   `.pdnote` above, which explains a mechanism and which he removed in three places
   in the same round. A description is about the kind of thing; a note was about
   why the system works that way. The second turned out to be prose nobody needed
   on the page.

   Same visual job as `.lp__intro` in section 10, and the duplication is recorded
   rather than resolved: renaming a class the FROZEN idea-board.html already uses
   would be a freeze amendment, so the two are unified at step 10 when app.css
   freezes, or not at all. */
.pdsec__d {
  font-size: var(--fs-2xs); color: var(--itk-charcoal);
  margin: 0 0 var(--sp-3);
}

/* --- the frozen-snapshot marker (D60's two deliberate snapshots) -----------
   It borrows --sec-band, the ground Ben chose at DP-47, and it belongs ON THE
   PAGE rather than inside a white card: --sec-band is declared against
   --itk-offwhite in tools/contrast-pairs.tsv and that is the pair it was chosen
   for. Placed on white it would be an undeclared combination, which is DP-10's
   standing lesson - a colour is checked by checking its pairs, and the checker
   cannot know about a pair nobody declared. The one exception in use is the
   Value model table's State cell, where it sits on a zebra row. */
.frz {
  display: inline-block; background: var(--sec-band); color: var(--itk-black);
  border-left: 3px solid var(--itk-black); padding: 5px var(--sp-3);
  font-size: var(--fs-4xs); font-weight: var(--fw-semibold); letter-spacing: .04em;
}

/* --- the quality-flow spine (D32) -----------------------------------------
   The submission, in the order the form walked the submitter through it: pain
   point, the idea, value expectation, evidence, how to test.

   THE MEASURE IS NOT DECORATION, AND ITS ABSENCE WAS A REAL DEFECT IN THE FIRST
   BUILD OF THIS SURFACE. Without it this prose sets to about 1250px, which is
   the exact fault DP-30 removed from the Submit form and the exact fault that
   silently cost the Idea Board's ranking list the 660px Ben had approved. That
   is three instances, so it is now a rule rather than an anecdote: any new prose
   container in this system gets a measure written at the moment it is created,
   because NO MECHANICAL CHECK CAN ASK FOR ONE - every value on the wide version
   was a frozen token and every element was present. */
.qflow { display: grid; gap: var(--sp-4); }
.qf__q {
  margin: 0 0 3px;
  font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--itk-charcoal); font-weight: var(--fw-bold);
}
.qf__a { margin: 0; max-width: 78ch; font-size: var(--fs-2xs); color: var(--itk-black); line-height: var(--lh-base); }
/* An optional field left blank at submission. The schema allows it - a thin
   submission is a real state the form is designed to deepen - so this is a
   normal reading rather than a gap. Whether it shows at all is DP-53. */
/* AN UNANSWERED OPTIONAL FIELD SAYS ONLY "Not answered at submission." - no promise
   and no link, and the shrinking of this rule across two rounds is the useful record.

   DP-53 A originally added "This can be added at any time." At round 3 Ben asked for
   that phrase to become a hyperlink to wherever the information could be added. Trying
   to build it surfaced the problem: NO MVP SURFACE LETS ANYONE ADD TO AN IDEA AFTER
   SUBMISSION. Submit only creates, there is no edit route among the eleven surfaces,
   and who may edit is unanswered (O37(b)).

   At round 4 he ruled the honest way out: "remove the link, remove the text that
   states you can edit the info (as for MVP you can't as you've rightly stated) - then
   add this as a future roadmap item". So the sentence goes rather than being left as a
   promise the system cannot keep, and the whole capability - the text, the link, and
   per-idea edit permissions for the I-Lead and Team Manager - is a row in
   plans/future-upgrades.md with a re-entry trigger.

   THE SHAPE IS O44's, ONE SURFACE LATER: a ruled element turned out to have no MVP
   route, and the choice was between quietly relabelling it and admitting it had left
   scope. Both times he took the second. `.qf__a--none a` is therefore not in this
   file - there is no link to style. */
.qf__a--none { color: var(--itk-charcoal); font-style: italic; }

/* --- the I-Revenue headline ----------------------------------------------
   IT NAMES ITS OWN BASIS IN WORDS, and that sentence is the mechanism rather
   than the copy. Whether the portfolio reads the LATEST measured checkpoint or a
   PINNED one is still open for Phase 6, so the rule in force is stated in prose
   and the checkpoint table carries a pinned column either way. Both rules can
   therefore be shown without redrawing this page, which is what stops an open
   Phase 6 setting becoming a Phase 6 redesign. */
.hl {
  background: var(--itk-white); border: 1px solid var(--itk-smoke);
  border-left: 3px solid var(--itk-gold); border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4); margin: 0 0 var(--sp-3);
}
.hl__n { font-size: var(--fs-2xl); font-weight: var(--fw-bold); color: var(--itk-black); line-height: var(--lh-tight); }
.hl__b { margin: 4px 0 0; font-size: var(--fs-3xs); color: var(--itk-charcoal); }

/* --- DP-48 B · a section that exists but has not happened yet -------------
   The dimming carries THREE signals, not colour alone, which is DP-14's rule
   applied here: the heading text drops to charcoal, its rule drops from black to
   silver, and the body is a dashed box rather than content. The dashed edge is
   deliberately the frozen empty state's own `--edge-empty`, so "this is
   deliberate, not broken" stays one visual idea across the whole system rather
   than becoming a second one - the same argument DP-44's dashed lane slot used.

   `.dorm` CARRIES THE MEASURE for the reason written above `.qflow`: without it
   the dormant prose set to 1200px, and every mechanical check passed on it. */
.pdsec--dorm .pdsec__t { color: var(--itk-charcoal); }
.pdsec--dorm .pdsec__hd { border-bottom: 2px solid var(--itk-silver); }
.dorm {
  border: 1px dashed var(--edge-empty); border-radius: var(--radius);
  background: var(--itk-white); padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-2xs); color: var(--itk-charcoal); max-width: 78ch;
}
/* The lead clause names WHAT and WHEN; the rest explains the mechanism. That
   split is the empty state's own copy rule (DP-17) reused, because a dormant
   section has the same job: explain a surface to somebody who has never seen a
   populated example, at launch, when no populated example exists. */
.dorm b { color: var(--itk-black); font-weight: var(--fw-semibold); }

/* --- DP-49 C · the record is TABBED, and the tab bar IS the skeleton -------
   Ruled 17 Aug 2026, against the recommendation of a sticky section rail and
   against a written argument that tabs fight DP-48 B. THE TWO RULINGS RECONCILE,
   and the reconciliation is better than either option offered: every section is
   still always present and every dormant one still names the stage that unlocks
   it, but in the tab strip rather than down the page. A Stage 0 record therefore
   shows its whole skeleton in one glance in about 40px - DP-48 B's requirement
   met and DP-49 B's progress map delivered, without B's 208px of permanent width
   and without A's scroll.

   THE BAR ITSELF IS THE FROZEN .btabs/.btab FROM THE IDEA BOARD (DP-37 A),
   reused and not redrawn, so a tab means the same thing on both surfaces. Only
   the dormant state below is new.

   Five tabs, as rendered and therefore as ruled: The idea · Gates & criteria ·
   KPIs · Value & I-Revenue · Documents. Seven sections onto five, with Gates
   beside Kill criteria and the value model beside I-Revenue. `The idea` opens by
   default at every stage, because it is the one tab that is never empty.

   The accepted cost, recorded rather than glossed: a dormant tab is one you click
   to arrive at an explanation, and under D3 each tab is a server round trip or an
   anchor rather than a local toggle. */

/* `:not(.btab--on)` IS LOAD-BEARING AND ITS ABSENCE WAS A REAL DEFECT, found by
   looking at the rendered panel rather than by any check. Without it a dormant tab
   that is ALSO the open tab lost its bold weight, because both are single classes
   and the later rule won the cascade - which silently reduced DP-14's frozen
   three-signal active state to colour plus an underline. A candidate option
   reversing a ruled decision is the worst kind of drift, because it looks like
   styling. */
.btab--dorm:not(.btab--on) { font-weight: var(--fw-regular); }

/* THE STAGE LABEL IN THE TAB TITLE IS GONE, REMOVED BY BEN AT THE ROUND-3
   ANNOTATION PASS: "remove all added text to the titles here (e.g. no need for
   'stage 1' in the KPIs title or 'none yet' in the Documents tab. keep the titles
   clean". So `.btab__w` is a rejected component and is not in this file.

   THE CONSEQUENCE IS WORTH STATING PLAINLY, BECAUSE IT WEAKENS AN ARGUMENT MADE
   ONE ROUND EARLIER. The claim that DP-49 C reconciles with DP-48 B rested on the
   tab bar being a progress map: every dormant tab naming what unlocked it. With
   clean titles the bar lists the five sections but no longer says which have
   happened, so the at-a-glance map is gone and the explanations do their work
   INSIDE each tab instead - where DP-48 B's own wording puts them.

   What is left distinguishing a dormant tab is `.btab--dorm`'s weight alone, and
   weight alone at 12px is thin against this system's standing rule that a state
   never rests on one signal (DP-14, DP-9).

   DP-55, RULED 17 AUG 2026: "leave it." Put to Ben explicitly, with the one-signal
   objection stated, and he took weight alone. Recorded as a KNOWN AND ACCEPTED
   exception to that rule rather than as an oversight, because the two are
   indistinguishable in a stylesheet six months from now and only one of them is
   defensible. What makes it defensible: unlike an active nav item or a selected
   chip, a dormant tab communicates nothing a reader must act on - the tab still
   opens, and what is inside it says plainly that nothing has happened yet and when
   it will. The signal is a courtesy, not a control. */

/* --- DP-50 B · a `computed` label on the COLUMN ---------------------------
   Ruled 17 Aug 2026 over one caption per section and over one line for the whole
   page. The marker sits on the thing it describes, which is what the other two
   could not do: both named the computed figures in prose and left the reader to
   map the sentence back onto the right columns.

   D60 is the reason any of this exists: ROI, payback, NPV, progress, RAG, drift
   and the annualised I-Revenue are computed in Python on every read and never
   stored, so the page must not imply anyone keyed them. The two deliberate
   snapshots are the exception and they wear `.frz` instead.

   --itk-silver on --itk-black is a declared pair at 7.90 (the topbar's inactive
   nav item), so this needs no new row in tools/contrast-pairs.tsv. */
.th__c {
  display: block;
  font-size: var(--fs-4xs); font-weight: var(--fw-regular);
  letter-spacing: 0; text-transform: none; color: var(--itk-silver);
}

/* --- DP-51 B · a breadcrumb ----------------------------------------------
   Ruled 17 Aug 2026 over the single back link and over back-plus-neighbours.

   ONE HONEST LIMIT, RECORDED AS A NOTE RATHER THAN FIXED, because it was put to
   Ben before he ruled: the crumb asserts a path that is real when you arrive from
   a board and not when you arrive from the Governance log or a dashboard. It is
   a HOME path rather than a history, and treating it as a history is what would
   make it wrong. Phase 5 renders it from the record's own team, never from the
   referrer, so it cannot lie about where the reader came from - it simply does
   not claim to know. */
.crumb { font-size: var(--fs-2xs); color: var(--itk-charcoal); margin: 0 0 var(--sp-3); }
.crumb a { text-decoration: none; }
.crumb span { color: var(--itk-silver); margin: 0 6px; }

/* --- DP-52 A, DP-53 A and DP-54 A add NO CSS, and that is worth a line ----
   DP-52 A keeps the gate history in the frozen zebra `.tbl`, so it stays the same
   table language as the Governance Decision Log and no `.gtl` timeline component
   is introduced. DP-53 A uses `.qf__a--none`, already above. DP-54 A borrows the
   frozen `.badge-cw` statuses as they are: `--on` for Completed, `--neutral` for
   Submitted and the in-flight stages, and - SINCE 18 Aug 2026 - `--ended` for
   Stopped, where it read `--off` when this surface was signed.

   THE HISTORY IS KEPT BECAUSE THE FIRST ANSWER WAS RIGHT AND STILL GOT SUPERSEDED,
   which is not a contradiction and is worth being able to read later. A concern was
   raised to Ben that an oxblood Stop contradicts DP-18; that concern was WRONG, and
   the correction stands - DP-18's actual text says a gate Stop is a normal valuable
   outcome, that destructive STYLING is for removing data, and that "a Stop wears a
   STATUS BADGE", so the distinction DP-18 draws is button versus badge, not oxblood
   versus not. `.btn-cw--danger` is the destructive control; `.badge-cw--off` was a
   status badge sharing the fill. DP-54 A did not contradict DP-18.

   WHAT CHANGED IS NOT THAT, BUT A SECOND ARGUMENT DP-18 NEVER MADE. At Governance
   Ben found oxblood carrying TWO meanings on one screen - "we decided to stop" and
   "this review is overdue" - and ruled a fifth badge. O49 then asked whether that
   reaches this page, and he ruled it does: "extend the charcoal badge to project
   detail too." So the colour moved for a reason about the PALETTE, not about DP-18,
   and the earlier correction is still the correct reading of DP-18. Freeze
   amendment A-3. */


/* --------------------------------------------------------------------------
   12. The gate surface · wave 2, surface 6 (Gate capture)
   --------------------------------------------------------------------------
   THE FIRST SURFACE IN THE BUILD THAT WRITES RATHER THAN READS. Every surface
   before it displays records; this one creates one, and D28 makes the status
   change and the Decision row ONE act - so the whole design problem is that the
   form must not imply two saves. SPEC-3 exit row 16 proved that atomicity for
   real: a failed Decision write leaves status unchanged and no orphan row.

   Ruled by Ben, 17 Aug 2026, on eleven rendered decision points. ONE WENT
   AGAINST THE RECOMMENDATION and one came back amended:

     DP-56 A  its own page, reached from the record's "Record a gate decision"
              control, which was already frozen on all eight Project Detail
              states. Consequence worth stating: this surface therefore has NO
              page-level empty state, because it cannot be reached without a
              record - the same argument that proved Submit has none
     DP-57 C  the frozen card plus the criteria under test, so the form shows
              what the verdict is actually about
     DP-58 B  radios, each carrying its consequence. NOT a select: Submit taught
              us a select sits on its first option and looks answered, which is
              why DP-42 rejected one, and a gate verdict is the worst field in
              the system for that fault
     DP-59 B  the form STATES the resulting stage, never asks for it
     DP-60 B  one row per criterion, not one textarea
     DP-61 B  one recorded outcome per criterion due at this gate
     DP-62 C  *** AGAINST THE RECOMMENDATION *** people always, and the deciding
              body only FROM GATE 2. See `.gcwho`: the cost was named before he
              ruled and is recorded rather than re-argued
     DP-63 B  no realised value yet is said in words, with a route out of it
     DP-64 B  the commitment note, AMENDED BY BEN to sit UNDER the button across
              the full width rather than beside it. See `.actions__note--under`
     DP-65 B  Gate 2 shows what it is about to freeze, before it freezes it
     DP-66 B  every gate sets criteria for the next one

   THE MAPPING THIS SECTION IS BUILT ON CHANGED MID-DESIGN. D28 previously mapped
   kill criteria "one stage downstream" - set at Gate 1, tested at Gate 3. Ben
   amended it on 17 Aug 2026: a criterion is tested at the NEXT gate, and
   "typically" is his word. Two components below are the shape they are because of
   that, and neither would have been drawn this way a day earlier. `.critrow__s`
   is a SELECT because the tested-at gate became a property of the criterion
   rather than arithmetic on the gate that set it; and every gate carries a
   `.critrow` section because slide 51 puts the first criteria before Stage 1
   begins, which made an earlier "None at Gate 0" wrong twice over.

   NO NEW TOKEN AND NO NEW CONTRAST PAIR, the third surface running to manage both
   after Submit and Project Detail. Every value below was already frozen and every
   foreground/background pairing was already declared, so tools/contrast-pairs.tsv
   is unchanged at 61 passing pairs.

   ONE BRIDGE RULE FOR THIS SURFACE LIVES IN SECTION 6 RATHER THAN HERE,
   deliberately: the radio's border-radius. It is a Bootstrap default mapped onto a
   token, which is that section's whole job, and putting it here would hide it from
   the next person asking why a form control looks the way it does.
   -------------------------------------------------------------------------- */

/* --- the verdict group (DP-58 B) -----------------------------------------
   A VERDICT MUST NOT HAVE A DEFAULT, which is the mechanical reason this is a
   radio group and not a select. Nothing is selected until a person selects it, so
   the form cannot be submitted having silently agreed with its first option.

   Each option carries its consequence in prose beside it, because Stop / Scale /
   Continue is a rubric rather than three self-evident words - and it is not the
   same rubric at every gate: Gate 0 takes Go and No-Go, Gate 4 adds Completed. The
   consequence text is therefore per-gate CONTENT, and the frozen target asserts
   the labels rather than the explanations. */
.verdict { display: grid; gap: var(--sp-2); }
.verdict__o {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  border: 1px solid var(--itk-smoke); border-radius: var(--radius);
  background: var(--itk-white); padding: 10px var(--sp-3);
}
.verdict__b { flex: 0 0 auto; margin-top: 1px; }
.verdict__l { font-size: var(--fs-2xs); font-weight: var(--fw-semibold); color: var(--itk-black); }
.verdict__c { font-size: var(--fs-3xs); color: var(--itk-charcoal); margin: 2px 0 0; }

/* --- the consequence statement (DP-59 B) ---------------------------------
   D28 makes the status change and the log entry ONE act, so the form states what
   recording this decision will DO rather than asking for the resulting stage as a
   second field. The rejected option was not merely uglier: two fields put
   "Continue" and a new stage of "Stopped" one click apart, and the system would
   have recorded both.

   WHAT THIS COMPONENT IS FOR NARROWED AT THE ROUND-2 ANNOTATION PASS, AND THE
   RULING DID NOT MOVE. Ben removed the separate consequence box from all five
   states - "not needed and clutters the page" - and in the same batch asked for
   the resulting stage to be named inside the verdict option instead: "after
   'next stage' add '(Stage 2 PoC)'". So the form still never ASKS for the stage,
   which is all DP-59 B ruled; each option now carries its own consequence rather
   than one box carrying the consequence of whichever option happens to be
   picked, which is the better place for it and reads shorter.

   `.willdo` THEREFORE SURVIVES FOR ONE JOB ONLY: stating which gate is being
   recorded and offering the route to record a different one. Worth saying plainly
   so a later session does not reintroduce a consequence box on the grounds that
   the component obviously exists for one.

   IT IS NOT `.pdnote`, AND THE DISTINCTION IS THE ONE BEN DREW AT PROJECT DETAIL.
   A note explains how the system works, which is the prose he deleted in three
   places; this is the thing the button is about to do. Same reasoning that gave
   `.pdsec__d` its own class rather than reusing a note.

   NO 78ch MEASURE, AND THAT IS THE MEASURE RULE APPLIED RATHER THAN IGNORED. The
   rule above `.qflow` carries its own carve-out for a caption belonging to the
   thing beside it, added when Ben ruled three times in one round that a caption
   should be as wide as its table. Measured here rather than assumed: with 78ch
   this box came out 90px short of its own 680px column, three times on one page,
   and every mechanical check passed on it. */
.willdo {
  background: var(--itk-white); border: 1px solid var(--itk-smoke);
  border-left: 3px solid var(--itk-gold); border-radius: var(--radius);
  padding: 10px var(--sp-3); margin: var(--sp-3) 0 0;
  font-size: var(--fs-2xs); color: var(--itk-black);
}
.willdo b { font-weight: var(--fw-semibold); }
.willdo__n { display: block; font-size: var(--fs-3xs); color: var(--itk-charcoal); margin-top: 4px; }

/* --- a criterion being SET at this gate (DP-60 B) ------------------------
   One row per criterion, because that is what the SIGNED Project Detail kill
   criteria table describes: four columns, one row each. A single textarea was the
   cheaper option and it is dead on the merits rather than on taste - it has
   nowhere to put a per-criterion tested-at gate, so D28's amended mapping cannot
   be expressed in it at all.

   `.critrow__s` IS A SELECT AND A DAY EARLIER IT WOULD HAVE BEEN A LABEL. The old
   mapping was arithmetic, so there was nothing to choose. Ben's amendment makes a
   criterion tested at the next gate "typically", and typically means the gate
   belongs to the criterion: it defaults to the next gate and can be moved later
   where a criterion genuinely cannot be measured sooner.

   AT THE LAST SETTING GATE IT DEGRADES TO A STATED VALUE, and that is a real
   consequence rather than a tidy-up. Gate 4 is the only gate after Gate 3, so a
   Gate 3 criterion has nothing to choose, and a select with one option is a
   control that cannot be operated - Gate 3 states the gate in `.critrow__w`
   instead. Found by rendering it and seeing "Gate 4" offered twice.

   The schema half is Phase 5's, in O47's family: `Decision.kill_criteria` is one
   TextField today, and a criterion carrying its own tested-at gate and its own
   recorded outcome is its own model. */
.critrow { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: 6px; }
.critrow .inp { flex: 1 1 auto; }
.critrow__w { flex: 0 0 auto; font-size: var(--fs-3xs); color: var(--itk-charcoal); white-space: nowrap; }
.critrow__s { flex: 0 0 auto; width: 148px; }

/* --- a criterion being TESTED at this gate (DP-61 B) ---------------------
   The `Outcome` column of that same signed table, which has no field anywhere in
   the schema today. Met or Not met, plus what was actually measured, so "were the
   criteria met" stays a queryable fact rather than becoming a sentence in a notes
   field that no dashboard can ever count.

   THE CRITERION ITSELF IS READ-ONLY HERE, AND THAT IS D28 RATHER THAN CAUTION.
   Criteria are locked at the gate that sets them precisely so a project cannot
   quietly redefine success once it is in trouble. The gate that tests them judges
   them; it never edits them. So the criterion is a paragraph rather than an input,
   and there is no control on this component that could change its text. */
.crittest {
  border: 1px solid var(--itk-smoke); border-radius: var(--radius);
  background: var(--itk-white); padding: var(--sp-3); margin-bottom: var(--sp-2);
}
.crittest__c { font-size: var(--fs-2xs); color: var(--itk-black); margin: 0 0 2px; max-width: 78ch; }
.crittest__m { font-size: var(--fs-3xs); color: var(--itk-charcoal); margin: 0 0 var(--sp-3); }
.crittest__r { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.crittest__r label { font-size: var(--fs-2xs); color: var(--itk-black); margin: 0 0 0 6px; }
.crittest__f { display: flex; align-items: center; gap: 6px; }
.crittest__f .inp { width: 170px; }
/* Its own label treatment rather than borrowing `.crittest__m`. The first draft
   used the meta class, so "Measured" rendered in the same 11px charcoal as "Set at
   Gate 2" and read as a caption rather than as the label of the field beside it.
   Found by looking at the rendered page, not by any check. */
.crittest__lb { font-size: var(--fs-2xs); font-weight: var(--fw-semibold); color: var(--itk-black); margin: 0; }

/* --- who decided (DP-62 C, AGAINST THE RECOMMENDATION) -------------------
   People are always asked. The deciding body appears only FROM GATE 2, because the
   canon has the I-Board as an independent gatekeeper from that gate, and every
   source has a body - "CW Steerco" - deciding Gates 2 to 4 against named
   individuals at Gates 0 and 1.

   THE COST WAS PUT TO BEN BEFORE HE RULED AND IS RECORDED RATHER THAN RE-ARGUED.
   The schema does not make the two mutually exclusive: `decided_by_body` sits
   BESIDE the many-to-many rather than replacing it, deliberately (X5). So hiding
   the field before Gate 2 means a Gate 0 or Gate 1 decision genuinely taken by a
   group cannot be recorded as one. It also takes a position on O41, whose whole
   question is whether that independence is enforced, advisory or abandoned, and
   which is unresolved and Phase 5's. He ruled C with both named.

   THIS CLASS CARRIES NO STYLE AND EXISTS ONLY AS A HOOK, so the frozen target can
   assert the field ABSENT at Gates 0 and 1 and PRESENT from Gate 2 - both
   directions, which is what makes a mutually exclusive rule a gate rather than a
   note. Phase 5 renders it from the gate number, never from a role. */
.gcwho { display: block; }

/* --- the commitment note, AMENDED BY BEN (DP-64 B) -----------------------
   A gate decision is an audit trail: `on_delete=PROTECT` refuses to delete a
   project that has one, there is no unique constraint on gate so a second row at
   the same gate is a record rather than a duplicate, and NO MVP SURFACE EDITS A
   DECISION. So the page says what is true - it cannot be edited, and a correction
   is a second decision - rather than a comforting sentence with no route behind it.
   That trap has been walked into twice this phase (O44, and DP-53's "can be added
   at any time") and both times the promise was removed rather than relabelled.

   BEN AMENDED THE PLACEMENT AFTER SEEING IT RENDERED: "put the description under
   the button on one line if possible (make sure the text is the width of the page
   unless there is something blocking it on the right)." So it leaves the flex row
   and sits beneath it at the full width of the form column, where nothing blocks
   it - and the one-line fit is MEASURED against line-height rather than eyeballed,
   because the same note set to three lines when it sat beside the button.

   THIS IS THE SAME COMPONENT IN A DIFFERENT POSITION FROM SUBMIT, BY TWO RULINGS,
   and the duplication is avoided deliberately: DP-33 B put Submit's no-obligation
   wording BESIDE the button at the moment of commitment, and that frozen markup is
   untouched. `flex: none` is declared rather than left to inherit, because the base
   class carries `flex: 1 1 0` for its Submit position and a reader needs to see
   that this position does not use it. */
.actions__note--under {
  flex: none;
  margin-top: var(--sp-3);
}

/* --- the Gate 2 lock (DP-65 B) ------------------------------------------
   Gate 2 freezes the KPI baseline and the value model, the only two stored figures
   in a system where every other derived number is computed in Python on every read
   (D60). After this gate they cannot change.

   IT ADDS NO CSS, AND THAT IS WORTH A LINE. It is built from `.frz` - the frozen
   snapshot marker section 11 introduced for exactly these two snapshots - plus the
   frozen zebra `.tbl` and a `.pdnote`. The reviewer sees what they are freezing at
   the moment they freeze it, using components that already mean that.

   `.frz` sits on the page rather than inside a white card, which is the placement
   its own comment in section 11 requires: `--sec-band` is declared against
   `--itk-offwhite` in tools/contrast-pairs.tsv and that is the pair it was chosen
   for. Recorded so a later session does not move it onto white and create an
   undeclared combination the checker cannot know about. */

/* --- a dormant box inside a form column ---------------------------------
   `.dorm` carries `max-width: 78ch` because on Project Detail it sits in a
   full-width surface where 1200px of prose was a real defect. In a 680px form
   column the same cap leaves it 90px short of everything around it, which is the
   fault that measure exists to prevent, arriving from the other direction.

   SCOPED TO `.formcol` RATHER THAN LIFTED, DELIBERATELY: removing the cap outright
   would widen every dormant box on the SIGNED project-detail.html, and a freeze
   amendment to fix a form is the wrong trade. */
.formcol .dorm { max-width: none; }


/* --------------------------------------------------------------------------
   13. The governance surface · wave 2, surface 7 (Governance)
   --------------------------------------------------------------------------
   TWO THINGS ON ONE PAGE, FACING OPPOSITE WAYS IN TIME. The decision log is
   every decision already taken, retrospective by construction; the gate calendar
   is the reviews still to come. Slide 47's own words for the log: "Each gate
   decision recorded with date, criteria, rationale, next-gate date - audit
   trail."

   Ruled by Ben, 18 Aug 2026, on eleven rendered decision points across four
   rounds. ONE WENT AGAINST THE RECOMMENDATION and came back amended:

     DP-67 A  two tabs: Decision log, Gate calendar
     DP-68 A  seven columns on the log - EIGHT once DP-73 added the Track
     DP-69 C  a MONTH GRID rather than a list          *against the rec*
              plus his amendment: clicking a reference reveals a panel
     DP-70 A  criteria and rationale live on the project's own page
     DP-71 A  the frozen team chip row
     DP-72 B  a capped window, with the count naming the cap
     DP-73 B  both tracks in one log, with the track named
     DP-74 A  headers stay, the frozen empty state below
     DP-75 A  overdue reviews get their own section ABOVE the months
     DP-76 A  the overdue count also shows on the tab
     DP-77 A  lateness is the day count, in a badge

   A MEASUREMENT REVERSED ONE OF HIS OWN RULINGS, which is the best argument in
   this phase for building a cost rather than describing it. He first ruled the
   current month plus the NEXT FOUR, continuous; measured, that tab was 2,351px
   at launch with most of it empty, and on seeing the figure he reduced it to the
   current month plus TWO. All four upcoming reviews still fall inside three
   months, so nothing is lost and the tab is 1,554px.

   NO NEW TOKEN AND NO NEW CONTRAST PAIR. White on oxblood, black on white,
   charcoal on white and charcoal on off-white are all already declared, which is
   the fourth surface running to add a section without widening the palette.
   -------------------------------------------------------------------------- */

/* THE MONTH HEADING. A rule under the month rather than a card around it: the
   grid below is already a strong shape and boxing it twice reads as two things. */
.calmonth {
  display: flex; align-items: baseline; gap: 6px;
  padding-bottom: 5px; margin: var(--sp-6) 0 var(--sp-3);
  border-bottom: 2px solid var(--itk-black);
}
.calmonth:first-of-type { margin-top: 0; }
.calmonth__t {
  font-size: var(--fs-3xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-bold); color: var(--itk-black);
}
.calmonth__n { margin-left: auto; font-size: var(--fs-3xs); color: var(--itk-charcoal); }

/* `minmax(0, 1fr)` is load-bearing, not defensive - the lesson section 10
   records for the lane grid, which bit this surface's own round-1 sketch. */
.cal { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
.cal__hd {
  font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-bold); color: var(--itk-charcoal); padding: 0 0 var(--sp-1);
}
.cal__d {
  min-height: 56px; background: var(--itk-white);
  border: 1px solid var(--itk-smoke); border-radius: var(--radius); padding: 5px 6px;
}
.cal__d--out { background: var(--itk-offwhite); border-color: var(--itk-offwhite); }

/* TODAY, needed the moment the calendar became a continuous sequence: a reader
   must find the present before anything else means anything. Two signals, per
   DP-14's never-colour-alone rule - a black edge and a bold number. */
.cal__d--today { border-color: var(--itk-black); }
.cal__d--today .cal__n { color: var(--itk-black); font-weight: var(--fw-bold); }
.cal__n { display: block; font-size: var(--fs-4xs); color: var(--itk-charcoal); }
.cal__d--out .cal__n { color: var(--itk-silver); }

/* A SCHEDULED REVIEW IN A DAY CELL. Gold edge rather than a fill, because a
   filled cell in a grid of empty ones reads as a status and this is a link. */
.cal__r {
  display: block; margin-top: 3px; padding: 2px 5px;
  border-left: 3px solid var(--itk-gold); background: var(--itk-offwhite);
  font-size: var(--fs-4xs); font-weight: var(--fw-semibold);
  color: var(--itk-black); text-decoration: none;
}
.cal__r:hover { background: var(--itk-smoke); color: var(--itk-black); }

/* DP-76 A - the overdue count on the tab. It follows DP-9's rule and appears
   ONLY when something is genuinely overdue - a count that is always there stops
   being read - which is why the two launch-day states assert it absent. */
.btab__c {
  display: inline-block; margin-left: 5px; padding: 1px 6px;
  background: var(--ox-oxblood); color: var(--itk-white);
  border-radius: var(--radius-pill); font-size: var(--fs-4xs); font-weight: var(--fw-bold);
}

/* DP-69's REVEAL PANEL. Bootstrap's own modal, so the X, the Escape key, the
   focus trap and the aria wiring come with it. The bundle is already vendored
   and already loaded by templates/base.html, whose comment draws the line this
   sits inside: the bundle drives chrome, not application state. A panel that
   only reveals fields already on the record posts nothing and saves nothing.

   THESE RULES LOAD AFTER bootstrap.min.css AND THAT IS LOAD-BEARING, not
   incidental: `.modal-content` and `.modal-backdrop` are Bootstrap's own
   selectors, and this file wins only because every template links it second. */
.gvpop__hd {
  display: flex; align-items: center; gap: var(--sp-3);
  background: var(--itk-black); padding: 9px var(--sp-3);
}
.gvpop__t { font-size: var(--fs-2xs); letter-spacing: .06em; font-weight: var(--fw-bold); color: var(--itk-white); }
.gvpop__x {
  margin-left: auto; background: transparent; border: 0; cursor: pointer;
  color: var(--itk-white); font-size: var(--fs-2xs); line-height: 1; padding: 2px 4px;
}
.modal-content .tbl th[scope="row"] {
  background: var(--itk-offwhite); color: var(--itk-charcoal);
  width: 34%; text-transform: none; letter-spacing: 0;
  font-size: var(--fs-3xs); font-weight: var(--fw-semibold);
}
.modal-content { border-radius: var(--radius-md); overflow: hidden; border: 0; }

/* Bootstrap's backdrop is #000, which is NOT --itk-black, so the token layer
   would report it on the open state. Mapped onto the token rather than
   exempted - the same call the radio's border-radius got at Gate capture. */
.modal-backdrop { --bs-backdrop-bg: var(--itk-black); }

/* ONE REJECTED COMPONENT IS DELIBERATELY ABSENT and this note is why nobody
   adds it later in good faith: DP-75 B's overdue-pulled-onto-today chip. Ben
   ruled the separate overdue section instead, so a chip that plants an overdue
   review on today's cell would state a date that is not true. A rejected
   component has no business in the file production serves. */


/* --------------------------------------------------------------------------
   14. The portfolio surface · wave 2, surface 8 (Portfolio Dashboard)
   --------------------------------------------------------------------------
   The purest enter-data-once page in the build: every figure is READ from a
   record entered on another surface, the page writes nothing, and it carries
   no control except its filters and the target toggle. Every derived figure
   is computed in Python on read (D60) and NEVER stored, and the page's five
   restatements of one number - headline, mix, category split, team split,
   the Total rows - are each a query over the same ValueRecord rows (D61), so
   a block that disagrees is a block that was typed. tools/check-dashboard-sums.py
   proves they agree, on BOTH page states, and it is part of this surface's
   signing evidence because compare.py reads structure, render.py reads layout
   and contrast.py reads colour - none of them can add up.

   EIGHTEEN decision points settled this surface, DP-78 to DP-95, over six
   rounds and three lavish batches (18-19 Aug 2026); the log is in
   docs/design-targets.md. The ones a maintainer must not quietly reverse:
     DP-78  a HORIZONTAL rung-segmented gauge, figure inline with the target
            line. A dial was ruled IN on 18 Aug and OUT on 19 Aug ("the
            horizontal gauge fills the space in the box better") - do not
            bring it back in good faith.
     DP-79  the Year 1 target is SEEDED at Ben's committed $100,000, not the
            deck's illustrative $10m and not empty (his ruling, with the trap
            argument in view). The value lives in SystemSetting; O50 carries
            the Year 5 field and the per-year mechanism, Phase 5.
     DP-89  bar length is the row's share of the (filtered) portfolio total -
            one scale everywhere, never per-block normalisation.
     DP-91  NO computed markers on this page (ruled; Project Detail keeps its
            nine - the two surfaces deliberately differ).
     DP-92  a leverage point counts against its RECEIVING team.
   ------------------------------------------------------------------------ */

/* The headline card: year toggle, one line carrying figure and target
   together, the rung-segmented gauge, the basis. The toggle is the frozen
   chip pattern doing what it does on the team filter (DP-94); Year 1 is the
   default view because at the $1,000,000 Year 5 target the launch-year fill
   is a 2.8% sliver. */
.gau { background: var(--itk-white); border: 1px solid var(--itk-smoke);
       border-radius: var(--radius-md); padding: var(--sp-5) var(--sp-5) var(--sp-4); }
.gau__lb { font-size: var(--fs-3xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
           font-weight: var(--fw-bold); color: var(--itk-charcoal); margin: 0 0 6px; }
.gau__tgl { display: flex; gap: var(--sp-2); margin: 0 0 var(--sp-4); }
.gau__line { font-size: var(--fs-sm); color: var(--itk-charcoal); margin: 0 0 var(--sp-3); }
.gau__line b { color: var(--itk-black); }
/* 21px: stepped down from 26 by Ben's 19 Aug annotation. Inline with the
   target line by his instruction - never a jumbo figure on its own row. */
.gau__fig { font-size: 21px; font-weight: var(--fw-bold); color: var(--itk-black);
            margin-right: 6px; vertical-align: -1px; }
/* 32px: doubled from 16 by Ben's 19 Aug annotation. The fill is a STACK of
   rung segments, darkest first - the same order and colours as the mix bar
   and the split bars, so the whole page says confidence in one language. */
.gau__bar { display: flex; height: 32px; border-radius: var(--radius-pill);
            background: var(--itk-smoke); overflow: hidden; }
.gau__seg--v { background: var(--conf-verified); }
.gau__seg--h { background: var(--conf-tracked); }
.gau__seg--m { background: var(--conf-evidenced); }
.gau__seg--l { background: var(--conf-banded); box-shadow: inset -1px 0 0 var(--conf-evidenced); }
.gau__scale { display: flex; justify-content: space-between;
              font-size: var(--fs-4xs); color: var(--itk-charcoal); margin-top: 5px; }

/* THE BASIS LINE. Three settings decide what the headline MEANS - net of run
   cost, the currency, latest-versus-pinned - and two are open for Phase 6.
   Cut to ONE sentence on one line by Ben's 19 Aug annotation; the per-track
   split and the receiving-team sentence were removed from this card by the
   same batch (the team fact survives in the By-team block's description). */
.basis { font-size: var(--fs-4xs); color: var(--itk-charcoal); margin: var(--sp-6) 0 0;
         padding-top: var(--sp-3); border-top: 1px solid var(--itk-smoke); }
.basis b { color: var(--itk-black); }

/* The value-confidence ladder as a stacked bar; short rung names spread
   UNDER THE SEGMENTS they belong to (widths mirror the segment widths, so
   each label sits centred under its own segment); the vertical legend below
   carries the model's full rung names, one line each - all three ruled in
   Ben's 19 Aug batches. */
.mix { display: flex; height: 26px; border-radius: var(--radius); overflow: hidden; }
.mix__seg { display: flex; align-items: center; justify-content: center;
            font-size: var(--fs-4xs); font-weight: var(--fw-bold); }
.mix__seg--v { background: var(--conf-verified); color: var(--itk-white); }
.mix__seg--h { background: var(--conf-tracked); color: var(--itk-black); }
.mix__seg--m { background: var(--conf-evidenced); color: var(--itk-black); }
.mix__seg--l { background: var(--conf-banded); color: var(--itk-black);
               box-shadow: inset -1px 0 0 var(--conf-evidenced); }
.mixlbl { display: flex; margin-top: 4px; font-size: var(--fs-4xs); color: var(--itk-charcoal); }
.mixlbl span { text-align: center; }
.mixleg { display: grid; gap: 4px; margin: var(--sp-5) 0 0;
          font-size: var(--fs-4xs); color: var(--itk-charcoal); }
.mixleg span { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.mixleg i { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
.mixleg b { color: var(--itk-black); }
.sw--v { background: var(--conf-verified); }
.sw--h { background: var(--conf-tracked); }
.sw--m { background: var(--conf-evidenced); }
.sw--l { background: var(--conf-banded); box-shadow: inset 0 0 0 1px var(--conf-evidenced); }

/* A split row: label, rung-stacked share-of-total bar (DP-89 C - the widths
   are each segment's share of the PAGE total, one scale everywhere), figure
   left of its percentage with clear air between. The 17rem label column fits
   the model's longest category label on one line at the check viewport
   (DP-90 B: canon labels in text, short forms only in chips). */
.spl { display: grid; grid-template-columns: 17rem minmax(0, 1fr) 7.5rem; gap: var(--sp-3);
       align-items: center; padding: 5px 0; }
.spl + .spl { border-top: 1px solid var(--itk-offwhite); }
.spl__lb { font-size: var(--fs-2xs); color: var(--itk-black); }
.spl__t { display: flex; height: 10px; border-radius: var(--radius-pill);
          background: var(--itk-smoke); overflow: hidden; }
.spl__f { height: 100%; }
.spl__f--v { background: var(--conf-verified); }
.spl__f--h { background: var(--conf-tracked); }
.spl__f--m { background: var(--conf-evidenced); }
.spl__f--l { background: var(--conf-banded); box-shadow: inset -1px 0 0 var(--conf-evidenced); }
.spl__v { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-3);
          font-size: var(--fs-2xs); font-weight: var(--fw-semibold); color: var(--itk-black); }
.spl__v em { font-style: normal; color: var(--itk-charcoal); font-weight: var(--fw-regular); }

/* The Total row (Ben, 19 Aug): the accounting rule sits over the FIGURE, not
   across the whole row, so it reads as a sum rather than a section divider.
   NOT a .spl on purpose - check-dashboard-sums.py reads a .spl-tot as a
   CLAIMED total to verify against the block's sum, never as another group,
   and a typed total under a computed block is exactly the fault it exists
   for. Under a filter the total recomputes with everything else. */
.spl-tot { display: grid; grid-template-columns: 17rem minmax(0, 1fr) 7.5rem; gap: var(--sp-3);
           align-items: center; padding: 7px 0 2px; }
.spl-tot__lb { font-size: var(--fs-2xs); font-weight: var(--fw-semibold); color: var(--itk-black); }
.spl-tot .spl__v { border-top: 1px solid var(--itk-black); padding-top: 5px; }

/* THE CONFIDENCE CHIP - deliberately NOT a .badge-cw. Green already means "on
   track" and this is the page where a RAG status and a confidence rung meet.
   Short forms per DP-90 B; the legend carries the canon names. The banded
   chip wears its hairline for the 1.18-against-white reason above. */
.conf { display: inline-block; padding: 1px 8px; border-radius: var(--radius-pill);
        font-size: var(--fs-4xs); font-weight: var(--fw-bold); }
.conf--v { background: var(--conf-verified); color: var(--itk-white); }
.conf--h { background: var(--conf-tracked); color: var(--itk-black); }
.conf--m { background: var(--conf-evidenced); color: var(--itk-black); }
.conf--l { background: var(--conf-banded); color: var(--itk-black);
           border: 1px solid var(--conf-evidenced); padding: 0 7px; }

/* Centre-aligned cell - the Confidence column (Ben, 19 Aug). */
.tbl .td-c { text-align: center; }

/* THE TOP-10 CAP, WIDTH-INDEPENDENT, and its history is the lesson. A pixel
   cap tuned at 1300px failed BOTH ways: narrower panels wrapped the category
   labels taller so fewer rows fit, and Ben's wider review panel unwrapped
   them so all thirteen rows fit UNDER the cap - thirteen visible, no thumb.
   So: one-line cells make every row the same height at any width (narrow
   panels get .tbl-scroll's own horizontal scrollbar instead of wrapping),
   and the cap is the measured header plus EXACTLY ten rows, cut sharp - a
   sliver of row eleven was tried and rejected by looking, because its text
   read as an eleventh row. The scrollbar is PERMANENT (overflow-y: scroll on
   a styled track): the Windows overlay scrollbar only appears on hover,
   which to a reader is the same as absent. Track is --itk-smoke, not
   --itk-offwhite - offwhite against the white table is 1.04, the DP-47
   invisible-ground fault, caught by looking at this rule's first render.
   If exactly-ten must survive font-metric changes too, Phase 5 slices the
   queryset instead; the height here matches the frozen artefact's metrics. */
.tbl--nowrap th, .tbl--nowrap td { white-space: nowrap; }
.tbl-scroll--cap { max-height: 379px; overflow-y: scroll; }
.tbl-scroll--cap::-webkit-scrollbar { width: 12px; }
.tbl-scroll--cap::-webkit-scrollbar-track { background: var(--itk-smoke); }
.tbl-scroll--cap::-webkit-scrollbar-thumb { background: var(--itk-silver);
    border-radius: var(--radius-pill); border: 3px solid var(--itk-smoke); }

/* Pipeline counts (DP-88 D · DP-93 A): one --sec-band band per group, BOTH
   tracks, each in its own vocabulary under its own sub-label - Track 2 has
   no middle band because D30 replaces Gates 0-3 with one sign-off, so a
   leverage point is waiting or it is finished. Counts of RECORDS, not money,
   which is why the bands are styled unlike every value block. Band titles
   bold and content centred (Ben, 19 Aug). The ground is DP-47's own ruling
   reapplied: the first draft's chips were offwhite ON offwhite - the same
   colour, measured - and --sec-band exists because Ben caught exactly this
   fault once before and chose the lightest visible shade. */
.statbands { display: grid; grid-template-columns: 2fr 4fr 3fr; gap: var(--sp-3); }
.statbands--t2 { grid-template-columns: 1fr 1fr; }
.statband { background: var(--sec-band); border-radius: var(--radius); padding: var(--sp-3); }
.statband__l { font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
               font-weight: var(--fw-bold); color: var(--itk-charcoal);
               margin: 0 0 var(--sp-2); text-align: center; }
.statband__r { display: grid; gap: var(--sp-2); }
.statband__r--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.statband__r--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.statband__r--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.stat { border-radius: var(--radius); padding: var(--sp-2); text-align: center; }
.stat__n { font-size: var(--fs-lg); font-weight: var(--fw-bold); color: var(--itk-black); line-height: 1.1; }
.stat__l { font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
           color: var(--itk-charcoal); margin-top: 2px; }

/* The top row: roughly 2/3 headline card, 1/3 measured box (Ben, 19 Aug),
   with clear air below it before the first split block. */
.dashgrid { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: var(--sp-5);
            margin-bottom: var(--sp-12); } /* A-14: the 48px section rhythm */

/* --------------------------------------------------------------------------
   15. The explainer surface · wave 2, surface 9 (How-It-Works)
   --------------------------------------------------------------------------
   The one page in the build whose job is to TEACH rather than to show a record,
   and the only one whose visual is bespoke rather than assembled from the
   pattern set. D70 re-scoped it from a nav placeholder into the pipeline
   explainer and ruled its three sections; DP-96 to DP-105 settled how it looks.

   THE PAGE HOLDS ALMOST NO DATA, and that shapes everything below: the only
   value an administrator can change is the narrative prose, a NarrativeBlock
   row (D35). Every stage name, gate name, rung name and step heading is
   furniture, which is why the target asserts nearly all of it.

   Ruled through the annotation loop over five rounds, 19 Aug 2026:
     DP-96  A   a horizontal rail, chosen over a vertical journey and a funnel,
                because Home's banner is a SIMPLIFICATION of this visual (D70)
                and only the rail simplifies into a strip without becoming a
                second drawing of the same system
     DP-97  A   two reveal layers - one line on hover, five labelled rows on
                click, in the same order at every box so the shape is learned
                once. The container later became a modal; the five rows did not
                change
     DP-98  A   the confidence ladder rides the visual, each rung under the
                stage that earns it, so a reader sees confidence rise with the
                pipeline instead of being told
     DP-99  A   Track 2 is a lane under Track 1 rather than its own section,
                and it wears Track 1's card so the two read side by side
     DP-100 A   the step guide is a numbered list, not a card grid
     DP-101     the two motions COMBINED on Ben's instruction: the light travels
                the rail AND lights each gate as it reaches it
     DP-102 A   the fourth section D70 left open is WHO DOES WHAT
     DP-103 A   a page inside More marks More as active
     DP-104     the plain surface, not the Oxford paper
     DP-105 E1  the narrative is a plate: the claim in gold on charcoal beside
                the body in reading colours, with a quotation mark above it

   ONE ABSENCE IS A DECISION RATHER THAN AN OMISSION, and it is recorded because
   a later session will otherwise read it as a slip. THE STOP-IS-A-NORMAL-OUTCOME
   SENTENCE IS NOT ON THIS PAGE. Ben removed it here, and removed the step that
   carried it, having earlier RESTORED the same idea on Project Detail with the
   rule this file records in section 11 - prose that teaches a concept the design
   rests on stays. Both readings are his and they are consistent: the concept
   belongs on the record it applies to, not on the explainer. It is unamended on
   the signed project-detail.html.
   -------------------------------------------------------------------------- */

/* --- DP-105 E1 · the narrative plate --------------------------------------
   The claim carries the colour and the body carries the reading. Ben rejected
   four earlier treatments to get here, and the one thing every rejected version
   had in common was long prose set in gold.

   --itk-gold on --itk-charcoal is 5.40:1 and clears AA (D68). It was measured
   BEFORE it was drawn and its row went into tools/contrast-pairs.tsv first,
   because a combination nobody declares is a combination the checker cannot see. */
.hiw-narE {
  display: grid; grid-template-columns: 40% minmax(0, 1fr);
  border-radius: var(--radius-md); overflow: hidden; box-shadow: var(--shadow-lg);
}
.hiw-narE__l {
  background: var(--itk-charcoal); padding: var(--sp-8) var(--sp-6);
  display: flex; flex-direction: column; justify-content: center;
  border-right: 3px solid var(--itk-gold);
}
.hiw-narE__mk { display: block; margin-bottom: var(--sp-3); opacity: .55; }
/* SCOPED, and it is a defect that fired THREE TIMES on this surface before it
   became a rule: written bare, a modifier on a <p> loses to `.hiw-narE__r p` on
   specificity - one class against one class plus one type - and renders at body
   size in body colour. Every mechanical check passes on it, because 16px and
   --itk-smoke are legal frozen values. In this component family a modifier on a
   <p> carries its container in the selector. */
.hiw-narE .hiw-narE__q {
  margin: 0; font-size: var(--fs-xl); line-height: var(--lh-snug);
  font-weight: var(--fw-bold); color: var(--itk-gold);
}
.hiw-narE__k { margin: var(--sp-4) 0 0; width: 44px; height: 2px; background: var(--itk-gold); }

/* BEN RULING, 1 Sep 2026, at the surface-9 sign-off review, THREE CHANGES TO THIS
   PANEL AND ONE TO THE SECTION RHYTHM BELOW. He read the built page and asked for
   the right-hand copy to run to the end of the plate, to sit on the same line as
   the quote beside it, and to be larger - "still at least 2 points smaller than
   the text on the left".

   `justify-content: center` MIRRORS `.hiw-narE__l` EXACTLY, which is what makes
   the two columns start on the same line: the left column has centred its
   contents since the artefact was signed and the right one never did, so the copy
   hung from the top while the quote floated in the middle. Same declaration, same
   value, same reason.

   `max-width: 64ch` IS REMOVED, which is the "run to the end" half. 64ch was a
   readability cap written when this panel was 40% of a narrower page; at 1300px
   it stopped the line 127px short of the padding and read as a ragged right edge
   rather than as a measure.

   THE SIZE IS `--fs-lg`, 18px, AND THE CEILING IS HIS RATHER THAN MINE. The quote
   is `--fs-xl`, 22px, so this is 4px below it and satisfies "at least 2 points
   smaller" with room to spare; `--fs-md` at 16px was the alternative and is 2px
   further down. 18px is the token the scale itself describes as "lead
   paragraphs", which is what this copy is. Both values are already in the
   how-it-works target's `allowedLengths`, so the token layer accepts either.

   THIS IS A FREEZE AMENDMENT AND A WORKER MAY NOT SIGN ONE. `static/css/app.css`
   is in FREEZE.md's manifest, so `check-freeze.py` reads 26 of 27 until the
   orchestrator lands the new hash. The change is deliberately scoped to
   `.hiw-narE*`, which only this surface and Home (D70) draw, so no other signed
   artefact moves - measured rather than asserted, in this surface's evidence. */
.hiw-narE__r {
  background: var(--itk-white);
  /* The extra top padding is the QUOTE MARK'S OWN BLOCK, derived rather than
     guessed: the `.hiw-narE__mk` svg is 39px tall and carries `--sp-3` beneath it.
     The left column centres mark + quote + keyline together, so without this the
     right copy's first line sits about 30px ABOVE the quote's - measured, not
     estimated. Offsetting by exactly what stands above the quote is what makes
     Ben's test ("starts on the same line as Organic growth") hold, and it keeps
     BOTH columns centred so the plate still degrades sensibly if the narrative
     grows or shrinks. */
  padding: calc(var(--sp-8) + 39px + var(--sp-3)) var(--sp-8) var(--sp-8);
  display: flex; flex-direction: column; justify-content: center;
}
.hiw-narE__r p {
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-lg); line-height: var(--lh-loose); color: var(--itk-black);
}
.hiw-narE__r p:last-child { margin-bottom: 0; }

/* THE SECTION RHYTHM, the fourth of Ben's 1 Sep changes: "space the sections out a
   bit more - it looks too cramped". The gap between two sections is `.pdsec`'s own
   32px bottom margin; this adds 16px inside the section, taking the gap to 48px.

   IT IS DELIBERATELY NOT A CHANGE TO `.pdsec`, AND THE REASON IS MEASURED RATHER
   THAN CAUTIOUS. `.pdsec` is drawn 32 times in `dashboard.html`, 36 in
   `system-health.html`, 23 in `project-detail.html` and 14 in `home.html`. Re-spacing
   it globally would move four other signed artefacts and every pixel baseline behind
   them - including the Portfolio Dashboard's, which is the OTHER worker in this live
   batch and has already captured its three and pushed. Ben was reading this page, not
   those four.

   So the rule hangs off the three blocks that only this surface draws, plus the
   narrative plate that only this surface and Home draw. It is the `.shell--app`
   discipline from amendment A-9: a rule scoped so that it CANNOT reach a page it was
   not ruled for. `:has()` would express it more directly and is deliberately not used
   - `app.css` contains no `:has()` anywhere, and one surface's spacing is a poor
   reason to introduce a selector feature the other twelve have never needed.

   The last section needs none: `.pdsec:last-of-type` already zeroes its margin.
   SUPERSEDED AT THE BATCH-3 MERGE (A-14, 1 Sep 2026), DECLARATION REMOVED - not
   reverted as a mistake. Ben ruled the 48px rhythm GLOBALLY the same day ("option
   C, full scope" - the cross-page separation rule), after giving BOTH workers the
   same cramped-sections feedback on two different pages within one day - the
   strongest available evidence the 32px rhythm was too tight app-wide, not here.
   With `.pdsec` itself at 48px, this inner 16px would have overshot How It Works
   to 64px. The measurement above survives on purpose: the per-artefact `.pdsec`
   counts are exactly A-14's blast radius, measured by this worker before the
   ruling existed. */

/* --- the two track titles -------------------------------------------------
   One component at two sizes: Track 1 sits one rung above Track 2. */
.hiw-trk {
  margin: 0 0 var(--sp-3);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-bold); color: var(--itk-charcoal);
}
.hiw-trk--1 { font-size: var(--fs-xs); }
.hiw-trk--2 { font-size: var(--fs-2xs); }

/* --- the rail ------------------------------------------------------------
   FOUR ROWS ON ONE GRID TEMPLATE, and the shared template is the teaching
   device rather than a convenience: a stage, the gate that closes it, and the
   confidence rung it can honestly claim all line up because they are the same
   columns. Track 2 deliberately does NOT share it - its four states do not
   correspond to five stages, and a shared grid would draw a mapping the canon
   does not have. */
.hiw-rail { display: grid; gap: var(--sp-2); }
.hiw-row {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr)) 112px;
  gap: var(--sp-3);
}

/* `color` IS LOAD-BEARING HERE even though every child sets its own, and it was
   found by the token check before this surface was signed rather than after. The
   card is a <button>, so without it the element computes to the browser's own
   buttontext - pure `rgb(0, 0, 0)`, which is NOT `--itk-black` #130C0E - and the
   check reported eighteen of them, nine boxes across two states. Nothing looked
   wrong, because the children paint over it. This is the same class of defect as
   the Bootstrap blue submit button measured at SPEC-4 step 2: a default nobody
   chose, invisible to any reading of our own stylesheet. */
.hiw-st {
  display: block; width: 100%; text-align: left;
  background: var(--itk-white); border: 1px solid var(--itk-smoke);
  border-radius: var(--radius-md); padding: var(--sp-3);
  cursor: pointer; position: relative; color: var(--itk-black);
}
.hiw-st:hover { border-color: var(--itk-gold); }
/* The revealed box carries THREE signals, never colour alone (DP-14): a gold
   left rule, a heavier title, and the panel naming it. */
.hiw-st--on { border-left: 3px solid var(--itk-gold); padding-left: 10px; }
.hiw-st--on .hiw-st__t { font-weight: var(--fw-bold); }
.hiw-st__n {
  display: block; margin-bottom: 3px;
  font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-bold); color: var(--itk-charcoal);
}
.hiw-st__t {
  display: block; font-size: var(--fs-sm); font-weight: var(--fw-semibold);
  color: var(--itk-black); line-height: var(--lh-snug);
}
.hiw-st__s {
  display: block; margin-top: 2px;
  font-size: var(--fs-3xs); color: var(--itk-charcoal); line-height: var(--lh-snug);
}
.hiw-st__d { display: block; margin-top: var(--sp-2); font-size: var(--fs-3xs); color: var(--itk-charcoal); }

/* The terminal. Not a stage, and drawn so it cannot be read as one: no number,
   no duration, and no gate after it. */
.hiw-end {
  display: flex; flex-direction: column; justify-content: center;
  background: var(--sec-band); border-radius: var(--radius-md);
  padding: var(--sp-3); text-align: center;
}
.hiw-end__t { font-size: var(--fs-xs); font-weight: var(--fw-bold); color: var(--itk-black); }
.hiw-end__s { margin-top: 2px; font-size: var(--fs-4xs); color: var(--itk-charcoal); }

/* --- the gate strip, the rail line, and DP-101's combined motion ----------
   The line is a background on the strip rather than a border on anything, so
   the markers sit ON it.

   THE FLASH DELAYS ARE DERIVED FROM THE GRID, NOT EYEBALLED, and that matters
   because the band is placed by LAYOUT while each flash is timed by the CLOCK.
   Two coordinate systems held in step by arithmetic is the shape of the
   dashboard's top-10 cap, which was tuned at one width and wrong at every
   other. At the 1210px measure each 1fr is (1210 - 112 - 60) / 5 = 207.6px and
   each gate sits at its column's right edge; the band is 22% of the strip
   translating -24% to 460% of its own width, so its centre travels 69px to
   1357px in 9s at 143px/s. Time at gate n = (x - 69) / 143, and the flash peaks
   0.72s after its delay.

   VERIFIED BY SAMPLING THE LIVE ANIMATION at each peak and reading where the
   band actually was: fifteen samples at 1000, 1300 and 1728px, every flash
   inside its band's half-width. The honest limit, stated rather than hidden:
   the five columns scale and the 112px terminal does not, so at a very
   different width a flash leads or lags slightly. */
.hiw-gates { position: relative; }
.hiw-gates::before {
  content: ""; position: absolute; left: 0; right: 0; top: 11px; height: 2px;
  background: var(--itk-smoke);
}
.hiw-gates::after {
  content: ""; position: absolute; left: 0; top: 11px; height: 2px; width: 22%;
  background: linear-gradient(90deg, rgba(255, 199, 0, 0), var(--itk-gold), rgba(255, 199, 0, 0));
  animation: hiwflow 9s linear infinite;
}
@keyframes hiwflow {
  from { transform: translateX(-24%); }
  to   { transform: translateX(460%); }
}
.hiw-gates .hiw-gt__d { animation: hiwlight 9s linear infinite; }
.hiw-gates .hiw-gt:nth-of-type(1) .hiw-gt__d { animation-delay: 0.25s; }
.hiw-gates .hiw-gt:nth-of-type(2) .hiw-gt__d { animation-delay: 1.78s; }
.hiw-gates .hiw-gt:nth-of-type(3) .hiw-gt__d { animation-delay: 3.32s; }
.hiw-gates .hiw-gt:nth-of-type(4) .hiw-gt__d { animation-delay: 4.85s; }
.hiw-gates .hiw-gt:nth-of-type(5) .hiw-gt__d { animation-delay: 6.39s; }
@keyframes hiwlight {
  0%, 20%, 100% { box-shadow: 0 0 0 0 rgba(255, 199, 0, 0); }
  8%            { box-shadow: 0 0 0 7px rgba(255, 199, 0, 0.38); }
}
@media (prefers-reduced-motion: reduce) {
  .hiw-gates::after { animation: none; opacity: 0; }
  .hiw-gates .hiw-gt__d { animation: none; }
}

/* The marker sits at the right edge of the stage it closes and does NOT
   overhang. The first draft centred it on the seam with negative margins, which
   render.py correctly reported as fifteen clipped elements - content wider than
   its own box in a container that does not scroll is unreachable text, whatever
   it looks like. Placing the gate inside its stage's column is also the truer
   reading, since Gate 0 closes Stage 0. */
.hiw-gt { position: relative; display: flex; flex-direction: column; align-items: flex-end; min-width: 0; }
.hiw-gt__d {
  width: 24px; height: 24px; border-radius: var(--radius-pill);
  background: var(--itk-black); color: var(--itk-white);
  font-size: var(--fs-4xs); font-weight: var(--fw-bold);
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto; position: relative; z-index: 1;
}
.hiw-gt__l {
  margin: var(--sp-1) 0 0; text-align: right;
  font-size: var(--fs-4xs); color: var(--itk-charcoal); line-height: var(--lh-snug);
}
.hiw-gt__l b { display: block; color: var(--itk-black); font-weight: var(--fw-semibold); }

/* --- the confidence ladder ------------------------------------------------
   The four rung colours are THE DASHBOARD'S OWN --conf-* tokens, deliberately:
   the same idea must not wear two palettes on two pages. Stage 0 has no bar,
   because it can claim no value. */
.hiw-lad__c { padding-top: var(--sp-1); }
.hiw-lad__c--none { padding-top: 26px; }
.hiw-lad__b { height: 6px; border-radius: var(--radius-sm); }
.hiw-lad__b--l { background: var(--conf-banded); border: 1px solid var(--itk-smoke); }
.hiw-lad__b--m { background: var(--conf-evidenced); }
.hiw-lad__b--h { background: var(--conf-tracked); }
.hiw-lad__b--v { background: var(--conf-verified); }
.hiw-lad__t { margin: var(--sp-1) 0 0; font-size: var(--fs-4xs); color: var(--itk-charcoal); line-height: var(--lh-snug); }
/* `display: block` is not decoration: without it the rung name and its
   description set as one word - "LowA banded hypothesis" - which every
   mechanical check passed and only looking caught. */
.hiw-lad__t b { display: block; color: var(--itk-black); font-weight: var(--fw-semibold); }

/* --- Track 2's row --------------------------------------------------------
   Its own four columns, its own card at Track 1's size, and a wide gap above so
   the hairline reads as a divider between two things rather than as the
   underline of the one above it. */
.hiw-t2 { margin-top: var(--sp-8); padding-top: var(--sp-6); border-top: 1px solid var(--itk-smoke); }
.hiw-t2__row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-3); }
.hiw-t2__p { margin: var(--sp-3) 0 0; font-size: var(--fs-2xs); color: var(--itk-charcoal); line-height: var(--lh-base); }
.hiw-t2__p b { color: var(--itk-black); font-weight: var(--fw-semibold); }

/* --- the progression arrows ----------------------------------------------
   Built from borders rather than a glyph, for two mechanical reasons: a glyph
   needs a font-size and every size here is a rung on the frozen scale, and the
   transparent sides compute to rgba(0, 0, 0, 0), which the token check already
   treats as inert.

   THE ARROW SITS IN THE GAP TO ITS CARD'S LEFT, not the previous card's right,
   and that is deliberate: a pseudo-element overhanging to the RIGHT grows its
   parent's scrollWidth and render.py reports it as clipped - which is exactly
   what the first draft of the gate markers did. */
.hiw-arrowed > * + * { position: relative; }
.hiw-arrowed > * + *::before {
  content: ""; position: absolute; z-index: 1;
  left: -11px; top: 50%; margin-top: -5px;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 6px solid var(--itk-silver);
}

/* --- the hover layer (DP-97 A) --------------------------------------------
   One line, the box's aim, and nothing else. It exists so a reader grazing the
   rail learns something without committing to a click. It is NOT the content
   layer: a hover reveals nothing on a touch screen and nothing to a keyboard,
   which is why the panel below is what carries the substance. */
.hiw-tipwrap { position: relative; display: block; }
.hiw-tip {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 2;
  background: var(--itk-black); color: var(--itk-white);
  border-radius: var(--radius); padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-3xs); line-height: var(--lh-snug);
  opacity: 0; visibility: hidden;
}
.hiw-tipwrap:hover .hiw-tip { opacity: 1; visibility: visible; }

/* --- the reveal panel (DP-97 A, in a modal on Ben's instruction) ----------
   THE CHROME IS SECTION 13's, UNCHANGED. .gvpop__hd, .gvpop__t and .gvpop__x
   were written for Governance and are reused here exactly, so the X, the
   Escape key, the focus trap and the aria wiring all come from Bootstrap's own
   modal at zero new dependency. The bundle is vendored at 5.3.8 and
   templates/base.html already loads it; the boundary its comment draws is
   unmoved, because a panel that only reveals fixed explanatory text posts
   nothing and holds no state.

   THE WIDTH IS SCOPED AND THAT IS THE WHOLE OF THE CARE HERE. Ben asked for the
   panel 50% wider. `.modal-dialog` is Bootstrap's own selector and the SIGNED
   governance.html renders through it, so widening that would have redrawn a
   frozen artefact on another page's instruction - exactly what the freeze
   manifest exists to make impossible to do quietly. */
.hiw-pop-dlg { max-width: 750px; }
.hiw-pop { padding: var(--sp-4) var(--sp-5); background: var(--itk-white); }
.hiw-pop__m { margin: 0 0 var(--sp-4); font-size: var(--fs-2xs); color: var(--itk-charcoal); }
.hiw-pop__r { display: grid; grid-template-columns: 128px minmax(0, 1fr); gap: var(--sp-3); margin-bottom: var(--sp-3); }
.hiw-pop__r:last-child { margin-bottom: 0; }
.hiw-pop__k {
  font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  font-weight: var(--fw-bold); color: var(--itk-charcoal); padding-top: 2px;
}
.hiw-pop__v { margin: 0; font-size: var(--fs-xs); color: var(--itk-black); line-height: var(--lh-base); }
.hiw-pop__v b { font-weight: var(--fw-semibold); }

/* --- the step list (DP-100 A) ---------------------------------------------
   The list is capped rather than the paragraph alone, because a bordered card
   stretching 400px past its own text reads as a layout fault. 940px is the 34px
   number column plus the measure plus the padding. */
.hiw-steps { display: grid; gap: var(--sp-3); max-width: 940px; }
.hiw-step {
  display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: var(--sp-3);
  background: var(--itk-white); border: 1px solid var(--itk-smoke);
  border-radius: var(--radius-md); padding: var(--sp-3) var(--sp-4);
}
.hiw-step__n {
  width: 26px; height: 26px; border-radius: var(--radius-pill);
  background: var(--itk-black); color: var(--itk-white);
  font-size: var(--fs-2xs); font-weight: var(--fw-bold);
  display: flex; align-items: center; justify-content: center;
}
.hiw-step__t { margin: 0 0 2px; font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--itk-black); }
.hiw-step__p { margin: 0; max-width: 84ch; font-size: var(--fs-xs); color: var(--itk-charcoal); line-height: var(--lh-base); }
.hiw-step__p b { color: var(--itk-black); font-weight: var(--fw-semibold); }

/* --- the role cards (DP-102 A) --------------------------------------------
   Two by two rather than four across: at four, "Steerco and I-Board" wraps to
   three lines while "Ideator" sits on one, and the row reads as ragged. */
.hiw-roles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); max-width: 940px; }
.hiw-role {
  background: var(--itk-white); border: 1px solid var(--itk-smoke);
  border-left: 3px solid var(--itk-gold); border-radius: var(--radius-md);
  padding: var(--sp-3) var(--sp-4);
}
.hiw-role__t { margin: 0 0 2px; font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--itk-black); }
.hiw-role__p { margin: 0; max-width: 52ch; font-size: var(--fs-xs); color: var(--itk-charcoal); line-height: var(--lh-base); }


/* --------------------------------------------------------------------------
   16. The home surface · wave 2, surface 10 (Home and feed)
   --------------------------------------------------------------------------
   Two new components and two deliberate reuses, and the reuses are the larger
   half of the page.

   REUSED, NOT REBUILT:
     the NARRATIVE is section 15's `.hiw-narE` plate, verbatim. D35 puts the
       narrative on Home and D70 lets How-It-Works read the SAME
       `NarrativeBlock` row, so one row renders in one treatment on two
       surfaces. Zero new CSS.
     the CAROUSEL is section 8's `.videoshelf` + `.carousel` + `.vid`, verbatim,
       the same components the Tool Shed froze. Its destination changed under
       D71 - it points at Training / Learning now - but that is a link label,
       not a rule.
     the SECTIONS are section 11's `.pdsec` + `.pdsec__hd` + `.pdsec__t`, so
       Home's headings are the ones Project Detail froze.

   Ruled by Ben through the annotation loop, 21 Aug 2026:
     DP-110 C  THE BANNER CARRIES BOTH TRACKS, not just the five-stage funnel.
               Taken over a spine-only version (125px) and a spine-plus-gates
               version (158px) at 261px, and the argument that won is canon
               rather than completeness: D30 makes dual submission tracks part
               of the system, so a banner showing only Track 1 tells a colleague
               with a two-line annoyance that this place is not for them - which
               is exactly the person Track 2 exists to catch. Amended twice at
               the annotation pass, and both amendments made the same point:
               the Track 2 label moved ABOVE its row, the row was extended to
               Track 1's full width, and then the grey rule between them was
               REMOVED with the gap kept identical. Track 2 is drawn as an
               equal, not as an afterthought pinned to a label.
     DP-111 D  THE FEED IS DERIVED FROM THE RECORDS, not written (D72). Each
               line is rendered from a row that already exists and every row is
               a real link to it. Ben asked whether the activity log could be
               genuinely automatic; the answer is that it must not be POSTED at
               all, because a `FeedItem` restating a `Decision` is a duplicate
               under D60 - "a snapshot with a reason is a record, a cached total
               is a duplicate". The provenance caption an earlier draft carried
               was REMOVED on his instruction, and the reason is worth keeping:
               "read straight from the record" was reassurance for the reviewer,
               not information for the reader. The honesty lives in the
               architecture, not in a caption.
   -------------------------------------------------------------------------- */

/* --- the banner into How-It-Works (D70, DP-110 C) -------------------------
   D70 rules the behaviour and both halves of it are here: the whole band is
   ONE LINK through to the explainer, and it is STATIC UNTIL HOVERED, because
   Home is the page people see every visit and a permanently moving element
   there is the decoration that wears out fastest. */
.hb {
  display: block; text-decoration: none; background: var(--itk-white);
  border: 1px solid var(--itk-smoke); border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-5);
}
.hb:hover { border-color: var(--itk-gold); }
.hb__hd { display: flex; align-items: baseline; gap: var(--sp-3); margin: 0 0 var(--sp-4); }
.hb__t { font-family: var(--font-serif); font-size: var(--fs-md); color: var(--itk-black);
         font-weight: var(--fw-semibold); margin: 0; }
.hb__go { margin-left: auto; font-size: var(--fs-3xs); color: var(--itk-charcoal); }
.hb:hover .hb__go { color: var(--ox-green); font-weight: var(--fw-semibold); }
.hb__go::after { content: " \2192"; }

.hb-trk {
  font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--itk-charcoal); font-weight: var(--fw-bold); margin: 0 0 4px;
}

/* THE SPINE IS SIX EQUAL CELLS AND THE END CARD IS NOT AUTO-WIDTH, which is
   geometry rather than taste. With an auto-width end card the spine's cells and
   the gate row's cells divide DIFFERENT widths, so every dot drifts right and
   Gate 4 lands under "Completed" - reading as the close-out gate happening
   after completion. Caught by looking at the render; `render.py` cannot see it,
   because nothing overflows and nothing is clipped. */
.hb-spine { display: flex; align-items: stretch; }
.hb-st {
  flex: 1 1 0; min-width: 0; padding: 7px 10px;
  border: 1px solid var(--itk-smoke); border-right: 0; background: var(--itk-white);
}
.hb-st:last-of-type { border-right: 1px solid var(--itk-smoke); }
.hb-st__n { display: block; font-size: var(--fs-4xs); letter-spacing: var(--ls-caps);
            text-transform: uppercase; color: var(--itk-charcoal); }
.hb-st__t { display: block; font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
            color: var(--itk-black); white-space: nowrap; overflow: hidden;
            text-overflow: ellipsis; }
.hb-st--end { background: var(--itk-offwhite); }
.hb-st--end .hb-st__t { color: var(--ox-green); }

/* The gate line. Dots and numbers, no per-gate prose - the prose is what the
   click is for. The dot is RIGHT-ALIGNED in its cell so it sits on the stage
   boundary, and its centre therefore falls half a dot-width left of that
   boundary. THAT OFFSET IS THE FROZEN CONVENTION SCALED, NOT A DEFECT: section
   15's `.hiw-gt` does exactly the same, measured at -12px for its 24px dot
   against -9px for this 18px one. Centring these would make the banner
   inconsistent with the page it simplifies, which is the opposite of the job. */
.hb-gates { position: relative; display: flex; margin-top: 3px; }
.hb-gates::before {
  content: ""; position: absolute; left: 0; right: 0; top: 6px;
  height: 1px; background: var(--itk-smoke);
}
.hb-gt { flex: 1 1 0; display: flex; flex-direction: column; align-items: flex-end; }
/* 18px RATHER THAN 13px, AND THE TOKEN CHECK IS WHY. The first draft used a
   13px dot with `font-size: 8px`, and 8px is not on the frozen type scale - the
   check caught it fifteen times across the three states, which is the same class
   of find as the Bootstrap blue submit button at SPEC-4 step 2 and the browser
   buttontext black at How-It-Works. The scale floor is `--fs-4xs` at 10px, which
   does not fit inside 13px, so the dot grew instead of the type shrinking - to 18px rather than 16px, because at 16px the inherited `--lh-base` line box nearly filled the circle and the digit read as a smudge. `line-height: 1` is set for the same reason; the frozen 24px dot never needed it.
   Dropping the digits entirely was the other candidate and was rejected: Ben
   approved DP-110 C with numbered dots, and losing them after sign-off is a
   design change rather than a fix. The gate labels beneath still carry the
   names, so the digit is redundant - but redundancy he has seen and approved
   beats tidiness he has not. */
.hb-gt__d {
  width: 18px; height: 18px; border-radius: var(--radius-pill);
  background: var(--itk-black); color: var(--itk-white);
  font-size: var(--fs-4xs); font-weight: var(--fw-bold);
  line-height: 1;
  display: flex; align-items: center; justify-content: center;
  /* THREE DECLARATIONS COPIED FROM THE FROZEN `.hiw-gt__d`, AND THE LAST TWO ARE
     WHY THE DIGIT WAS UNREADABLE FOR THREE ATTEMPTS. `.hb-gates::before` is the
     hairline the dots sit on; it is an absolutely-positioned pseudo-element and
     therefore paints AFTER them, straight through the middle of every circle. So
     the dot has to be lifted out of that paint order - which is exactly what the
     explainer's version does, and I had dropped both declarations when writing
     this one.

     WORTH RECORDING BECAUSE THE DIAGNOSIS WAS WRONG TWICE FIRST. The digit looked
     cut, so I read it as too little room and grew the dot 13 -> 16 -> 18px and set
     `line-height: 1`; then read it as flex compression and added `flex: 0 0 auto`.
     Neither was the cause: the dot measured its full 18x18 throughout. A
     bounding-rect check said the text FIT, and it was right - the text did fit,
     and a line was being drawn over it. Rect geometry cannot see paint order,
     which is the same blind spot as O39 in a third direction. Only looking found
     it, three times running. */
  flex: 0 0 auto;
  position: relative;
  z-index: 1;
}
.hb-gt__l { margin-top: 2px; font-size: var(--fs-4xs); color: var(--itk-charcoal); }

/* SEPARATION BY SPACE ALONE. Ben removed the rule that was here and kept the
   gap identical: the margin absorbs the border plus the padding it replaced, so
   the banner is the same height it was with the line. My argument for the rule
   was that two stacked rows of equal-width cells would read as one grid without
   it - which the render disproves, because each track carries its own label and
   the labels were already doing the separating. */
.hb-sep { margin: calc(var(--sp-4) * 2 + 1px) 0 0; }
.hb-t2 { display: flex; }
.hb-t2__s {
  flex: 1 1 0; min-width: 0; padding: 5px 10px;
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold); color: var(--itk-black);
  border: 1px solid var(--itk-smoke); border-right: 0; background: var(--itk-white);
}
.hb-t2__s:last-of-type { border-right: 1px solid var(--itk-smoke); }

/* THE MOTION IS HOVER-ONLY, WHICH IS D70's RULING AND NOT AN IMPLEMENTATION
   CHOICE. The travelling light is section 15's, borrowed so the banner and the
   page it leads to read as one idea; on the explainer it runs ambiently, here
   it waits to be asked. `prefers-reduced-motion` turns it off entirely, the
   same guard both other animated sections carry. */
.hb:hover .hb-gt__d { animation: hblight 6s linear infinite; }
.hb:hover .hb-gt:nth-of-type(1) .hb-gt__d { animation-delay: .1s; }
.hb:hover .hb-gt:nth-of-type(2) .hb-gt__d { animation-delay: 1.2s; }
.hb:hover .hb-gt:nth-of-type(3) .hb-gt__d { animation-delay: 2.3s; }
.hb:hover .hb-gt:nth-of-type(4) .hb-gt__d { animation-delay: 3.4s; }
.hb:hover .hb-gt:nth-of-type(5) .hb-gt__d { animation-delay: 4.5s; }
/* THE SAME GOLD AND THE SAME SHAPE AS SECTION 15's `hiwlight`, deliberately,
   with the spread scaled to this dot: 7px on a 24px dot becomes 4px on a 13px
   one. The literal rgba matches the frozen keyframe rather than introducing a
   token, because the point of the banner is that its motion IS the explainer's
   motion. A keyframe colour is also outside the token check by construction -
   `compare.py` reads computed colour properties on elements, and box-shadow is
   not among them - so this is precedent followed rather than a gap exploited. */
@keyframes hblight {
  0%, 82%, 100% { box-shadow: 0 0 0 0 rgba(255, 199, 0, 0); }
  88%           { box-shadow: 0 0 0 4px rgba(255, 199, 0, 0.38); }
}
@media (prefers-reduced-motion: reduce) {
  .hb:hover .hb-gt__d { animation: none; }
}

/* --- the ramp applied, and the text colour that each step forces ----------
   DP-113 A. `Completed` is NOT in the ramp: it is not a stage, it keeps
   `--itk-offwhite` and its green title, and that is also what let the ramp be
   five steps rather than six. */
.hb-st--s0 { background: var(--stage-0); }
.hb-st--s1 { background: var(--stage-1); }
.hb-st--s2 { background: var(--stage-2); }
.hb-st--s3 { background: var(--stage-3); }
.hb-st--s4 { background: var(--stage-4); }

/* Titles. Black holds to step 2; white from step 3. */
.hb-st--s3 .hb-st__t,
.hb-st--s4 .hb-st__t { color: var(--itk-white); }

/* Labels, and this is the line the first pass got wrong. `--itk-charcoal` is
   the default and it FAILS from step 2 (3.51 against a 4.5 floor), so each of
   the last three steps names its own. */
.hb-st--s2 .hb-st__n { color: var(--itk-black); }
.hb-st--s3 .hb-st__n { color: var(--itk-white); }
.hb-st--s4 .hb-st__n { color: var(--itk-smoke); }

/* --- Track 2 takes the SAME ramp (DP-115 A) -------------------------------
   Ben ruling, 21 Aug 2026, from two mockups: the same teal rather than Intertek
   yellow. So the colour says *this is all one pipeline logic* and the LAYOUT
   carries the difference - five stages with gates against four steps with none.

   ZERO NEW TOKENS: Track 2 reuses Track 1's first four steps.

   THE ARGUMENT AGAINST IT WAS MADE AND OVERRULED, and it is recorded because a
   Phase 5 reader will meet the consequence rather than the reasoning: sharing
   the ramp makes Track 2's four boxes resemble a shorter Track 1, and Track 2 is
   NOT a truncated funnel - D30 gives it no gates at all, replacing Gates 0-3
   with one Team Manager sign-off. What carries that distinction instead is the
   gate line, which exists under Track 1 and pointedly does not under Track 2.
   If a colleague ever reads Track 2 as "Track 1 but shorter", that is where to
   look first.

   Only the last step flips its text, and the pair is already measured:
   `--itk-white` on `--stage-3` at 4.83. */
.hb-t2__s--s0 { background: var(--stage-0); }
.hb-t2__s--s1 { background: var(--stage-1); }
.hb-t2__s--s2 { background: var(--stage-2); }
.hb-t2__s--s3 { background: var(--stage-3); color: var(--itk-white); }

/* --- the hover reveal, on BOTH tracks (DP-114 A) --------------------------
   Ben ruling: option A, *"and can you add this to track 2 too"*. Each box lifts
   and reveals the field the banner otherwise drops - a DURATION on Track 1, and
   on Track 2 the one-line description of what that step is. So the dynamism
   does the simplification's own job: the short version invites the question and
   answers it in place, without sending anyone to the full page first.

   RESPECTS D70, which is why this option was takeable at all: the banner is
   "static until hovered" and nothing here moves until a pointer arrives.

   `visibility` PLUS `opacity`, NEVER `max-height: 0`, and that is a check
   talking rather than a preference. A collapsed box with text inside is exactly
   what `render.py`'s "text in a zero-size box" rule looks for, and the first
   draft of this treatment reported five defects that were every one deliberate.
   The frozen `.hiw-tip` already solved it this way; copying it keeps the check
   meaningful instead of teaching the operator to shrug at it.

   TRACK 1 OPENS DOWNWARD AND TRACK 2 OPENS UPWARD, deliberately. Track 2 is the
   LAST row in the banner, so a downward reveal would hang past the card's
   bottom padding - and vertical escape is precisely what `render.py` cannot see
   (O39). Opening upward keeps it inside the box the eye and the checker both
   trust. */
.hb-st, .hb-t2__s { transition: transform .18s ease, box-shadow .18s ease; }
.hb-st:hover, .hb-t2__s:hover {
  transform: translateY(-3px); box-shadow: var(--shadow-md); z-index: 4;
}

.hb-st, .hb-t2__s { position: relative; }
.hb-st__d, .hb-t2__d {
  position: absolute; left: 8px; right: 8px; z-index: 5;
  font-size: var(--fs-4xs); line-height: var(--lh-snug);
  color: var(--itk-white); background: var(--itk-black);
  border-radius: var(--radius-sm); padding: 3px 7px;
  opacity: 0; visibility: hidden; transition: opacity .18s ease;
}
.hb-st__d  { top: calc(100% + 1px); }     /* Track 1: downward */
.hb-t2__d  { bottom: calc(100% + 1px); }  /* Track 2: upward, it is the last row */
.hb-st:hover .hb-st__d,
.hb-t2__s:hover .hb-t2__d { opacity: 1; visibility: visible; }

@media (prefers-reduced-motion: reduce) {
  .hb-st, .hb-t2__s { transition: none; }
  .hb-st:hover, .hb-t2__s:hover { transform: none; }
}

/* --- the derived activity feed (DP-111 D, D72) ----------------------------
   A MEASURE, for the reason `.lpcol` has one: this is a list of sentences a
   reader goes down, and a 1250px row with eight words on it strands its own
   content. 620px is wider than the ranking list's 660px measure needs to be
   because these lines are shorter, and it is the width the frozen artefact was
   reviewed at. */
.actcol { max-width: 620px; }

/* Each row is an ANCHOR, and that is the half of D72 a stylesheet can show:
   every line is rendered from a record, so every line has somewhere real to go.
   A hand-written feed could only fake this - `FeedItem.link` is a CharField
   somebody types, not a foreign key. */
.act {
  display: flex; gap: var(--sp-3); padding: 9px 0;
  border-bottom: 1px solid var(--itk-smoke); text-decoration: none;
}
.act:hover { background: var(--itk-offwhite); }
.act__d {
  flex: 0 0 auto; width: 8px; height: 8px; border-radius: var(--radius-pill);
  background: var(--ox-green); margin-top: 5px;
}
.act__x { min-width: 0; }
.act__l { font-size: var(--fs-2xs); color: var(--itk-black); margin: 0; }
.act__l b { font-weight: var(--fw-semibold); }
.act:hover .act__l { color: var(--ox-green); }
.act__t { font-size: var(--fs-4xs); color: var(--itk-charcoal); margin: 1px 0 0; }

/* --- the PLAIN carousel (DP-112 B) ---------------------------------------
   Ben ruling, 21 Aug 2026, on a collision between the canon and a frozen ruling
   rather than on a preference. The canon puts a video carousel on Home; DP-12 B
   keeps the Oxford treatment on the library pages - "cleaner for the pages
   outside the library". Both cannot be honoured by reuse, because EVERY
   component that renders a video was born on the Tool Shed and is Oxford-styled.
   Measured on the assembled Home page before the ruling: the carousel painted
   `--ox-paper-vid`, `--ox-green` twice, `--ox-card`, `--ox-brass` and
   `--ox-ink` in GEORGIA SERIF onto a `--itk-offwhite` surface.

   LATENT SINCE WAVE 1. Home is simply the first non-library surface to want a
   video, so nothing had asked the question before.

   SIX RULES, AND THE COST IS NAMED RATHER THAN MINIMISED: this is a SECOND
   VIDEO LANGUAGE. A video on Home no longer looks exactly like the same video on
   Training / Learning, which is the continuity the verbatim reuse would have
   bought, and every future non-library surface wanting a video will reach for
   this modifier. That was put to Ben as the argument against B and he took it
   anyway, on the reading that an Oxford band on a plain page reads as a piece of
   another page that wandered in.

   NEW SELECTORS ONLY. Nothing above is touched, which is what lets the
   eleven-artefact sweep come back identical - the Tool Shed and Training /
   Learning keep the Oxford carousel unchanged and unamended.

   NO NEW CONTRAST PAIR, checked rather than assumed: this variant's text is
   `--itk-black` and `--itk-charcoal` on `--itk-offwhite`, and both pairs are
   already measured in `tools/contrast-pairs.tsv` as body copy on the page
   background. The one addition is `--fade-plain` in section 2, which is a
   gradient rather than a text colour. */
.videoshelf--plain { background: none; border-left: 0; padding-left: 0; }
.videoshelf--plain .videoshelf__t {
  color: var(--itk-black); font-family: var(--font-sans); font-size: var(--fs-sm);
}
.videoshelf--plain .vid__t {
  font-family: var(--font-sans); color: var(--itk-black);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
}
.videoshelf--plain .carousel__arrow {
  background: var(--itk-white); border-color: var(--itk-silver); color: var(--itk-charcoal);
}
.videoshelf--plain .carousel__fade { background: var(--fade-plain); }
.videoshelf--plain .carousel__row { padding-left: 0; }

/* --------------------------------------------------------------------------
   17. The health surface · wave 2, surface 11 (System health)
   --------------------------------------------------------------------------
   The System Architect's page (D31) and the only surface any governed document
   scopes to a role. That single fact is what lets a FORM live here: the whole
   page is already role-scoped, so the Targets tab needs no second role
   mechanism, and O37(b) - which nav items each role sees - stays Phase 5's
   exactly as DP-16 C left it.

   THIS IS THE ONLY SURFACE IN THE BUILD THAT BOTH REPORTS AND EDITS WHAT IT
   REPORTS AGAINST, and that is a Ben ruling rather than a drift. The Portfolio
   Dashboard freezes "the page writes nothing" as a gate; its sibling here
   deliberately does not, because the figures every metric is judged by have to
   be enterable somewhere and this is the page that uses them. The two halves are
   separated by a tab so the reading surface stays a reading surface.

   FIFTEEN decision points settled it, DP-116 to DP-130, over four rounds
   (21-22 Aug 2026); the log is in docs/design-targets.md. The ones a maintainer
   must not quietly reverse:
     DP-120 A  the participation COUNT and NO percentage. The canon's metric is
               "% of CW staff who have engaged" and NOTHING HOLDS A CW HEADCOUNT.
               An admin-typed denominator was offered and declined: it moves when
               IT creates accounts, so the figure would fall with no change in
               participation. Do not add one back.
     DP-123 C  the Dashboard's gauge is copied here WHOLE, both year chips. The
               standing objection - a sixth restatement of one number outside
               check-dashboard-sums.py's reach - is answered by
               tools/check-health-sums.py, not by argument.
     DP-125 B  the console is DARK, and its two direction colours are measured
               rather than chosen. See the token note below.
     DP-129    the active-project ceiling IS in MVP. G4's bandwidth row was
               parked only because "how are per-team targets set" was open; D73
               answered it, which fired that row's own re-entry trigger.
     DP-121    the stocks-and-flows block is ABSENT from this surface on purpose.
               It left for the interactive graph view (DP-128 b), which is its
               own SPEC after step 10. There is no link to it here either: a door
               to a room that does not exist is worse than no door.
   ------------------------------------------------------------------------ */

/* THE ACTIVE `More`, AND THIS IS THE FIRST SURFACE THAT NEEDED IT. Every other
   page is one of the four primary nav groups, so an anchor wears the active
   mark; System health lives under More, and `.is-active` was written only for
   `.shell__nav a`. DP-116 A applies DP-14 B's three signals unchanged - colour,
   weight and a position marker - so the chrome says "you are here" in one
   language on all eleven surfaces. */
.nav-more--on {
  color: var(--itk-white);
  font-weight: var(--fw-bold);
  border-bottom: 3px solid var(--itk-gold);
}

/* THE CONSOLE (DP-125 B). Ben asked for each figure in its own box and for it to
   look "more professional and like a control panel"; of three treatments he took
   the dark one. The ground is `--itk-black`, the topbar's own, and the hairline
   between cells is `--topbar-rule`, so this imports no new colour - it reuses the
   chrome's palette on the page body.

   THE TWO DIRECTION TOKENS ARE A MEASUREMENT AND THEY ARE SCOPED ON PURPOSE.
   Ben asked for green and red arrows. The frozen pair CANNOT be read here:
   `--text-success` (#2F4A3C) is 1.99 on this ground and `--text-danger`
   (#6E2639) is 1.84, against D68's 4.5 floor - both would have been invisible,
   which is the letter of the instruction and none of its point. So:

     --text-success-dk  #739987  6.11  = --itk-sage, ALREADY a brand token, at
                                        hue 151.6 against --ox-green's 148.9 -
                                        the same green lifted, not a new one
     --text-danger-dk   #CD6F88  5.73  oxblood's EXACT hue (344.2) and
                                        saturation, lightness 0.29 -> 0.62

   They are declared INSIDE `.csl` rather than in `:root`, and that is a guard
   rather than tidiness: both FAIL on white (3.16 and 2.86), so a future session
   reaching for `var(--text-success-dk)` on a light surface gets nothing at all
   instead of an unreadable pass. The on-white pair every other surface uses is
   untouched, so no fill acquires a second meaning - the O49 fault.

   COLOUR IS THE SECOND SIGNAL, NEVER THE ONLY ONE. The glyph carries direction
   and the colour carries whether that direction is good, which is why
   "Time to decide" shows a DOWN arrow in GREEN. Read the two independently. */
.csl {
  --text-success-dk: #739987;
  --text-danger-dk:  #CD6F88;
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1px;
  background: var(--topbar-rule); border-radius: var(--radius-md);
  overflow: hidden; margin-bottom: var(--sp-12); /* A-14: the 48px section rhythm */
}
.csl__c { background: var(--itk-black); padding: var(--sp-4) var(--sp-3); }
.csl__l { font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
          color: var(--itk-silver); margin-bottom: 6px; }
.csl__n { font-size: var(--fs-2xl); font-weight: var(--fw-bold); color: var(--itk-white);
          line-height: 1.05; }
.csl__u { font-size: var(--fs-sm); font-weight: var(--fw-regular); color: var(--itk-silver);
          margin-left: 3px; }
/* Gold, not a rung colour: this bar measures against a TARGET, and the four
   confidence rungs mean something else entirely two clicks away. */
.csl__bar { height: 6px; background: var(--topbar-rule); border-radius: var(--radius-pill);
            overflow: hidden; margin: var(--sp-3) 0 5px; }
.csl__bar span { display: block; height: 100%; background: var(--itk-gold); }
.csl__tg { font-size: var(--fs-4xs); color: var(--itk-silver); }
.csl__tg b { color: var(--itk-white); }
.csl__d { font-size: var(--fs-4xs); font-weight: var(--fw-bold); margin-top: 5px;
          color: var(--itk-silver); }
.csl__d--up { color: var(--text-success-dk); }
.csl__d--dn { color: var(--text-danger-dk); }

/* SECTION SEPARATION (DP-131 A, Ben 22 Aug: the sections "look too cluttered and
   separation is not clear enough"). Each section becomes a white panel on the
   page's own offwhite - which invents nothing, because `.gau` on the Dashboard
   and the console cells above are already exactly this object.

   SCOPED TO `.surface--health`, AND THAT SCOPING IS THE WHOLE REASON THIS IS A
   RULE HERE RATHER THAN AN AMENDMENT TO FOUR SIGNED ARTEFACTS. `.pdsec` is
   frozen and shared: project-detail uses it 23 times, the Dashboard 32,
   How-It-Works 18, Home 14. A bare `.pdsec` rule would move all four on the last
   surface of the phase - the exact risk SPEC-4 recorded when it ruled the build
   order. Phase 5 applies this by putting one class on one template.

   THE OPEN QUESTION THIS LEAVES, recorded rather than resolved: if the card is
   better here it is probably better on the Dashboard too, and scoping it makes
   System health the one surface that looks different from its siblings. Put to
   Ben with the treatment; he took the card and left the scope where it is.

   THE GAP IS 32px AND IT WENT UP BEFORE IT CAME BACK DOWN. Ben asked first for
   more separation (32 -> 40), then, on seeing the card, for slightly less
   because the card's own border and padding already do the separating. Back to
   `--sp-8`, which is the frozen default - so the card adds the separation and
   the spacing rhythm stays the one every other surface uses. */
.surface--health .pdsec {
  background: var(--itk-white); border: 1px solid var(--itk-smoke);
  border-radius: var(--radius-md); padding: var(--sp-5);
  margin-bottom: var(--sp-8);
}
/* The heavy 2px rule is the separator of LAST resort on a flat page. Inside a
   card the card edge already separates, so the rule drops to a hairline and
   stops competing with it. */
.surface--health .pdsec__hd { border-bottom-width: 1px; }

/* THE HISTORY TABLE (DP-127 A, extended by Ben's 22 Aug annotation to carry the
   trend charts as a final wide column). Every column is centred except the first
   and the last, on his instruction; `vertical-align: middle` goes with it,
   because without it a one-line figure floats at the top of a row whose chart is
   110px tall. Zebra striping and the black header are the frozen table (P8).

   DP-132, Ben 22 Aug: more room per row and a darker separating line. The
   diagnosis is worth keeping rather than just the fix - `--itk-smoke` measures
   **1.06** against the white row it sits on, so the old line was not subtle, it
   was very close to invisible. `--itk-silver` measures 2.6. Vertical padding
   7px -> 12px with it. */
.hist { width: 100%; border-collapse: collapse; table-layout: fixed; }
.hist th, .hist td { font-size: var(--fs-2xs); padding: 12px 8px;
                     border-bottom: 1px solid var(--itk-silver);
                     text-align: center; vertical-align: middle; }
.hist th.mcol, .hist td.mcol { text-align: left; }
.hist th.gcol, .hist td.gcol { text-align: left; padding-right: 12px; }
.hist thead th { background: var(--itk-black); color: var(--itk-white);
                 font-weight: var(--fw-bold); border-bottom: 0; }
.hist tbody tr:nth-child(even) { background: var(--itk-offwhite); }
.hist td.now  { font-weight: var(--fw-bold); color: var(--itk-black); }
.hist td.tcol { font-weight: var(--fw-semibold); }

/* ONE TREND CHART, AND ITS PROPORTIONS ARE ARITHMETIC RATHER THAN TASTE. The
   first attempt was ~430 x 48, an aspect of 9:1, which Ben correctly called
   stretched - that is a strip, not a chart. This plot is ~250 x 84, which is
   3:1, and it gets there by taking width AWAY as well as adding height: the
   y-axis gutter and the target-label gutter both come out of the cell.

   THE TARGET NUMBER SITS OUTSIDE THE PLOT, and that is a fix rather than a
   preference. Inside it there is no safe side: Submissions peaks on its LAST bar
   so a right-hand label collides, and Median days peaks on its FIRST so a
   left-hand label collides. The gutter is clear on every row and aligns the
   number exactly to the rule it names. It also needs no painted background,
   which matters because the rows are zebra-striped.

   THE Y AXES RUN TO ROUND NUMBERS chosen to clear both the series peak and the
   target - 8, 40, 20, 80% and 10. An earlier version scaled to peak-plus-15%,
   which produced maxima like 8.05 and 34.5 that cannot be labelled. */
.tr { font-size: var(--fs-4xs); }
/* The axis title sits above the plot rather than rotated beside it: a rotated
   label needs width the plot cannot spare at this size. */
.tr__u { color: var(--itk-charcoal); margin: 0 0 3px; letter-spacing: var(--ls-caps);
         text-transform: uppercase; }
.tr__g { display: grid; grid-template-columns: 22px minmax(0, 1fr) 30px;
         column-gap: 6px; row-gap: 3px; }
.tr__y { grid-column: 1; grid-row: 1; display: flex; flex-direction: column;
         justify-content: space-between; align-items: flex-end; height: 84px;
         color: var(--itk-charcoal); line-height: 1; }
.tr__p { grid-column: 2; grid-row: 1; position: relative; height: 84px;
         border-bottom: 1px solid var(--itk-silver); }
.tr__b { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 3px;
         align-items: end; height: 100%; }
/* `--radius-sm` rather than a literal 1px, and the token check is why: a bare
   1px would be the twelfth allowed length on this surface for one hairline. */
.tr__b span { background: var(--itk-charcoal); min-height: 1px;
              border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
.tr__l { position: absolute; left: 0; right: 0; border-top: 2px dashed var(--itk-black); }
.tr__t { grid-column: 3; grid-row: 1; position: relative; height: 84px; }
.tr__t b { position: absolute; left: 0; color: var(--itk-black);
           transform: translateY(50%); white-space: nowrap; }
.tr__x { grid-column: 2; grid-row: 2; display: grid;
         grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 3px;
         color: var(--itk-charcoal); }
.tr__x span { text-align: center; }

/* THE READ-ONLY TARGETS TABLE (DP-126 A, headings prefixed "Target" on Ben's
   22 Aug annotation). Two footer rows rather than one, and the pair carries the
   whole of D73's shape: `.tgt-sum` is the eight teams added up, `.tgt-cw` is
   CW's own figure. BEN RULED THEY NEED NOT MATCH - a CW target above the team
   total is a deliberate stretch - so the table SHOWS BOTH and a gap is visible
   instead of hidden. check-health-sums.py verifies the sum row against the eight
   teams and deliberately does NOT require the CW row to equal it. */
.tgt { width: 100%; border-collapse: collapse; }
.tgt th, .tgt td { font-size: var(--fs-2xs); padding: 6px 10px; text-align: right;
                   border-bottom: 1px solid var(--itk-smoke); }
.tgt th:first-child, .tgt td:first-child { text-align: left; }
.tgt thead th { background: var(--itk-black); color: var(--itk-white);
                font-weight: var(--fw-bold); border-bottom: 0; }
.tgt tbody tr:nth-child(even) { background: var(--itk-offwhite); }
.tgt tfoot td { border-bottom: 0; }
.tgt .tgt-sum td { color: var(--itk-charcoal); border-top: 1px solid var(--itk-black);
                   padding-top: 7px; }
.tgt .tgt-cw td  { font-weight: var(--fw-bold); color: var(--itk-black); }

/* THE TARGETS FORM (Ben, 22 Aug: "a simple kind of form with the targets per
   team per year that can be edited as needed"). The fields are the frozen `.inp`
   at its own width; nothing about the field states is re-invented here. */
.tgtyear { max-width: 15rem; }
.tgtform { width: 100%; border-collapse: collapse; }
.tgtform th, .tgtform td { font-size: var(--fs-2xs); padding: 5px 10px; text-align: right;
                           border-bottom: 1px solid var(--itk-smoke); vertical-align: middle; }
.tgtform th:first-child, .tgtform td:first-child { text-align: left; }
.tgtform thead th { background: var(--itk-black); color: var(--itk-white);
                    font-weight: var(--fw-bold); border-bottom: 0; }
.tgtform tfoot td { border-bottom: 0; color: var(--itk-charcoal);
                    border-top: 1px solid var(--itk-black); padding-top: 7px; }
.tgtform .inp { width: 7rem; margin-left: auto; text-align: right; padding: 5px 8px; }
.cwgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4); }
.tgtsave { display: flex; align-items: center; gap: var(--sp-4);
           font-size: var(--fs-2xs); color: var(--itk-charcoal); margin-top: var(--sp-6); }

/* A SINGLE-FILL SPLIT BAR. The frozen `.spl` row is reused whole for the
   participation and category blocks - DP-89 C's share-of-total rule holds here
   for the same reason it holds on the Dashboard, one scale everywhere - but a
   head count and a category count have no confidence rung, so the bar takes one
   fill instead of a stack. */
.spl__f--one { background: var(--itk-charcoal); }

/* The cross-team pair (DP-122 A). Two figures, each stated as a fraction in
   words rather than as a percentage, because at 6 of 27 a percentage implies a
   precision the sample does not have. */
.xt { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
.xt__c { background: var(--sec-band); border-radius: var(--radius); padding: var(--sp-4); }
.xt__n { font-size: var(--fs-xl); font-weight: var(--fw-bold); color: var(--itk-black);
         line-height: 1.1; }
.xt__u { font-size: var(--fs-2xs); font-weight: var(--fw-regular);
         color: var(--itk-charcoal); margin-left: 5px; }
.xt__l { font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
         color: var(--itk-charcoal); margin-top: 4px; }

/* FORECAST ACCURACY - predicted at Banded against realised at Verified. Two bars
   on one baseline sharing ONE scale across every row, so a long bar means a big
   figure rather than a big row. The two greys are deliberately not rung colours:
   this block compares a prediction with an outcome, and the confidence ladder
   means something else. */
.fckey { display: flex; gap: var(--sp-5); font-size: var(--fs-4xs);
         color: var(--itk-charcoal); margin-bottom: var(--sp-3); }
.fckey span { display: flex; align-items: center; gap: 6px; }
.fckey i { width: 11px; height: 11px; border-radius: 3px; }
.sw--pred { background: var(--itk-silver); }
.sw--real { background: var(--itk-charcoal); }
.fc { display: grid; grid-template-columns: 20rem minmax(0, 1fr) 9rem; gap: var(--sp-3);
      align-items: center; padding: 6px 0; }
.fc + .fc { border-top: 1px solid var(--itk-offwhite); }
.fc__l { font-size: var(--fs-2xs); color: var(--itk-black); }
.fc__l em { font-style: normal; color: var(--itk-charcoal); }
.fc__t { display: grid; gap: 3px; }
.fc__p, .fc__r { height: 9px; background: var(--itk-smoke); border-radius: var(--radius-pill);
                 overflow: hidden; }
.fc__p span, .fc__r span { display: block; height: 100%; }
.fc__p span { background: var(--itk-silver); }
.fc__r span { background: var(--itk-charcoal); }
.fc__v { font-size: var(--fs-2xs); font-weight: var(--fw-semibold); color: var(--itk-black);
         text-align: right; }
.fc__v em { font-style: normal; font-weight: var(--fw-regular); color: var(--itk-charcoal); }

/* Tracking freshness. Three counts on the frozen `--sec-band` ground, and the
   page NAMES the project that has gone quiet underneath rather than only
   counting it - a count of one tells the System Architect there is a problem and
   not which one. */
.fr { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-3); }
.fr__c { background: var(--sec-band); border-radius: var(--radius); padding: var(--sp-3);
         text-align: center; }
.fr__n { font-size: var(--fs-xl); font-weight: var(--fw-bold); color: var(--itk-black);
         line-height: 1.1; }
.fr__l { font-size: var(--fs-4xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
         color: var(--itk-charcoal); margin-top: 2px; }
.fr__d { font-size: var(--fs-4xs); color: var(--itk-charcoal); margin-top: 4px; }

/* TRIAGE PACE (DP-124 A). The bars are days on a shared scale set by the slowest
   case, and the canon's stated two-week design intent is a reference rule
   ACROSS ALL ROWS rather than a tick on one - the first draft drew it inside the
   last row and it read as part of that bar. The marker overlay repeats the row
   grid so the rule lands in the bar column only, and is `aria-hidden` because
   the caption already states it in words.

   THE WORDING IS LOAD-BEARING: "design intent", never "target". The canon's own
   target-setting question was open when this was drawn, and even now D73's store
   deliberately has no field for decision time - a page that called this a target
   would invent one. */
.paceplot { position: relative; padding-bottom: 20px; }
.pacerow { display: grid; grid-template-columns: 11rem minmax(0, 1fr) 5rem; gap: var(--sp-3);
           align-items: center; margin-bottom: var(--sp-2); }
.pacerow:last-of-type { margin-bottom: 0; }
.pacerow__l { font-size: var(--fs-2xs); color: var(--itk-black); }
.pacerow__v { font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
              color: var(--itk-black); text-align: right; }
.pacebar { height: 14px; background: var(--itk-smoke); border-radius: var(--radius-pill);
           overflow: hidden; }
.pacebar span { display: block; height: 100%; background: var(--itk-charcoal); }
.pacemark { position: absolute; top: 0; left: 0; right: 0; bottom: 20px;
            display: grid; grid-template-columns: 11rem minmax(0, 1fr) 5rem; gap: var(--sp-3);
            pointer-events: none; }
.pacemark__c { position: relative; }
.pacemark__c i { position: absolute; top: -4px; bottom: -4px; width: 2px;
                 background: var(--itk-black); transform: translateX(-50%); }
.pacemark__c b { position: absolute; top: 100%; margin-top: 6px; font-size: var(--fs-4xs);
                 color: var(--itk-black); white-space: nowrap; transform: translateX(-50%); }
