/* web/css/hub_sd.css — the market-hub supply & demand sheet.
   ---------------------------------------------------------------------------
   THE SHAPE OF THE ANSWER, which is what is left here now that the palette is
   gone. The T-1 overview is a roster beside a chart — one ranked list, one
   shape. This page is a BALANCE, and a balance has two halves that must be
   read against each other, so the charts stack vertically on a SHARED,
   IDENTICAL x-axis and a shared y-scale. Two charts of the same hub drawn to
   different y-maxima is the single easiest way to make a hub that exports
   twice what it receives look balanced, and it is invisible unless you read
   the axis labels. `js` pins the two maxima together for that reason; this
   file gives them equal height so the pinning is legible.

   The grid gets the wider track. It is the artefact the reader actually works
   in — the charts show the shape, the grid is where a number gets copied out —
   and it carries one column per pipeline plus five balance columns, so it is
   the half that runs out of room first.

   ---------------------------------------------------------------------------
   THE PRIVATE PALETTE IS GONE (2026-08-07), and the old header comment
   defending it is gone with it. It said the palette was t1_product.css's,
   "deliberately unchanged", so that a reader crossing between this page and
   the T-1 pages "must land somewhere obviously the same product". The goal was
   right and the mechanism was a copy: six files each declared their own
   --bg/--panel/--line/--ink block and stayed identical only for as long as
   nobody edited one of them. tokens.css is the same goal with a mechanism.

   It also could not do the one thing this page now has to do. This document is
   loaded into index.html's #sd-dock as an iframe and fills two thirds of the
   window; a dark-only stylesheet meant that switching the map to light left
   the map light and this sheet black, side by side, in the same screenshot.
   A palette that cannot follow the theme is not a palette, it is a hardcode
   with a variable name.

   WHAT THE ELEMENT SELECTORS BELOW MEAN NOW. `html, body`, `table`, `thead th`
   and `a` are styled bare here because this is a page of its own. That is only
   safe because index.html embeds it in an IFRAME rather than porting the
   markup — see the long note on #sd-dock in index.html. If anyone ever inlines
   this sheet into another document, these five rules are what will land on
   that document's own panels. */

*{box-sizing:border-box}

/* THE FONT-SIZE IS ON `body` AND NOT ON `html`, AND THAT SPLIT IS THE WHOLE
   REASON THIS RULE IS TWO RULES (found 2026-08-08, WP4.4b).

   It used to be one `html,body{...font-size:var(--t-data)}`. --t-data is
   `.8rem`, and a rem on `html` is relative to `html`'s OWN inherited size, so
   setting it there re-based the root from 16px to 12.8px — and then every other
   token on the page, all of which are rem, resolved against 12.8 instead of 16.
   R2.2's five sizes silently became five different sizes, each 20% small:

       token        declared    rendered here
       --t-label    10.88px      8.70px
       --t-data     12.80px     10.24px
       --t-body     14.40px     11.52px

   8.70px is what the whole #grid header row and both chart legends were
   actually being drawn at. Nothing in the repo could see it: the lint reads
   token DECLARATIONS, and the declaration was right — the compounding happens
   at resolve time, on the page.

   `body` is the correct host because a rem there resolves against the root,
   which is now left alone. `html` keeps only --bg, so overscroll past the end
   of the document still shows the page colour rather than the browser's white,
   which is what putting the background on both was for. */
html{margin:0;overflow:hidden;background:var(--bg)}
body{
  margin:0;
  background:var(--bg);
  color:var(--ink);
  font-family:var(--font-data);
  font-size:var(--t-data);
  -webkit-font-smoothing:antialiased;
  overflow:hidden;
}
button,select{font-family:var(--font-data);font-size:var(--t-data)}
/* R3.11 — no hue on an interaction state. This file is where the green link
   started, and every other page copied it from here, so it is fixed here
   rather than propagated. See app.css .link for the full argument: this page
   puts links in the same view as --accent-measured evidence badges, so the
   green was claiming two different things at once. */
a{color:var(--ink);text-decoration:underline;text-underline-offset:2px;
  text-decoration-color:var(--line-strong)}
a:hover{text-decoration-color:var(--ink)}

/* ---------- top bar ---------- */
.top{
  display:flex;align-items:center;gap:var(--sp-3);
  padding:7px var(--sp-3);
  border-bottom:1px solid var(--line);
  background:var(--surface-2);
  height:36px;
}
/* The product mark. --pine and not --warn: R3.3 says amber means warning and
   nothing else, and a permanent amber block in the corner of every screenshot
   is a page that looks like it is complaining about itself. This is the brand,
   so it takes the brand token. --on-pine for the text, NOT --bg: --pine is dark
   in both themes, so its foreground must be light in both, and --bg inverts.
   That read 1.52:1 in dark — the word inside the mark was invisible. */
.top .tag{
  background:var(--pine);color:var(--on-pine);font-weight:700;
  padding:2px 9px;letter-spacing:.06em;border-radius:var(--r-ctl);
}
.top h1{font-size:var(--t-data);font-weight:600;margin:0;letter-spacing:.04em;
  white-space:nowrap}
.top .right{margin-left:auto;display:flex;gap:14px;align-items:center;
  color:var(--ink-faint)}
.top select{background:var(--surface);color:var(--ink);
  border:1px solid var(--line);border-radius:var(--r-ctl);padding:3px 6px}
.top select:hover{border-color:var(--line-strong)}
.top label{color:var(--ink-faint);letter-spacing:var(--label-tracking);
  text-transform:uppercase;font-size:var(--t-label)}

/* ---------- the ⓘ and its popover ---------- */
.info{display:inline-flex;align-items:center;justify-content:center;
  width:14px;height:14px;border-radius:var(--r-pill);
  border:1px solid var(--line-strong);
  color:var(--ink-faint);background:none;cursor:pointer;padding:0;
  font-size:var(--t-label);line-height:1;vertical-align:middle;
  margin-left:6px}
.info:hover{border-color:var(--ink-body);color:var(--ink-body)}
#pop{position:fixed;z-index:3000;max-width:470px;background:var(--surface);
  border:1px solid var(--line-strong);border-radius:var(--r-panel);
  padding:11px 13px;color:var(--ink-body);
  line-height:1.55;box-shadow:var(--shadow-panel);display:none}
#pop.on{display:block}
/* The popover's own title uses the .eyebrow treatment's numbers rather than a
   fourth uppercase style; it cannot take the class itself because the string
   is built in js as an <h4>. --ink-body, not an accent: this is a definition,
   not a claim about the data. */
#pop h4{margin:0 0 6px;font-size:var(--t-label);
  letter-spacing:var(--label-tracking);color:var(--ink-body);
  text-transform:uppercase}
#pop p{margin:0 0 var(--sp-2)}
#pop p:last-child{margin-bottom:0}

/* ---------- layout ---------- */
.sheet{display:grid;grid-template-columns:minmax(0,1fr) minmax(560px,1.25fr);
  height:calc(100vh - 36px - 26px)}
.col{display:flex;flex-direction:column;min-width:0;min-height:0;
  border-right:1px solid var(--line)}
.col:last-child{border-right:none}
.block{display:flex;flex-direction:column;min-height:0;flex:1;
  border-bottom:1px solid var(--line)}
.block:last-child{border-bottom:none}
.bhead{display:flex;align-items:baseline;gap:10px;padding:var(--sp-2) var(--sp-3);
  background:var(--surface-2);border-bottom:1px solid var(--line);flex:0 0 auto}
/* SUPPLY / DEMAND / BALANCE. These were amber, which R3.3 reserves for
   warnings — three permanent amber headings taught a reader to read amber as
   decoration, which is exactly what makes the ONE amber thing on this page
   (the unexplained residual column) stop working. Same numbers as .eyebrow;
   an <h2> written by the markup, so it takes them here. */
.bhead h2{margin:0;font-size:var(--t-label);
  letter-spacing:var(--label-tracking);
  text-transform:uppercase;color:var(--ink-faint)}
.bhead .sub{color:var(--ink-faint);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.bhead .spacer{margin-left:auto}
.chartwrap{flex:1;min-height:0;position:relative;padding:var(--sp-2) 10px}
.chartwrap canvas{position:absolute;inset:8px 10px}
.scroll{overflow:auto;min-height:0;flex:1}
.caption{padding:5px var(--sp-3);color:var(--ink-faint);
  border-top:1px solid var(--line);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:0 0 auto}

/* R7.2. The caption is normally a one-line honesty note and is clipped to stay
   one line — deliberate, it must not steal height from the grid. A FAILURE
   state cannot be clipped: the sentence is "what happened, and what to do",
   and the clip took the second half off ("…and nothing was served. Nothing
   here is s…"), leaving a reader with a complaint and no instruction, which is
   worse than saying nothing. In this state there is no grid to steal height
   from anyway. */
.caption.state{white-space:normal;overflow:visible;text-overflow:clip;
  padding:var(--sp-3)}

/* ---------- the grid ----------
   #grid carries .data-table, so the sticky axes, the mono/tabular cells, the
   uppercase header treatment, .col-key, .col-rule, .cell-nd, .cell-total and
   .is-dead all come from app.css and are the SAME rules the map panel's
   history tables use. What is left here is only what this grid does that no
   other table does: a two-row header with a spanning band above the columns,
   and a slightly tighter type size, because it is the widest table in the
   product and the one most likely to run out of room. */
#grid{font-size:11px}

/* The band row — "Balance", "Supply — into hub", "Demand — out of hub" — sits
   ABOVE the column names and labels a group rather than a column, so it is
   left-aligned and tracked wider to read as a section rule. --ink-body, one
   step up from the column names beneath it, which is the whole hierarchy this
   row needs; it was amber, and see .bhead h2 above for why it is not. */
#grid thead th.side{text-align:left;color:var(--ink-body);letter-spacing:.14em}

/* The residual. THE one amber thing on this page, and now the only one: a
   month whose supply and demand do not close is unexplained, which is exactly
   what R3.1 gives --warn to.

   R3.13 asks whether this fires on a healthy state. Measured 2026-08-08 over
   the API's own answers: Cushing returns a residual in all 120 months and the
   SMALLEST of them is 49.7 MBBL/D, median 67% of that month's supply. There is
   no benign tail here to protect — every number in this column has earned it.

   The em-dash HAS to be excused, though, and it is the majority case: USGC and
   Patoka compute no residual in any of their 120 months, so both rendered a
   full column of amber dashes for a figure that was never calculated. That is
   not an unexplained month, it is an absent one, and absence has no hue. The
   ID selector above outranks app.css's .cell-nd on specificity, so the excuse
   has to be restated at the same weight rather than left to the cascade. */
#grid td.resid{color:var(--warn)}
#grid td.resid.cell-nd{color:var(--ink-faint)}

/* Stock draw/build is not a pipeline and not a residual — it is a different
   KIND of quantity sharing a row with them, so it takes a series slot rather
   than an evidence colour. Slot 6 because slots 1-5 are spoken for by the
   supply/demand columns this table already renders.

   THE SLOT MARKS THE HEADER, NOT THE NUMBERS (R3.4, ratified 2026-08-07). The
   147 stock-draw figures were painted --series-6 outright, which is the "table
   category cell" case the rule names: it spends colour 147 times to say one
   thing the column head can say once, and it puts a series token on TEXT,
   which drags the whole six-hue ramp back to a 4.5:1 floor it cannot meet.
   The header carries the swatch; the figures are --ink like every other
   figure, and the --warn residual beside them keeps its meaning because it is
   now the only coloured number in the balance block. */
#grid thead th.th-stock{--swatch:var(--series-6);--swatch-on:inline-block;color:var(--ink-body)}

/* ---------- legend ---------- */
.legend{display:flex;flex-wrap:wrap;gap:2px var(--sp-3);padding:5px var(--sp-3);
  border-top:1px solid var(--line);flex:0 0 auto;max-height:52px;overflow:auto}
.legend span{display:inline-flex;align-items:center;gap:5px;
  color:var(--ink-body);
  font-size:var(--t-label);white-space:nowrap}
/* --swatch is set per-<i> by drawChart(), as `var(--series-N)` — a pass-through
   (R1.4), never a value. --series-other is the fallback rather than a
   transparent gap: a key rung with no block would read as a series that isn't
   drawn, when it means the opposite. */
.legend i{width:9px;height:9px;display:inline-block;flex:0 0 auto;
  background:var(--swatch,var(--series-other))}

footer{height:26px;display:flex;align-items:center;gap:16px;padding:0 var(--sp-3);
  border-top:1px solid var(--line);background:var(--surface-2);
  color:var(--ink-faint)}
footer .spacer{margin-left:auto}
.warn{color:var(--data-neg)}

/* ---------- docked in the map (?embed=1, see hub_sd.js) ----------
   The footer is cross-PAGE navigation, and inside index.html's #sd-dock those
   links would load a second map into a frame on the first one. It is the only
   thing the embed drops — and dropping it gives the 26px back to the charts,
   which is why .sheet's height has to be restated rather than left to inherit
   a reserve for a footer that is no longer there. */
.embed footer{display:none}
.embed .sheet{height:calc(100vh - 36px)}

/* ---------- phone and small tablet (820px and under, 2026-10-24) ----------
   The sheet is a fixed-height, two-track grid whose grid track will not go
   under 560px, inside a page that does not scroll. On a 375px phone, and in
   the US map's dock, which goes full screen at that width (style.css), the
   grid track ran off the right edge with nothing to scroll it back, and the
   hub and window selects were clipped off the top bar. Under the breakpoint the
   BODY scrolls (html keeps its overflow:hidden, which is what stops a phone
   widening its layout viewport to fit the shared top bar), the bar wraps, and the two columns stack: charts first, with
   the height two stacked charts need, then the grid, which keeps its own
   scroller and sticky header. The shared x-axis and y-scale are untouched;
   they are drawn in js. Same 820px as the map that docks this page. Nothing
   outside this block changed, so desktop is as it was. */
@media (max-width: 820px) {
  body{height:100vh;overflow-x:hidden;overflow-y:auto}
  .top{flex-wrap:wrap;height:auto;row-gap:var(--sp-2)}
  .sheet,
  .embed .sheet{grid-template-columns:minmax(0,1fr);height:auto}
  .col{border-right:none;border-bottom:1px solid var(--line)}
  .col:first-child{height:76vh;min-height:420px}
  .col:last-child{height:80vh;min-height:360px;border-bottom:none}
}
