/* TFM v8 -- the shell. src/web/shared/tfm-shell.css
   NO COLOUR LITERAL APPEARS HERE. Every colour is a custom property DEFINED IN
   shared/tfm-tokens.css, which is the one colour file unit 4 requirement 1
   names. The tokens used to be written onto :root from JavaScript; they are
   plain CSS now, because a page that loses its palette when a module fails to
   link is a page whose styling depends on code executing. Measured live. */
* { box-sizing: border-box; }
body { margin: 0; font: 15px/1.55 system-ui, -apple-system, Segoe UI, sans-serif;
       background: var(--tfm-ground); color: var(--tfm-ink); }

/* Bare form controls, so a page that has not reached for .tfm-btn still sits on
   the palette. Without this a plain <button> renders in the browser's own
   colours -- light grey on a dark ground, which is how admin and dashboard
   looked. */
button, input, select, textarea {
  font: inherit; color: var(--tfm-ink); background: var(--tfm-panel);
  border: 1px solid var(--tfm-edge); border-radius: .4rem; padding: .35rem .6rem;
}
button { cursor: pointer; }
button:hover, input:focus, select:focus, textarea:focus {
  border-color: var(--tfm-accent); outline: none;
}
button[disabled] { opacity: .5; cursor: default; border-color: var(--tfm-edge); }
a { color: var(--tfm-accent); }
h1, h2, h3 { line-height: 1.2; margin: 0 0 .5rem; }

/* ============================================================================
   THE SHELL: A RAIL, A HEADER STRIP, AND A RECESSED PAGE.
   Ruled 2026-09-01 (the look) and 2026-09-01 part two (the structure), which is
   the half that was missing: the first pass changed the ground and kept the
   shape, so nothing looked different.

   **BUILT ONCE, IN `shared/tfm-chrome.js`, AND NEVER TYPED INTO A PAGE.** Five
   pages carrying five copies of a navigation is the copy-paste rule broken in
   the most visible place in the product. The rules are here; the markup that
   uses them has exactly one author.
   ========================================================================== */

/* The page is a two-column grid: the rail, then everything else. The rail is
   fixed-width and full-height; `min-width: 0` on the right column is what stops
   a wide table forcing the whole page to scroll sideways instead of itself. */
.tfm-app { display: grid; grid-template-columns: 13.25rem 1fr;
           min-height: 100vh; }
.tfm-app > .tfm-main { display: flex; flex-direction: column; min-width: 0;
                       min-height: 100vh; }

.tfm-side { background: var(--tfm-nav); color: var(--tfm-nav-ink);
            border-right: 1px solid var(--tfm-nav-edge);
            display: flex; flex-direction: column; gap: .15rem;
            padding: 0 0 .75rem; position: sticky; top: 0; height: 100vh;
            overflow-y: auto; }
.tfm-side .brand { display: flex; align-items: center; gap: .5rem;
                   padding: .8rem 1rem; font-weight: 600;
                   border-bottom: 1px solid var(--tfm-nav-edge);
                   margin-bottom: .5rem; }
/* The caption over the group. Muted on purpose: it names the group, it is not
   an item, and an item is the only thing here that should read as clickable. */
.tfm-side .cap { padding: .35rem 1rem .2rem; font-size: .68rem;
                 letter-spacing: .08em; text-transform: uppercase;
                 color: var(--tfm-nav-ink-muted); }
.tfm-side a { display: flex; align-items: center; gap: .65rem;
              padding: .55rem 1rem; color: var(--tfm-nav-ink);
              text-decoration: none; font-size: .92rem;
              border-left: .2rem solid transparent; }
.tfm-side a:hover { background: var(--tfm-nav-high); }
.tfm-side a svg { width: 1.05rem; height: 1.05rem; flex: none; }
/* **THE CURRENT ITEM IS MARKED TWICE, NOT ONCE.** A background alone is a
   colour difference, and `aria-current` alone is invisible. The left rule is the
   carrier a screenshot shows and a screen reader gets the attribute. */
.tfm-side a[aria-current="page"] { background: var(--tfm-nav-high);
                                   border-left-color: var(--tfm-nav-mark);
                                   font-weight: 600; }
.tfm-side .foot { margin-top: auto; padding: .75rem 1rem 0; }

.tfm-bar { display: flex; align-items: center; gap: 1rem; padding: .55rem 1rem;
           background: var(--tfm-panel); border-bottom: 1px solid var(--tfm-edge); }
/* The page's own name, beside the product's. */
.tfm-bar .page-name { font-weight: 600; font-size: 1.02rem; }
/* The level tag. Not a colour alone -- the word is written in it. */
.tfm-bar .lvl { font-size: .68rem; letter-spacing: .06em; text-transform: uppercase;
                padding: .12rem .45rem; border-radius: .6rem; font-weight: 700;
                border: 1px solid var(--tfm-edge); color: var(--tfm-ink-muted); }
.tfm-bar .lvl.admin { color: var(--tfm-admin-tag); border-color: var(--tfm-admin-tag); }

/* **CONTENT SITS ON THE RECESSED GROUND, NOT ON THE PANEL.** The cards are
   white and the page behind them is the off-white `--tfm-ground`; without this
   the cards are invisible because they are the same colour as the page. */
.tfm-page { flex: 1; min-width: 0; background: var(--tfm-ground);
            padding: 1.15rem 1.4rem 2.5rem; }
.tfm-page > h1 { margin: 0 0 1rem; font-size: 1.35rem; }
/* The map's content column. Same rail, same strip, NO recessed padded page:
   the canvas fills what is left, which is what `bleed: true` asks for. */
.tfm-bleed { flex: 1; min-width: 0; min-height: 0; display: flex;
             flex-direction: column; }

/* **A SECTION CARD, AND IT USES THE WIDTH IT HAS.** Part two section 3: "the
   current table squeezes into the left half with three buttons stacked in a
   narrow column and most of the width empty." That was `max-width: 60rem` on
   BOTH the panel and the table, written into `dashboard.html` -- a second and
   narrower copy of `.tfm-table`, which is shared, full-width, and was being
   overridden by it. The cap is gone and the rule is here, once. */
.tfm-section { background: var(--tfm-panel); border: 1px solid var(--tfm-edge);
               border-radius: .6rem; padding: 1.1rem 1.2rem; margin: 0 0 1.15rem; }
.tfm-section > h2 { margin: 0 0 .75rem; font-size: 1.05rem; }
/* A wide table scrolls ITSELF rather than the page. */
.tfm-section .rows { overflow-x: auto; }
/* **ACTIONS INLINE, ON ONE LINE.** They were stacking because the cell was
   narrow; `white-space: nowrap` and a row-direction flex keep them beside each
   other and let the row be one line, which is what part two asked for. */
/* ---- the triage strip and the card view (owner, 2026-09-02) -------------- */
/* **THE COUNTS ARE FILTERS, NOT A CHART.** `product/requirements/14-` rules that
   out; each card is a control, so each is a button and behaves like one. */
.sev-cards { display: grid; gap: .8rem; margin: 0 0 1.1rem;
             grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }
.sev-card { display: flex; flex-direction: column; gap: .15rem; text-align: left;
            font: inherit; cursor: pointer; padding: .7rem .9rem;
            background: var(--tfm-panel); color: var(--tfm-ink);
            border: 1px solid var(--tfm-edge); border-top: .2rem solid var(--tfm-edge);
            border-radius: .5rem; }
.sev-card:hover:not(:disabled) { border-color: var(--tfm-accent); }
/* **A CARD WITH NOTHING IN IT IS DIMMED AND UNCLICKABLE, NOT REMOVED.** The four
   severities are a ruled set; a missing card would read as one that does not exist. */
.sev-card:disabled { cursor: default; opacity: .55; }
.sev-card[aria-pressed="true"] { background: var(--tfm-panel-high);
                                 box-shadow: inset 0 0 0 1px var(--tfm-accent); }
.sev-label { font-size: .7rem; letter-spacing: .08em; text-transform: uppercase;
             color: var(--tfm-ink-muted); }
.sev-count { font-size: 1.9rem; font-weight: 600; line-height: 1.1; }
.sev-note { font-size: .72rem; color: var(--tfm-ink-muted); }

.issue-cards { display: grid; gap: .9rem;
               grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr)); }
.issue-card { background: var(--tfm-panel); border: 1px solid var(--tfm-edge);
              border-left: .25rem solid var(--tfm-edge); border-radius: .5rem;
              padding: .8rem .9rem; }
.ic-head { font-weight: 600; margin-bottom: .5rem; }
/* Label above value, so a long free-text "where" wraps instead of squeezing. */
.ic-body { display: grid; grid-template-columns: max-content 1fr; gap: .15rem .7rem;
           margin: 0 0 .6rem; font-size: .82rem; }
.ic-body dt { color: var(--tfm-ink-muted); font-size: .72rem; }
.ic-body dd { margin: 0; }
.ic-actions { display: flex; flex-wrap: wrap; gap: .3rem; }
.view-toggle { display: inline-flex; gap: .25rem; margin-left: .4rem; }
.view-toggle .tfm-btn[aria-pressed="true"] { background: var(--tfm-panel-high);
                                             border-color: var(--tfm-accent); font-weight: 600; }

.tfm-table td.actions { white-space: nowrap; width: 1%; }
.tfm-table td.actions > * { display: inline-flex; margin-right: .3rem; }
label.filter { margin-right: 1rem; color: var(--tfm-ink-muted); }

/* **THE CHROME DOES NOT PRINT, ON ANY PAGE.** `reports.html` already hid
   `.tfm-bar` in its own print block; the rail is new and would otherwise have
   put a navigation down the left edge of every printed report. The rule is here
   rather than there because it is true of every page that gains the chrome, and
   a per-page copy is how the next page gets it wrong. */
@media print {
  .tfm-side { display: none !important; }
  .tfm-app { display: block; }
  .tfm-page, .tfm-bleed { padding: 0; background: none; }
  /* **THE CONTROLS DO NOT PRINT, AND NEITHER DOES A SECTION THAT IS NOT ON
     SCREEN.** The dashboard's rail switches sections; printing the hidden one
     would hand somebody a page they did not ask for. This is the OPPOSITE of
     `reports.html`, where every tab prints on purpose because a report is one
     document -- the difference is that a report's tabs are parts of a whole and
     these sections are two different lists. */
  .tfm-btn, .tfm-search, .tfm-filters, .tfm-columns, label.filter,
  .view-toggle { display: none !important; }
  /* **THE TRIAGE STRIP PRINTS, THE FACT THAT IT IS CLICKABLE DOES NOT.** The
     counts are part of what the page said; the hover and pressed states are not. */
  .sev-card { box-shadow: none !important; border-color: var(--tfm-paper-edge) !important; }
  .issue-card { break-inside: avoid; border-color: var(--tfm-paper-edge) !important; }
  .tfm-section[hidden] { display: none !important; }
  .tfm-section { border: none; padding: 0; }
  /* **WHAT WAS PRINTED, AND WHAT IT LEAVES OUT.** Screen-only otherwise. */
  .print-stamp { display: block !important; font-size: .78rem; margin: 0 0 .7rem; }
  body { background: var(--tfm-paper); color: var(--tfm-paper-ink); }
  .tfm-table, .tfm-table th, .tfm-table td { border-color: var(--tfm-paper-edge) !important; }
  .tfm-table tbody tr:hover { background: none !important; }
  .tfm-table td.actions { display: none !important; }
  .tfm-table th:last-child { display: none; }
}
/* Hidden on screen; the print block above reveals it. */
.print-stamp { display: none; }
.tfm-logo { display: flex; align-items: center; gap: .5rem; font-weight: 600; }
.tfm-logo .mark { width: 1.6rem; height: 1.6rem; border-radius: .35rem;
                  background: var(--tfm-accent); color: var(--tfm-accent-ink);
                  display: grid; place-items: center; font-weight: 700; }
.tfm-bar .spacer { flex: 1; }
.tfm-bar .who { color: var(--tfm-ink-muted); font-size: .9rem; }

.tfm-btn { font: inherit; padding: .4rem .85rem; border-radius: .4rem; cursor: pointer;
           border: 1px solid var(--tfm-edge); background: transparent; color: var(--tfm-ink); }
.tfm-btn:hover { border-color: var(--tfm-accent); }
.tfm-btn.primary { background: var(--tfm-accent); color: var(--tfm-accent-ink);
                   border-color: var(--tfm-accent); font-weight: 600; }

.tfm-card { display: block; background: var(--tfm-panel); border: 1px solid var(--tfm-edge);
            border-radius: .6rem; padding: 1rem; text-decoration: none; color: inherit; }
.tfm-card:hover { border-color: var(--tfm-accent); }
.tfm-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
            padding: 1.5rem; max-width: 78rem; }
.tag { display: inline-block; font-size: .68rem; letter-spacing: .04em; text-transform: uppercase;
       padding: .12rem .45rem; border-radius: .25rem; border: 1px solid var(--tfm-edge);
       color: var(--tfm-ink-muted); margin: .1rem .2rem .1rem 0; }
.tag.live { color: var(--tfm-live); border-color: var(--tfm-live); }
.tag.admin { color: var(--tfm-admin-tag); border-color: var(--tfm-admin-tag); }
.muted { color: var(--tfm-ink-muted); }
.msg { border: 1px solid var(--tfm-edge); border-radius: .4rem; padding: .7rem; margin-top: .7rem;
       background: var(--tfm-panel); }
.msg[hidden] { display: none; }
/* **A STATUS LINE UNDER A LONG LIST IS PINNED, NOT LEFT BELOW THE FOLD** (finding 21). The dashboard's
   two section lines carry it (set by shared/tfm-worklist.js): the line sticks to the bottom of the
   window while its section is on screen, and sits in its own place once the list's end is reached. */
.msg.pinned { position: sticky; bottom: 0; z-index: 2; box-shadow: 0 -.3rem .8rem rgba(0, 0, 0, .25); }
@media print { .msg.pinned { position: static; box-shadow: none; } }

/* ---- panels ---------------------------------------------------------------
   shared/tfm-panels.js makes an element draggable, closable and Escape-
   dismissable, and NOTHING USED IT -- because there was no `.tfm-panel` rule
   anywhere, so a panel had no position, no ground and no size to resize. The
   behaviour existed and could not be seen. `resize: both` is the resizing half:
   the module deliberately leaves it to CSS rather than reimplementing a corner
   grip in JavaScript. */
/* max-width and min-width both yield to the VIEWPORT. `tfm-panels.place()` clamps the
   position; these clamp the size, and both halves are needed -- a panel placed inside a
   360px screen still overflows it if its own minimum is 15rem = 240px and its maximum is
   32rem = 512px. The owner met the result on a phone: right-hand text cut off with no way
   to reach it. `min()` rather than a media query, so there is one rule rather than a
   breakpoint somebody has to keep in step with `place()`. */
/* **RESIZABLE MEANS THE OPERATOR CAN MAKE IT BIGGER, AND `max-width: 32rem` MEANT THEY
   COULD NOT** *(owner, brief 4 section 4: "a fixed box with a scrollbar; on a wide screen
   the tables are read through a slot while the map has room to spare")*. `resize: both` was
   already here and already worked -- it just could not pass 32rem, so dragging did nothing
   in the direction that mattered.

   The CEILING is now the viewport and the STARTING width stays 32rem, set per panel by
   `tfm-panels.place()`. That is the whole change: the default layout is untouched and the
   drag is no longer capped short of the screen.

   **THE PHONE CLAMP IS NOT WEAKENED** (section 4.2) -- it is the same `calc(100vw - 1rem)`
   that was inside the `min()`, now standing alone as the only bound. A panel still cannot
   exceed the viewport, which is what the clamp was for; what it can no longer do is stop at
   32rem on a 1,600 px screen.

   `min-height` is set so a panel dragged to nothing can still be grabbed again -- a resize
   handle on a 0-pixel box is a panel an operator has destroyed by accident. */
/* **THE DEFAULT WIDTH IS THE CONTENT'S, AND THAT IS A REGRESSION OF MINE BEING REPAIRED**
   *(owner, brief 7 section 4.1: a panel is sized to its content, not to the viewport --
   eleven short rows filling the screen edge to edge).*

   Brief 4 needed the panel to resize past 32rem, and I did that by DELETING the 32rem from
   `max-width: min(32rem, calc(100vw - 1rem))`. An absolutely positioned box with `width:
   auto` is shrink-to-fit, and a `.tfm-fields` grid with a `1fr` column has a preferred width
   larger than the space, so it resolved to the max-width -- the whole viewport. **Removing a
   ceiling changed the default, not just the limit**, which is the part I did not check.

   So the two concerns are separated properly this time: `width` sets what it opens at,
   `max-width` sets how far it may be dragged. `resize` writes an inline width that overrides
   the former and is still bounded by the latter, so brief 4's resizing is untouched and
   brief 3's phone clamp is still the floor. `max-content` is the fallback for a browser
   without `fit-content()`; both are content-sized, which is the requirement. */
.tfm-panel { position: absolute; z-index: 30;
             min-width: min(15rem, calc(100vw - 1rem));
             width: max-content;
             width: fit-content(32rem);
             max-width: calc(100vw - 1rem);
             min-height: 4rem;
             max-height: 90vh; overflow: auto; resize: both;
             background: var(--tfm-panel); color: var(--tfm-ink);
             border: 1px solid var(--tfm-edge); border-radius: .6rem;
             /* **Softened for the 2026-09-01 light ground.** 45% black lifted a
                panel off a near-black page; on white the same value is a smudge
                rather than depth. A card on paper casts a faint shadow, which is
                the v7 look this is taking. */
             box-shadow: 0 2px 4px rgb(0 0 0 / 6%), 0 8px 24px rgb(0 0 0 / 10%); }
.tfm-panel[hidden] { display: none; }
.tfm-panel > header { display: flex; align-items: center; gap: .5rem;
                      padding: .5rem .7rem; cursor: move; user-select: none;
                      border-bottom: 1px solid var(--tfm-edge);
                      background: var(--tfm-panel-high);
                      border-radius: .6rem .6rem 0 0; position: sticky; top: 0; }
.tfm-panel > header h4 { margin: 0; font-size: .9rem; flex: 1; }
.tfm-panel > header button { padding: .15rem .35rem; line-height: 1; }
.tfm-panel .body { padding: .7rem; }

/* Tabs. ONE definition: the reports page styled these and the map's circuit
   panel needed the same thing, and two copies is how a build ends up with two
   answers to what a selected tab looks like. The formatter marks the live tab
   with aria-selected, so that is what this styles -- what is emitted, not what
   a tab bar usually looks like. */
.tabbar { display: flex; gap: .3rem; flex-wrap: wrap; margin: .2rem 0 .6rem; }
.tabbar button { font: inherit; }
.tabbar button[aria-selected="true"] { border-color: var(--tfm-accent);
                                       color: var(--tfm-accent); }

/* A field list, as the identify popup shows it. */
.tfm-fields { display: grid; grid-template-columns: minmax(7rem, auto) 1fr;
              gap: .3rem .8rem; font-size: .85rem; }
.tfm-fields dt { color: var(--tfm-ink-muted); }
.tfm-fields dd { margin: 0; overflow-wrap: anywhere; }
/* THE PORT LINE UNDER ITS PLACE (brief 5 section 1). Subordinate by weight and indent, so a
   Connectivity list reads as places with a port under each rather than sixteen equal rows.
   Colour is a TOKEN, never a literal: the page is theme-aware and a hard-coded grey is
   invisible on one of the two themes. */
.tfm-fields dt.tfm-sub { color: var(--tfm-ink-muted); font-size: 0.9em; padding-left: 0.6rem; }
.tfm-fields dd.tfm-sub { font-size: 0.9em; }
/* A VALUE THE DOOR COULD NOT NAME reads as a remark, not as data -- it is a sentence about
   what is absent, and it must not be mistaken for the port's name. */
.tfm-fields dd.tfm-unknown { color: var(--tfm-ink-muted); font-style: italic; }

/* The one data table, shared/tfm-table.js. Columns resize from the header cell. */
.tfm-table { border-collapse: collapse; width: 100%; font-size: .85rem; }
/* **A WIDE PANEL MUST NOT PRODUCE A WIDE PAGE** (section 4.1 with 4.2). The table fills the
   panel, and when its columns need more than that the SCROLL happens inside its own wrapper
   rather than pushing the panel -- and therefore the page -- sideways. This is the same rule
   the artifact guidance states for wide content, applied to the one data table. */
.tfm-panel .body > table.tfm-table { display: block; overflow-x: auto; }
.tfm-panel .body > table.tfm-table > tbody,
.tfm-panel .body > table.tfm-table > thead { display: table; width: 100%; }
.tfm-table th, .tfm-table td { text-align: left; padding: .35rem .55rem;
                               border-bottom: 1px solid var(--tfm-edge); }
.tfm-table th { color: var(--tfm-ink-muted); font-size: .72rem; letter-spacing: .04em;
                text-transform: uppercase; white-space: nowrap; }
.tfm-table tbody tr:hover { background: var(--tfm-panel-high); }

/* The metres box under a fault reading (brief 6). Wraps on a phone rather than pushing the
   panel wider -- the panel is already resizable and must not need to be. */
.tfm-ask { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center;
           margin: .6rem 0 .2rem; }
.tfm-ask label { color: var(--tfm-ink-muted); font-size: .85em; flex: 1 1 100%; }
.tfm-ask input { flex: 1 1 6rem; min-width: 0; }

/* The buffer tube heading in the picker's second level (brief 7 section 2.3): a cable holds
   buffers and a buffer holds strands, so the list is broken where the tube changes. */
.tfm-tube { margin: .5rem 0 .25rem; font-size: .75rem; letter-spacing: .02em;
            color: var(--tfm-ink-muted); text-transform: uppercase; }
/* One fibre's colour. **The name is in the `title` as well as the swatch** -- a colour alone
   excludes a colour-blind operator, and Slate against Black is the pair that costs. */
.tfm-fibre-dot { display: inline-block; width: .7rem; height: .7rem; border-radius: 50%;
                 margin-right: .4rem; vertical-align: -1px;
                 border: 1px solid var(--tfm-edge); }
.tfm-fibre-dot[data-color="blue"]   { background: var(--tfm-fibre-blue); }
.tfm-fibre-dot[data-color="orange"] { background: var(--tfm-fibre-orange); }
.tfm-fibre-dot[data-color="green"]  { background: var(--tfm-fibre-green); }
.tfm-fibre-dot[data-color="brown"]  { background: var(--tfm-fibre-brown); }
.tfm-fibre-dot[data-color="slate"]  { background: var(--tfm-fibre-slate); }
.tfm-fibre-dot[data-color="white"]  { background: var(--tfm-fibre-white); }
.tfm-fibre-dot[data-color="red"]    { background: var(--tfm-fibre-red); }
.tfm-fibre-dot[data-color="black"]  { background: var(--tfm-fibre-black); }
.tfm-fibre-dot[data-color="yellow"] { background: var(--tfm-fibre-yellow); }
.tfm-fibre-dot[data-color="violet"] { background: var(--tfm-fibre-violet); }
.tfm-fibre-dot[data-color="rose"]   { background: var(--tfm-fibre-rose); }
.tfm-fibre-dot[data-color="aqua"]   { background: var(--tfm-fibre-aqua); }


/* ---- the splice view ------------------------------------------------------
   **v7's DRESS, ON v8's DOCUMENT** *(owner, 2026-08-19, amendment 2)*: the v8
   view was built roomy and stood on a page of its own, and the owner pressed
   both systems side by side and asked for v7's -- compact rows, a small swatch
   BESIDE the written colour name rather than a painted cell block, the status
   word marked lightly rather than filled edge to edge, and the whole thing over
   the map rather than away from it.

   **THIS IS DRESS AND NOTHING ELSE.** The document, its sections, its figures
   and its wording are untouched, and the mirror's proofs are what say so: if a
   restyle changed a compared figure, `web_splice_parity_gate` fails.

   Measured from v7 at `acf221a` (`shared/tfm-splice-diagram.js`): 10px table
   text, 3-4px cell padding, a 10px swatch with a 2px radius, sticky 9px
   uppercase headings, and a narrow centre lane for the bezier fan. The sizes
   below are the same figures in rem, on this build's own palette. */
.sv-tabs { display: flex; flex-wrap: wrap; gap: .3rem; align-items: center;
           margin: .3rem 0 .5rem; }
.sv-tabs .tfm-btn { padding: .2rem .55rem; font-size: .78rem; }
.sv-tabs .tfm-btn[aria-selected="true"] { border-color: var(--tfm-accent);
                                          background: var(--tfm-panel-high); }
.sv-count { display: inline-block; margin-left: .3rem; padding: 0 .35rem;
            border-radius: .7rem; background: var(--tfm-panel-high);
            font-size: .68rem; color: var(--tfm-ink-muted); }
/* **EQUAL PANES, FIXED LANE** *(owner, 2026-08-20, amendment 4 ruling 4 -- v7's own
   layout at `webmap-ol-edit.html` 900-926)*. Resizing the panel divides the change
   equally between the two panes and the lane does not grow: v7 gives both sides
   `flex: 1; min-width: 260px` and the centre `width: 80px; flex-shrink: 0`. A grid
   with `1fr 3.5rem 1fr` looked the same at one width and was not the same rule. */
.sv-grid { display: flex; gap: .25rem; align-items: stretch; }
.sv-grid > .sv-side { flex: 1 1 0; min-width: 16.25rem; }
.sv-lane { width: 80px; flex-shrink: 0; position: relative; overflow: hidden; }
.sv-lane svg { position: absolute; inset: 0; width: 100%; height: 100%;
               pointer-events: none; }
/* **THE COLUMNS ARE THE OPERATOR'S** (ruling 3): each heading carries a drag handle, and
   a table wider than its pane scrolls sideways rather than squeezing every column. */
.sv-scroll { overflow: auto; }
.sv-table th { position: relative; }
.sv-grip { position: absolute; top: 0; right: 0; width: .35rem; height: 100%;
           cursor: col-resize; user-select: none; }
/* **THE SIDE NO LONGER SCROLLS; THE BOX INSIDE IT DOES** *(owner, 2026-08-20,
   amendment 3 item 2)*. The chooser used to scroll away with the rows, so an
   operator reading row 90 could not see which cable they were reading. The
   chooser and the sentence sit in the side; only `.sv-scroll` moves. */
.sv-side { border: 1px solid var(--tfm-edge); border-radius: .3rem;
           background: var(--tfm-panel); padding: .25rem; }
.sv-scroll { max-height: 20rem; }
.sv-pick { width: 100%; font-size: .72rem; padding: .2rem; }
.sv-table { border-collapse: collapse; width: 100%; font-size: .68rem;
            font-variant-numeric: tabular-nums; }
.sv-table th { position: sticky; top: 0; z-index: 2; text-align: left;
               padding: .15rem .3rem; font-size: .6rem; letter-spacing: .03em;
               text-transform: uppercase; color: var(--tfm-ink-muted);
               background: var(--tfm-panel-high);
               border-bottom: 1px solid var(--tfm-edge); }
.sv-table td { padding: .12rem .3rem; white-space: nowrap; overflow: hidden;
               text-overflow: ellipsis; max-width: 11rem;
               border-bottom: 1px solid var(--tfm-edge); }
/* **A SWATCH BESIDE THE NAME, NOT A PAINTED CELL.** The name is what survives
   for the eight percent of men with a colour vision deficiency -- that ruling
   stands and is why the word is still written; this only stops the colour
   shouting over it. */
.sv-swatch { display: inline-block; width: .62rem; height: .62rem;
             margin-right: .3rem; border-radius: .12rem; vertical-align: middle;
             border: 1px solid var(--tfm-edge); }
/* **THE MAP LEGEND'S SWATCH IS THE SAME OBJECT AND NOW CARRIES THE SAME BORDER.**
   It lived in `map.html` with no border at all, which did not show while the
   ground was dark. Measured on the 2026-09-01 light ground, three of the ten
   served layers fall under the 3.0 contrast floor against a white panel --
   `field_markups` at 1.95, `splice_closures` at 2.52 and `nids_mpts` at 2.54 --
   and so do six of the twelve strand colours (White, Yellow, Aqua, Rose,
   Orange, Slate). The values are in the token file and only there; naming them
   here would be a second copy that goes stale silently, which is why rule 1
   refuses a hex in a comment as readily as in a rule, and is right to.
   **The border is what carries them, and it is why none of those colours was
   changed:** the twelve are TIA-598-C and belong to the industry, and a layer
   colour is a legend's promise about what is drawn on the map. A second copy of
   this rule in a page is also the thing the one-shell ruling exists to stop. */
.swatch { display: inline-block; width: .75rem; height: .75rem; border-radius: 2px;
          margin: 0 .4em -.05em; border: 1px solid var(--tfm-edge); }
.sv-note { margin: .25rem 0 0; padding: .2rem .4rem; border-radius: .25rem;
           font-size: .72rem; background: var(--tfm-panel-high); }
/* Lightly marked, not filled: a left rule in the state's colour. */
.sv-note.confirm { border-left: .2rem solid var(--tfm-splice-free); }
.sv-note.blocked { border-left: .2rem solid var(--tfm-splice-circuit-spliced); }
.sv-chip { display: inline-block; margin-right: .35rem; font-size: .68rem;
           color: var(--tfm-ink-muted); }
.sv-cards { display: flex; flex-wrap: wrap; gap: .4rem; margin: .4rem 0; }
.sv-card { border: 1px solid var(--tfm-edge); border-radius: .35rem;
           padding: .3rem .55rem; min-width: 6rem; background: var(--tfm-panel); }
.sv-card span { display: block; font-size: .62rem; letter-spacing: .04em;
                text-transform: uppercase; color: var(--tfm-ink-muted); }
.sv-card strong { font-size: 1rem; }
.sv-pair-head td { background: var(--tfm-panel-high); font-weight: 600; }

/* ---- the panel report's footer and its printed sheet ----------------------
   RULING 19: PRINT PUTS PORT CONNECTIVITY ONLY ON PAPER. The other four sections
   are screen and CSV. The sheet is built into a hidden host in THIS document and
   revealed by the rules below -- rather than opened in a second window, because a
   popup is what the owner asked us to stop doing and a blocked one fails silently.

   EVERY COLOUR HERE IS A TOKEN. A hex literal in a print block is a second palette
   the one colour file cannot see. The paper tokens already exist and are used. */
.panel-footer { display: flex; gap: .4rem; flex-wrap: wrap;
                padding: .5rem 0 .2rem; border-top: 1px solid var(--tfm-edge);
                margin-top: .6rem; }
.caption { font-size: .78rem; color: var(--tfm-ink-muted); margin: .2rem 0 .5rem; }
#tfm-print-host { display: none; }

@media print {
  /* **THE PAGE ITSELF IS PAPER, AND FORGETTING TO SAY SO PUT A DARK BAR ON EVERY SHEET.**
     *(owner, 2026-08-22: "a solid black bar runs the full width of the table beneath the
     last row".)* Diagnosed from the owner's PDF by reading its drawing operators rather
     than by guessing at the CSS: of the filled rectangles taller than 2pt, 73 were
     `rgb(0.6,0.6,0.6)` -- the paper edge, correct -- and exactly ONE was
     `rgb(0.043,0.071,0.125)`, which is `--tfm-ground` -- the SCREEN's page token -- painted
     page-wide. (Named by its token rather than written out: `p8_map_gate` check 1 refuses a
     hex literal anywhere outside the colour file and CANNOT tell one in a comment from one
     in a rule. It caught this sentence, correctly, and the standing conclusion for that
     family is to record the limitation rather than loosen the check.) The host was told it was paper; `body` never was, so the dark ground showed
     wherever the host did not cover it.

     **This is the 22-percent-black lesson exactly**: a value tuned for one surface arriving
     on another because only the role was copied and not the question "which surface was
     this for". `reports.html`'s own print block sets `body` and has done all along; this
     one was written without it. */
  html, body { background: var(--tfm-paper) !important; color: var(--tfm-paper-ink) !important; }
  /* THE MAP AND THE PANEL FURNITURE ARE NOT PAPER. Hiding the body's children and
     showing one host is what keeps the sheet to ONE section without this file
     having to know every id the map draws. */
  body > * { display: none !important; }
  #tfm-print-host { display: block !important; position: static;
                    background: var(--tfm-paper); color: var(--tfm-paper-ink); }
  .panel-sheet h1 { font-size: 1.1rem; margin: 0 0 .2rem; }
  .panel-sheet .sheet-sub { font-size: .78rem; margin: 0 0 .6rem; }
  /* v7's SHEET, UNCHANGED (ruling 15): nine columns, two blocks side by side, one
     row per port. The gutter is a real column so the two halves cannot slide out
     of alignment when a Terminal Name is merged down one of them. */
  table.sheet-2up { border-collapse: collapse; width: 100%; font-size: .72rem; }
  table.sheet-2up th, table.sheet-2up td {
      border: 1px solid var(--tfm-paper-edge); padding: .12rem .3rem;
      vertical-align: top; }
  table.sheet-2up th { background: var(--tfm-paper); font-weight: 600; }
  /* A MERGED TERMINAL NAME IS CENTRED DOWN ITS RUN, which is what makes the merge
     readable as "these ports all reach this" rather than as a formatting accident. */
  table.sheet-2up td.terminal { vertical-align: middle; }
  table.sheet-2up th.spacer, table.sheet-2up td.spacer {
      border: none; width: 1.2rem; }
}
