/* Semantic presentation; no look-name component branches. */
body{font-family:var(--font-body);font-weight:var(--font-body-weight)}
h1,h2,h3,.verdict-word{font-family:var(--font-display)}
input,button,select{font-family:inherit}
.workspace-chrome{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:16px}
.workspace-chrome button,.workspace-tabs button,.workspace-modes button{font:inherit;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;min-height:40px;padding:8px 12px;cursor:pointer}
.workspace-tabs{display:flex;gap:4px;margin:16px 0;border-bottom:1px solid var(--hair-strong)}
.workspace-tabs button{flex:1;border-bottom-left-radius:0;border-bottom-right-radius:0}
.workspace-tabs [aria-selected=true],.workspace-modes [aria-pressed=true]{background:var(--accent);color:var(--accent-ink)}
.workspace-region[hidden]{display:none}.workspace-modes{display:flex;gap:4px}.workspace-field-note{display:none}
body :is(button,input,select,summary,a):focus-visible{outline:2px solid var(--focus-ink);outline-offset:3px;box-shadow:none}
#go{color:var(--accent-ink)}#addr::placeholder,.why,.sub,.cite{color:var(--ink-subtle)}
.workspace-region .panel{padding:16px;margin-bottom:16px;min-width:0}.workspace-region .cols{display:block}

/* `break-word`, never `anywhere`, on the containers a result renders into.
   The two differ in exactly one way that matters here: `anywhere` counts a break between any
   two characters as a break opportunity when the browser computes an element's MIN-CONTENT
   size, and `break-word` does not. So under `anywhere` a grid or table column holding a price
   reports a minimum of one character wide, the layout hands it that, and the price then breaks
   to fit the column it was given. Measured at 400px on the verdict card: the best-feasible
   column resolved to 44px, `$352K` rendered as "$35" over "2K", and the Source button beside
   it as "So"/"ur"/"ce". `break-word` reports the whole token, the column is sized for it, and a
   word is still broken when a line genuinely cannot hold it -- which is what the narrow-width
   wrapping these declarations were added for actually needs.
   The atomic rule below is the floor under that: a currency figure and a button label are one
   token to a reader and never break mid-token, whatever an ancestor later declares. */
.workspace-region :is(button,.tag),.workspace-verdict-card :is(button,.tag),#field-sheet :is(button,.tag),
.hero-best-k,.hero-best-v,.range-k,.range-v,.rc-k,.rc-v,.tri-v,.recap-val,.sl-n,.sl-k,
.provenance-tap{overflow-wrap:normal;word-break:normal}

@media screen and (min-width:1024px){
 /* THE SPLIT (assumptions workspace R3). ONE custom property, --inspector-default, sets the default share of the window the inspector takes: 45 percent, never under the 480px
    the column has always been. The reader's divider writes --inspector-w on <html> (split-divider.mjs); everything is clamped so the inspector is never under 420px and the visual
    never under 520px, whatever the window does. Full report (below) is the one other thing that replaces --inspector, and it leaves --inspector-w alone, so closing it puts the
    reader's own split back exactly. --divider-w is the gutter the divider sits in: 8px at the desktop shell (the divider is there), nothing elsewhere. */
 body{--inspector-default:max(480px,45vw);--divider-w:0px;--inspector:clamp(420px,var(--inspector-w,var(--inspector-default)),calc(100vw - 520px - var(--divider-w)))}
 body[data-topbar=on]{--divider-w:8px}
 html,body{height:100%;overflow:hidden}
 .wrap{position:relative;margin:0;padding:24px;width:var(--inspector);height:100dvh;overflow-y:auto;background:var(--dock-fill);border-right:1px solid var(--dock-stroke)}
 header{margin-bottom:12px}header p{font-size:14px;margin-top:8px}.search{max-width:none}.search-row{gap:6px}#addr{min-width:0}
 #out>.cols{display:block}#v-recap{position:static;top:auto;flex-wrap:wrap}#v-strip{grid-template-columns:repeat(2,minmax(0,1fr))}
 .workspace-hero{position:fixed;inset:0 0 0 calc(var(--inspector) + var(--divider-w));background:var(--canvas);z-index:0}
 .workspace-hero #map{position:absolute;inset:0;height:100%;width:100%;border:0;border-radius:0;margin:0}
 .workspace-modes{position:absolute;top:16px;left:64px;z-index:1000;background:var(--s1);padding:4px;border-radius:8px}
 #massing-empty{position:absolute;inset:40% 20% auto;text-align:center;background:var(--s1);color:var(--ink);padding:24px;border:1px solid var(--hair-strong);border-radius:12px}
 .workspace-region{min-width:0;overflow-wrap:break-word}.workspace-region table{max-width:100%}.workspace-region .d-b{overflow-x:auto}footer{font-size:12px}
 #empty{padding:20px 0}#empty>ol,#empty>details{display:none}#empty h2{font-family:var(--font-display);font-size:28px}
}
@media screen and (max-width:1023px){
 /* Full workspace on narrow screens: same owned nodes, stacked rather than hidden. */
 html,body{height:auto;overflow:auto}
 .wrap{display:flex;flex-direction:column;width:100%;max-width:100%;padding:16px;overflow-wrap:break-word}
 .wrap>*{min-width:0}.workspace-field-note{display:none}
 .workspace-hero{order:-1;position:relative;flex:none;height:380px;margin-bottom:16px;background:var(--canvas);border:1px solid var(--hair-strong);border-radius:8px;overflow:hidden}
 .workspace-hero #map{position:absolute;inset:0;width:100%;height:100%;margin:0;border:0;border-radius:0}
 .workspace-modes{position:absolute;top:8px;left:48px;right:8px;z-index:1000;flex-wrap:wrap;background:var(--s1);padding:4px;border-radius:8px}
 .workspace-modes .workspace-lens-control{padding:0}.workspace-modes select{width:150px}
 #workspace-assembly-summary{position:absolute;top:104px;left:8px;right:8px;z-index:1000;font-size:12px}
 #massing-empty{position:absolute;inset:40% 8% auto;background:var(--s1);padding:16px;text-align:center}
 .workspace-tabs{display:flex;flex-wrap:wrap}.workspace-tabs button{flex:1;min-width:65px;padding:10px 6px}
 .workspace-region{min-width:0;max-width:100%}.workspace-region .d-b{overflow-x:auto}.workspace-region .cols{display:block}
 #v-strip{grid-template-columns:repeat(2,minmax(0,1fr))}#v-recap{position:static;flex-wrap:wrap}
 .workspace-map-status{left:8px;right:8px;bottom:18px;font-size:12px;padding:8px}
 #addr{min-width:0}.search-row{flex-wrap:wrap}.search-row #addr{width:60%}
}
@media print{.workspace-tabs,.workspace-modes,.workspace-chrome,.workspace-field-note{display:none!important}.workspace-region[hidden]{display:block!important}.workspace-hero{position:static!important}#map{position:relative!important;width:170mm!important;max-width:100%!important;height:340px!important;margin-inline:auto;overflow:hidden!important;break-inside:avoid}body{font-family:var(--font-body)}}

@media screen and (min-width:1024px){
 body[data-workspace-result] header p,body[data-workspace-result] #chips{display:none}
 .workspace-chrome .stamp{font-size:11px;margin:0;display:inline}
 .workspace-chrome{gap:6px;margin-bottom:12px}
 .workspace-chrome button{font-size:12px;min-height:32px;padding:4px 8px}
 #v-card{margin-top:16px}.verdict{padding:16px}.verdict h2{font-size:23px;line-height:1.25}
 .workspace-tabs{position:sticky;top:0;background:var(--dock-fill);z-index:1100}
}

/* The Looks declare how saturated the map should be; the tiles obey the declaration rather
   than the declaration describing tiles nobody applied it to. Aerial photography has its own
   token because a quiet vector basemap and a photograph do not want the same treatment. */
#map .leaflet-tile-pane{filter:saturate(var(--map-saturation,1))}
#map[data-basemap="Aerial"] .leaflet-tile-pane{filter:saturate(var(--map-imagery-saturation,1))}

/* A jurisdiction with no sourced starting view gets no map column at all until a lookup
   resolves a point (views/site.mjs sets the attribute). An empty 960 px panel next to a live
   one reads as a broken screen; an absent one reads as a screen that has not been asked yet. */
@media screen and (min-width:1024px){
 body[data-map-column=collapsed] .workspace-hero{display:none}
 /* With no map column the dock is the page, so it drops the split width (--inspector), the divider and
    the 1320 px cap that would leave a strip of canvas down one side; the search returns to
    the ordinary measure rather than stretching across the whole window. */
 body[data-map-column=collapsed] .wrap{width:100%;max-width:none;border-right:0}
 body[data-map-column=collapsed] .search{max-width:var(--measure)}
 body[data-map-column=collapsed] #empty:not(.hidden){display:block}
 body[data-map-column=collapsed] #empty:not(.hidden)>ol,body[data-map-column=collapsed] #empty:not(.hidden)>details{display:revert}
}
@media screen and (max-width:1023px){body[data-map-column=collapsed] .workspace-hero{display:none}}

/* Persistent Site owner and stable appearance slot; metadata stays in the footer. */
.workspace-chrome{display:grid;grid-template-columns:150px 145px;gap:8px;min-height:40px;align-items:stretch;flex-wrap:nowrap}
.workspace-chrome>span,.workspace-chrome>button{min-width:0}.workspace-chrome .appearance-menu-trigger{width:150px;height:40px;white-space:nowrap}
.workspace-chrome>button{height:40px;white-space:nowrap}
.workspace-map-status{position:absolute;bottom:22px;left:16px;right:16px;z-index:1000;background:var(--s1);color:var(--ink);padding:10px 14px;border:1px solid var(--hair-strong);border-radius:8px;font-size:13px;pointer-events:none}
.workspace-zone-gloss{font-size:13px;color:var(--ink-subtle);margin:8px 0}
@media screen and (min-width:1024px){
 .workspace-resource{position:sticky;top:-24px;padding-top:8px;background:var(--dock-fill);z-index:1100}
 .workspace-resource #v-card{margin-top:0}.workspace-resource .workspace-tabs{position:static;margin:8px 0}
 .workspace-resource .verdict{max-height:340px;overflow-y:auto}
 footer .stamp{display:block;font-size:11px;color:var(--ink-subtle)}
}
@media print{.workspace-resource{position:static!important}.workspace-map-status{position:static;break-inside:avoid}.workspace-resource .verdict{max-height:none;overflow:visible}}

.workspace-example-pin{background:var(--accent);color:var(--accent-ink);border:2px solid var(--canvas);border-radius:50%;text-align:center;line-height:26px;font-weight:700;box-shadow:0 0 0 1px var(--ink)}
@media screen and (min-width:1024px){.search-row{display:grid;grid-template-columns:minmax(0,1fr) 110px;gap:6px}.search-row #go{width:110px;min-width:0;padding-inline:8px;white-space:nowrap;font-size:14px}}
@media screen and (min-width:1024px){header .brand{line-height:21px;min-height:21px;margin:0}#empty{display:none}}

/* Owned persistent Site summary; empty mount takes no space or pointer target. */
#workspace-assembly-summary:empty{display:none}
#workspace-assembly-summary .assembly-combined-site{margin:0;background:var(--s1);color:var(--ink);overflow-wrap:anywhere}
@media screen and (min-width:1024px){#workspace-assembly-summary{position:absolute;top:84px;left:64px;right:16px;max-width:440px;z-index:1000}}
@media print{#workspace-assembly-summary{position:static;margin:12px 0}}

/* A source-outage chip must not squeeze the assembly title into word fragments. */
@media screen and (min-width:1024px){#d-assembly>summary{display:grid;grid-template-columns:minmax(0,1fr) auto}#d-assembly>summary>.d-t{grid-column:1;grid-row:1;overflow-wrap:normal}#d-assembly>summary>.d-s{grid-column:1;grid-row:2}#d-assembly>summary>.d-v{grid-column:1;grid-row:3;margin-left:0;padding-left:0;white-space:normal}#d-assembly>summary::after{grid-column:2;grid-row:1 / 4}}

.workspace-lens-control{display:flex;align-items:center;gap:6px;padding-inline:4px;color:var(--ink);font:400 13px/1.3 var(--font-body)}
/* The author display:flex outranks the UA [hidden] rule, so the hidden state is stated here. */
.workspace-lens-control[hidden]{display:none}
.workspace-lens-control select{width:166px;min-height:36px;max-width:100%;border:1px solid var(--hair-strong);border-radius:5px;color:var(--ink);background:var(--s1);font:inherit;padding:6px}
@media print{.workspace-lens-control{display:none!important}}

/* Massing is a document-like view: chrome participates in flow, and the still is reachable.
   Map keeps its existing absolute overlay controls and fixed narrow-screen frame. */
@media screen {
 .workspace-hero:has(#massing-region:not([hidden])){overflow-x:hidden;overflow-y:auto}
 .workspace-hero:has(#massing-region:not([hidden])) .workspace-modes{position:relative;inset:auto;margin:12px;width:fit-content;max-width:calc(100% - 24px);flex-wrap:wrap}
 .workspace-hero:has(#massing-region:not([hidden])) #workspace-assembly-summary{position:relative;inset:auto;margin:12px;max-width:none}
}
@media screen and (max-width:1023px){
 .workspace-hero:has(#massing-region:not([hidden])){height:auto;overflow:visible}
 .workspace-hero:has(#massing-region:not([hidden])) .massing-region-canvas{height:360px}
}

/* A priced narrow result starts with its answer; initial/refused search affordances remain. */
@media screen and (max-width:1023px){
 body[data-workspace-result="priced"] header p,body[data-workspace-result="priced"] #chips{display:none}
}

@media print {
 #massing-region .massing-region-canvas, #massing-region .massing-facts, #massing-region .massing-tail > .sub, #massing-region .massing-region-still > summary { display:none !important; }
 #massing-region .massing-region-still { display:block !important; break-inside:avoid; }
 #massing-region .massing-region-still svg { max-width:100%; height:auto; }
}

.model-dimensions dl { display:grid; grid-template-columns:repeat(auto-fit,minmax(130px,1fr)); gap:12px; }
.model-dimensions dd { margin:4px 0 0; font-variant-numeric:tabular-nums; }
.model-dimensions dt { color:var(--ink-subtle); }

[data-scene-download],[data-scene-json],[data-pre-app]{font:inherit;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;min-height:44px;padding:8px 12px;cursor:pointer;max-width:100%}
[data-scene-download]:disabled,[data-scene-json]:disabled,[data-pre-app]:disabled{cursor:default;opacity:.45}
@media print{[data-scene-download],[data-scene-json],[data-pre-app]{display:none!important}}

.massing-edit-controls{padding:10px 0;min-width:0}
.massing-edit-controls button,.massing-edit-controls input,.massing-edit-controls select{font:inherit;color:var(--ink);background:var(--s1);border:1px solid var(--control-line);border-radius:5px;min-height:44px;max-width:100%;padding:6px 9px}
.massing-edit-controls button:disabled,.massing-edit-controls input:disabled,.massing-edit-controls select:disabled{opacity:.45;cursor:default}
.massing-edit-panel:not([hidden]){display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:8px}
.massing-edit-panel>p{grid-column:1/-1}.massing-edit-panel label{min-width:0}.massing-edit-panel input{display:block;width:100%;box-sizing:border-box}
.massing-edit-overlay{position:absolute;inset:0;pointer-events:none;z-index:3;overflow:hidden}
.massing-edit-handle{position:absolute;pointer-events:auto;touch-action:none;min-height:44px;font:inherit;color:var(--ink);background:var(--s1);border:2px solid var(--control-line);border-radius:5px;padding:4px 7px;max-width:110px}
.massing-edit-ghost{position:absolute;width:100%;height:100%;overflow:visible;pointer-events:none}.massing-edit-ghost polyline{fill:none;stroke:var(--warning-ink,#9b5c00);stroke-width:3;stroke-dasharray:5 4}
.massing-handle-dock{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;padding:6px 0}
.massing-handle-dock[hidden]{display:none}
.massing-handle-dock .massing-edit-handle{position:static;min-width:44px;min-height:44px;max-width:none;width:100%;box-sizing:border-box;cursor:grab}
.massing-handle-dock .massing-edit-handle:active{cursor:grabbing}
@media(max-width:600px){.massing-handle-dock{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media print{.massing-handle-dock{display:none!important}}
@media print{.massing-edit-controls,.massing-edit-overlay{display:none!important}}

/* Principal massing actions stay before the view; advanced fields remain one action away. */
.massing-primary-actions{display:flex;flex-wrap:wrap;gap:8px;padding:8px 0}
.massing-primary-actions button{min-height:44px;padding:8px 14px;border:1px solid var(--control-line);border-radius:var(--radius-control,4px);background:var(--s1);color:var(--ink);font:inherit;cursor:pointer}
.massing-primary-actions button[aria-pressed="true"]{background:var(--mode-selected-fill);color:var(--mode-selected-ink)}
.massing-primary-actions button:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.massing-primary-actions button:disabled{opacity:.45;cursor:default}
.massing-presentation-status:empty{display:none}
@media print{.massing-primary-actions,.massing-presentation-status{display:none!important}}
/* R3: one rehosted chosen hero, with explicitly current-scheme range and source chips. */
.workspace-verdict-card{border:1px solid var(--hair-strong);border-radius:10px;padding:16px;margin:12px 0;background:var(--s1)}
/* THE CARD CARRIES THE REFUSAL FAMILY, because it strips the rehosted section's own border and
   wash three rules below. A settled finding about the land is drawn as a finished, solid, red
   card; a coverage decline is drawn as an ordinary card, because nothing is wrong; a read that
   did not answer is drawn UNFINISHED -- a dashed edge, which is the one signal a reader takes in
   before any words, and the only one of the three that carries a retry. */
.workspace-verdict-card[data-refusal-family=site]{border-color:var(--bad-line);background:linear-gradient(180deg,var(--bad-tint),transparent 60%),var(--s1)}
.workspace-verdict-card[data-refusal-family=coverage]{border-color:var(--hair-strong);background:var(--s1)}
.workspace-verdict-card[data-refusal-family=read]{border:2px dashed var(--warn-line);padding:15px;background:linear-gradient(180deg,var(--warn-tint),transparent 60%),var(--s1)}
/* ONE FIGURE AT 34px PER CARD, AND NOTHING ELSE IN IT ABOVE 20px.
   Measured inside #out on 1831 Pandosy St, 2026-09-20: $352K (a scheme the caption under it
   disowns) at 32px, the verdict sentence that names $143K at 22px, its own $143K at 14px. The
   biggest number was the wrong number. The figure below is the one the sentence names, it is the
   only thing at the top of the scale, and it is the one place --font-data is spent: a monospaced,
   tabular figure reads as an instrument rather than as prose, and the token had no consumer at
   all until this rule. */
.workspace-verdict-card>.ceiling{padding:0;border:0;background:none}
.workspace-verdict-card .ceiling>.k{font-size:var(--f2);font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-subtle)}
/* The provenance chip is one word and must stay one word: at 400px the longer label pushed it
   into the flex row's last column narrow enough to break "DERIVED" across two lines as DERIVE/D. */
.workspace-verdict-card .ceiling>.k>.tag{white-space:nowrap;flex:none}
.workspace-verdict-card .ceiling>.v{font-size:var(--f7);font-weight:600;line-height:1.1;margin:6px 0 4px;letter-spacing:-.5px;font-family:var(--font-data);font-variant-numeric:tabular-nums}
.workspace-verdict-card .ceiling>.v.phrase{font-family:var(--font-body);font-size:var(--f5)}
.workspace-verdict-card .ceiling>.s{font-size:13px;color:var(--ink-subtle)}
.verdict-range{margin-top:16px;padding-bottom:8px;border-bottom:4px solid var(--accent)}.verdict-range-ends{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;font-size:13px}.verdict-range-basis,.verdict-range-caveat,.verdict-honest-line{font-size:12px;line-height:1.5;margin:8px 0;color:var(--ink-subtle)}.verdict-honest-line{color:var(--ink)}
.verdict-chips{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0}.verdict-chips button,.verdict-method-button,.verdict-method-sheet button{font:inherit;font-size:12px;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;min-height:40px;padding:8px 10px;text-align:left;cursor:pointer}.verdict-chips button[data-verdict-chip=servicing]{flex-basis:100%}
.verdict-method-sheet{box-sizing:border-box;width:min(680px,calc(100vw - 24px));max-height:85dvh;overflow:auto;background:var(--s1);color:var(--ink);border:1px solid var(--hair-strong);border-radius:12px;padding:20px}.verdict-method-sheet::backdrop{background:#0008}.verdict-method-sheet h2{margin-top:0}.verdict-method-sheet>p{font-size:14px;line-height:1.5}
@media print{.verdict-chips,.verdict-method-button,.verdict-method-sheet{display:none!important}.workspace-verdict-card{break-inside:avoid}}

/* Keep only a short, source-owned finding sticky; the complete card scrolls normally. */
/* A 32px bar with the figure and the address, not a 115px restatement of the headline. */
.workspace-verdict-summary{box-sizing:border-box;font:500 13px/1.2 var(--font-body);color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;padding:6px 10px;min-height:32px;width:100%;text-align:left;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.workspace-verdict-card>.verdict{margin:0 0 12px;padding:0;border:0;background:none;max-height:none;overflow:visible}
/* On a refusal the headline IS the finding, so it leads. On a priced card the figure leads and
   the sentence that reads it is a subtitle underneath -- same words, one step down the scale. */
.workspace-verdict-card .verdict .word{font-size:var(--f6);font-weight:600;line-height:1.15;letter-spacing:-.3px}
.workspace-verdict-card:has(>.ceiling) .verdict{margin:0 0 12px;order:0}
.workspace-verdict-card:has(>.ceiling) .verdict .word{font-size:var(--f4);font-weight:400;line-height:1.15;letter-spacing:0}
.workspace-verdict-card>.workspace-refusal{padding:0;border:0}.workspace-verdict-card .vbody{font-size:13px}
@media print{.workspace-verdict-summary{display:none!important}}

.verdict-explanations{margin:12px 0}.verdict-explanations>summary{font:600 13px/1.4 var(--font-body);cursor:pointer;padding:8px 0;color:var(--ink)}.verdict-explanations .vbody{padding-top:8px}
@media print{.verdict-explanations>.vbody{display:block!important}}

/* R4: source-owned sentences and one keyboard-accessible expert sheet. */
.provenance-inline{display:block;font-size:12px;line-height:1.5;color:var(--ink-subtle);white-space:normal;overflow-wrap:anywhere;margin-top:4px}.provenance-tap{font:inherit;font-size:12px;min-height:36px;padding:6px 10px;margin-top:4px;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;cursor:pointer}.provenance-sheet{box-sizing:border-box;width:min(680px,calc(100vw - 24px));max-height:85dvh;overflow:auto;background:var(--s1);color:var(--ink);padding:20px;border:1px solid var(--hair-strong);border-radius:12px;overflow-wrap:anywhere}.provenance-sheet::backdrop{background:#0008}.provenance-sheet button{font:inherit;min-height:40px;padding:8px 12px}.provenance-sheet p{font-size:14px;line-height:1.5}.provenance-source-list{padding-left:20px}.provenance-source-list li{margin:16px 0}.provenance-caveat{color:var(--ink-subtle)}
@media print{.provenance-tap,.provenance-sheet{display:none!important}}

.provenance-range-sources{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin:16px 0}.provenance-range-sources>div{min-width:0;padding:8px;border:1px solid var(--hair);border-radius:6px}.provenance-range-sources strong{font-size:12px}

/* The Sources ledger is two columns: a label with its tag, its citing sentence and its Source
   button, and a short value. Auto table layout sized them by max-content, and a value cell is
   `white-space:nowrap` (td.n), so one long text value ("No Urban Centre designation") claimed
   the width and the label column got what was left: measured at 1440px on production, label
   53px, value 280px, and the label column printing "ParcelM/ap BC", "MEAS/URED", "Sour/ce".
   Fixed layout proportions the columns for their content, two thirds to the words and one third
   to the value, and a text value wraps (a money cell with a magnitude bar, td.n[data-v], is not
   in this table; it keeps nowrap everywhere else). */
#d-prov > .d-b table{width:100%;table-layout:fixed}
#d-prov > .d-b td:first-child{width:66%}
#d-prov > .d-b td.n{width:34%}
#d-prov > .d-b td.n:not([data-v]){white-space:normal}
#d-prov > .d-b :is(td,th){overflow-wrap:break-word}
#d-prov > .d-b select{max-width:100%}

/* Source cards keep the existing distribution values legible in the narrow dock. */
.workspace-region .dist-tri .tri-v{font-size:clamp(16px,3vw,24px);white-space:nowrap}
.workspace-region .dist-row{grid-template-columns:minmax(0,1fr) auto}
.workspace-region .dist-row>span:nth-child(3){grid-column:1/-1;text-align:left}
.workspace-region .dist-row>.provenance-inline,.workspace-region .dist-row>.provenance-tap{grid-column:1/-1;justify-self:start}
#v-recap>*{max-width:100%;box-sizing:border-box}
#v-recap .recap-actions{flex-wrap:wrap}#v-recap .recap-btn{white-space:normal}

/* Keep the original quarterly table and headings; narrow docks display each row as a card. */
#v-cashflow{container-type:inline-size}
@container (max-width:760px){
 .cf-table,.cf-table tbody{display:block;width:100%}
 .cf-table tr{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:12px 0;border:1px solid var(--hair);border-radius:6px;padding:8px}
 .cf-table tr:first-child{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);margin:0;padding:0;border:0}
 .cf-table td{min-width:0;white-space:normal;overflow-wrap:anywhere;text-align:left}
 .cf-table td::before{content:attr(data-cf-column);display:block;font-size:11px;color:var(--ink-subtle);margin-bottom:4px}
}

@media print { body.scenario-restore-blocked > :not(#scenario-restore-notice) { display:none !important; } body.scenario-restore-blocked #scenario-restore-notice { display:block !important; } #scenario-restore-notice button { display:none; } }

/* R5 controls inherit the selected Look's existing palette and focus treatment. */
#scenario-controls{min-width:0;padding-block:12px;border-bottom:1px solid var(--hair-strong);margin-bottom:16px}
#scenario-controls h3{font:inherit;font-weight:700;margin:0 0 8px}
#scenario-controls p{font-size:13px;line-height:1.6;overflow-wrap:anywhere}
#scenario-controls form{display:flex;flex-wrap:wrap;gap:8px;align-items:end;margin-block:12px}
#scenario-controls form label{display:flex;flex-direction:column;gap:4px;flex:1 1 180px;min-width:0}
#scenario-controls input{box-sizing:border-box;min-width:0;width:100%;min-height:40px;font:inherit;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;padding:8px}
#scenario-controls button,.scenario-lever-action,#scenario-restore-notice button,#scenario-site-change button{box-sizing:border-box;max-width:100%;min-height:40px;padding:8px 10px;margin:4px 4px 4px 0;font:inherit;font-size:13px;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;white-space:normal;text-align:left;cursor:pointer}
#scenario-controls button:disabled{opacity:.45;cursor:default}
#scenario-controls table{font-size:13px;border-collapse:collapse}
#scenario-controls th,#scenario-controls td{padding:8px;vertical-align:top;border-bottom:1px solid var(--hair-strong)}
[data-scenario-range]{accent-color:var(--accent);min-height:32px}
@media print{#scenario-controls form,#scenario-controls button,.scenario-lever-action,[data-scenario-range]{display:none!important}}

/* The guard names the address it is asking about, so the whole question has to be readable.
   It laid out at the FULL viewport width (its sentence measured 1222px at 1280x800) while the
   fixed .workspace-hero -- positioned, z-index 0 -- painted over everything past the 480px
   dock, so the reader got "...remain on their origin" and never the address. Held to the dock's
   own column it stops running under the map, and the stacking context keeps it above any
   positioned layer that reaches this corner. It stays IN FLOW, like #scenario-restore-notice
   beside it: the page below is pushed down rather than covered, so answering the question is
   the only thing this notice makes harder. */
#scenario-site-change{position:relative;z-index:2000;box-sizing:border-box;max-width:440px;padding:16px;margin:12px;border:1px solid var(--hair-strong);background:var(--s1);color:var(--ink);border-radius:8px}
#scenario-site-change p{margin:0 0 12px;font-size:14px;line-height:1.5;overflow-wrap:anywhere}

#revenue-binder{margin:var(--sp4) 0;padding:var(--sp4);border:1px solid var(--hair-strong);border-radius:8px;background:var(--s1);color:var(--ink);min-width:0}
#revenue-binder label{display:block;margin-top:12px}#revenue-binder input[type=number],#revenue-binder select{display:block;box-sizing:border-box;width:100%;min-height:44px;padding:8px;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:5px;font:inherit}
#revenue-binder input[type=range]{box-sizing:border-box;width:100%;min-height:40px;accent-color:var(--accent)}#revenue-binder button{min-height:44px;padding:8px;margin:4px;border:1px solid var(--hair-strong);border-radius:5px;background:var(--s1);color:var(--ink);font:inherit}
#revenue-binder p{overflow-wrap:anywhere}#revenue-binder .binder-terms{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,250px),1fr));gap:16px}#revenue-binder svg{width:100%;max-height:130px}#revenue-binder .binder-central{font-size:1.2em;font-weight:700}
@media print{#revenue-binder input,#revenue-binder select,#revenue-binder button,#revenue-binder .binder-terms{display:none!important}}

@media print{body.revenue-draft-pending #revenue-binder{display:none!important}}

/* Confirmed assembly needs room below its map controls and above its status on phones. */
@media screen and (max-width:1023px){.workspace-hero.has-assembly-map:has(#map:not([hidden])){height:560px}}
/* The same accepted model and draft preview stay visible during long rent edits. */
#revenue-binder .binder-result{position:sticky;top:8px;z-index:4;background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;padding:8px 12px;margin-bottom:12px;box-shadow:0 2px 6px #0002}
#revenue-binder .binder-result p{margin:4px 0;font-size:1rem;line-height:1.3}
#revenue-binder .binder-accepted{font-weight:600}
@media print{#revenue-binder .binder-result{position:static;box-shadow:none}}

@media screen and (max-width:1023px){
 #revenue-binder[data-in-view=true] .binder-result{position:fixed;top:auto;bottom:8px;left:16px;right:16px;margin:0;z-index:1200}
 #revenue-binder input,#revenue-binder select,#revenue-binder button{scroll-margin-bottom:160px}
}

/* A rejected typed edit is explained at that same field, using the original live status node. */
.massing-edit-panel [data-massing-edit-status]{font-size:13px;line-height:1.45;color:var(--ink);border-left:3px solid var(--warning-ink,#9b5c00);padding:8px;margin:8px 0 0;scroll-margin-block:16px;overflow-wrap:anywhere}

/* R5 reader-only references and full-data comparison, including field mode. */
.scenario-reset-reference{display:block;margin-top:6px;font-size:13px;line-height:1.5;color:var(--ink-subtle)}
.scenario-reset-reference[hidden]{display:none}
.scenario-comparison{overscroll-behavior-x:contain;scrollbar-gutter:stable}
.scenario-comparison .caption-wide{display:none}
.scenario-comparison .scenario-comparison-table{table-layout:fixed;min-width:728px;width:728px;border-collapse:collapse}
.scenario-comparison .scenario-comparison-table :is(th,td){min-width:128px;width:200px;white-space:normal;overflow-wrap:break-word;word-break:normal;vertical-align:top;padding:12px}
.scenario-comparison .scenario-comparison-table th:first-child{position:sticky;left:0;background:var(--s1);z-index:1;width:128px}
.scenario-comparison .scenario-comparison-table .provenance-inline{overflow-wrap:break-word;word-break:normal}
.verdict-figure-basis{margin:12px 0 0;font-size:13px;color:var(--mut, inherit)}

/* R5 AC7: at desktop widths the three saved scenarios are compared side by side inside the Levers column
   (compact cells, wrapped values); below 1024px the stacked layout keeps the readable 728px table that scrolls. */
@media (min-width:1024px){
.scenario-comparison .caption-narrow{display:none}
.scenario-comparison .caption-wide{display:inline}
.scenario-comparison .scenario-comparison-table{min-width:0;width:100%}
.scenario-comparison .scenario-comparison-table :is(th,td){min-width:0;width:auto;padding:6px 4px;font-size:12px;line-height:1.35;overflow-wrap:anywhere}
.scenario-comparison .scenario-comparison-table th:first-child{position:static;width:20%}
}

/* R7 AC2, on the compact and phone shells only: the edit status line reserves two lines so a longer message ("Accepted undo, revision 2.") never moves the output controls below it. The desktop shell reserves nothing (the R4 rule below). */
.massing-edit-controls [data-massing-edit-status]{min-height:calc(2 * 1.4em);}
/* R4: at the desktop shell the acknowledgement line reserves nothing. An empty status line took two lines of the tail all the time (View mode included) for a message that is mostly not there; the strip already says what an edit did (result-strip.mjs), and a rejection is shown at the field it is about. */
body[data-topbar=on] .massing-edit-controls [data-massing-edit-status]{min-height:0}
body[data-topbar=on] .massing-edit-controls [data-massing-edit-status]:empty{display:none}

/* R7 AC1/AC5: the state banner directly under the viewport (warnings and context notes) is the row the COMPACT and PHONE shells keep. At the desktop shell it stays empty (display:none): those warnings are the pinned strip's severity lines and its checks list (caveat-register.mjs, assumptions workspace R4), so nothing in the stage grows, shrinks or reserves room for a warning. Where it does show it never covers the drawing (an overlay hid the mass roof and failed the GPU-pixel bounds test). */
.massing-stage{position:relative}
.massing-stage-banner{display:flex;flex-direction:column;gap:4px;padding:6px 0 0;pointer-events:auto}
.massing-stage-banner:empty{display:none}
.massing-stage-banner p{margin:0;padding:6px 10px;font-size:13px;line-height:1.35;border-radius:4px;background:var(--s1);color:var(--ink);border:1px solid var(--control-line)}
.massing-stage-banner .massing-warning{color:var(--warn-ink);background:var(--warn-tint);border-color:var(--warn-ink)}
@media print{.massing-stage-banner{display:none !important}}
/* The invalid-draft alert at the desktop shell lives at the top of the tail, out of the stage: it is the one message about a drawing gesture that is worded where the reader is looking, and it is sticky so a scrolled tail keeps it in view. It takes room in the tail, which flexes,
   so it can never move the canvas. */
.massing-tail>.massing-stage-invalid{position:sticky;top:0;z-index:2;margin:0 0 4px;padding:6px 10px;font-size:13px;color:var(--warn-ink);background:var(--warn-tint);border:1px solid var(--warn-ink);border-radius:4px}

.massing-region-canvas{position:relative}
.massing-stage-banner .massing-stage-summary{background:var(--s1);font-weight:600}
.massing-stage-banner .massing-stage-caution{color:var(--warn-ink);border-color:var(--warn-ink)}
/* R7 (Edmonton demo shot 13): the same source-chip squeeze broke the Combined-site servicing title into 'Co mb ine d-sit e'; same stacked-summary grid as the assembly drawer. */
@media screen and (min-width:1024px){#d-servicing>summary{display:grid;grid-template-columns:minmax(0,1fr) auto}#d-servicing>summary>.d-t{grid-column:1;grid-row:1;overflow-wrap:normal}#d-servicing>summary>.d-s{grid-column:1;grid-row:2}#d-servicing>summary>.d-v{grid-column:1;grid-row:3;margin-left:0;padding-left:0;white-space:normal}#d-servicing>summary::after{grid-column:2;grid-row:1 / 4}}
@media screen and (min-width:1024px){#d-edmonton-zoning>summary{display:grid;grid-template-columns:minmax(0,1fr) auto}#d-edmonton-zoning>summary>.d-t{grid-column:1;grid-row:1;overflow-wrap:normal}#d-edmonton-zoning>summary>.d-s{grid-column:1;grid-row:2}#d-edmonton-zoning>summary>.d-v{grid-column:1;grid-row:3;margin-left:0;padding-left:0;white-space:normal}#d-edmonton-zoning>summary::after{grid-column:2;grid-row:1 / 4}}
/* R7 AC5: principal scenario controls on the first frame; row height is fixed so showing or hiding it never shifts the drawing (AC2). */
.massing-quick-controls{display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px;padding:2px 0 0;min-height:32px;font-size:13px}
.massing-quick-controls[hidden]{display:none}
.massing-quick-controls button,.massing-quick-controls input{font:inherit;color:var(--ink);background:var(--s1);border:1px solid var(--control-line);border-radius:var(--radius-control,4px);min-width:32px;min-height:32px;padding:0 10px;cursor:pointer}
.massing-quick-controls input{cursor:text}.massing-quick-controls button:disabled,.massing-quick-controls input:disabled{opacity:.45;cursor:default}.massing-quick-controls button:focus-visible,.massing-quick-controls input:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.massing-quick-controls output{display:inline-block;min-width:2ch;text-align:center;font-weight:600}
.massing-quick-controls input{width:7.5em}
.massing-floor-panel{min-width:0;margin:6px 0 12px;padding:12px;border:1px solid var(--control-line);border-radius:6px;background:var(--s1)}
.massing-floor-panel[hidden],.massing-floor-panel [hidden]{display:none!important}
.massing-floor-panel h3{margin:0 0 5px;font-size:15px}
.massing-floor-panel .why{margin:4px 0 8px;font-size:12px;line-height:1.4;color:var(--ink-subtle)}
.massing-floor-range{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;min-width:0}
.massing-floor-range label{display:grid;gap:3px;min-width:0;font-size:12px;line-height:1.25;color:var(--ink)}
.massing-floor-range input,.massing-floor-range select{box-sizing:border-box;width:100%;min-width:0;min-height:36px;padding:5px 7px;font:inherit;color:var(--ink);background:var(--canvas);border:1px solid var(--control-line);border-radius:4px}
.massing-floor-actions{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 0}
.massing-floor-actions button{min-height:36px;padding:5px 9px;font:inherit;font-size:12px;color:var(--ink);background:var(--canvas);border:1px solid var(--control-line);border-radius:4px;cursor:pointer}
.massing-floor-actions button:disabled{opacity:.45;cursor:default}
.massing-floor-actions button:hover:not(:disabled){background:var(--s2);border-color:var(--accent)}
.massing-floor-advanced{margin:6px 0 0}
.massing-floor-advanced summary{cursor:pointer;font-size:12px;font-weight:600;padding:4px 0;color:var(--accent)}
.massing-floor-advanced[open]>.massing-floor-range{margin-top:6px}
@media(max-width:600px){.massing-floor-panel{padding:10px}.massing-floor-range{grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}.massing-floor-range label:last-child:nth-child(odd){grid-column:1 / -1}.massing-floor-actions button{flex:1 1 calc(50% - 6px)}}
@media print{.massing-floor-panel{display:none!important}}
@media print{.massing-quick-controls{display:none !important}}
/* R7: a visible pointer affordance on the massing controls (the hover frame was byte-identical to the loaded frame). */
.massing-primary-actions button:hover:not(:disabled):not([aria-pressed="true"]),.massing-quick-controls button:hover:not(:disabled),.massing-edit-controls button:hover:not(:disabled):not([aria-pressed="true"]),.massing-stage button:hover:not(:disabled):not([aria-pressed="true"]),.massing-edit-handle:hover{background:var(--s2);border-color:var(--accent)}
.massing-quick-controls input:hover:not(:disabled){border-color:var(--accent)}
.massing-viewport button:hover:not(:disabled):not([aria-pressed="true"]){background:var(--s2) !important;border-color:var(--accent) !important} /* the viewport sets its buttons look tokens inline */

/* Gesture instructions belong to the interactive controls, not the printed still. */
.massing-gesture-help{font-size:13px;line-height:1.4;margin:6px 0}
@media print{.massing-gesture-help{display:none !important}}
/* Source links must use our surface ink, including visited links: the document's
   dark color-scheme otherwise supplies pale UA link colors on light sheets. */
.provenance-sheet a[href],.provenance-sheet a[href]:visited{color:var(--ink);text-decoration:underline;text-underline-offset:.15em;text-decoration-thickness:1px}
.provenance-sheet a[href]:focus-visible{outline:2px solid var(--ink);outline-offset:3px;border-radius:2px}

/* Exact edits stay beside the drawing: the same input nodes are moved here. */
.massing-context-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr)) auto;gap:8px;padding:4px 0}
.massing-context-fields[hidden]{display:none}
.massing-context-fields label{font-size:13px;min-width:0;color:var(--ink)}
.massing-context-fields input{display:block;width:100%;box-sizing:border-box;min-height:44px;font:inherit;color:var(--ink);background:var(--s1);border:1px solid var(--control-line);border-radius:5px;padding:6px 9px}
.massing-handle-dock button[aria-pressed="true"]{background:var(--mode-selected-fill);color:var(--mode-selected-ink)}
.massing-stage[data-editing="true"] .massing-region-canvas{height:300px!important;min-height:0!important}
.massing-stage[data-editing="true"] .massing-viewport{min-height:0!important}
@media(max-width:600px){.massing-stage[data-editing="true"] .massing-region-canvas{height:240px!important}}
@media print{.massing-context-fields{display:none!important}}

.massing-context-fields button{align-self:end;min-height:44px;padding:6px 10px;font:inherit;color:var(--ink);background:var(--s1);border:1px solid var(--control-line);border-radius:5px}
.massing-stage>.massing-stage-invalid{margin:4px 0;padding:6px 10px;font-size:13px;color:var(--warn-ink);background:var(--warn-tint);border:1px solid var(--warn-ink);border-radius:4px}

.massing-stage-binding p{margin:4px 0;padding:6px 10px;font-size:13px;line-height:1.35;border-radius:4px;background:var(--s1);color:var(--ink);border:1px solid var(--control-line);font-weight:600}
.massing-stage-binding:empty{display:none}
@media print{.massing-stage-binding{display:none!important}}
/* THE BINDING SENTENCE HAS ONE HEIGHT WHATEVER IT SAYS (assumptions workspace R4). "Storeys 7 · Limited by: ... · Target: ..." runs from about 560px to about 730px of text depending on the limit and the basis, so in a pane narrower than
   that it wraps for some states and not for others, and a change of limit moved the canvas by a line. Where the widest sentence can wrap (a stage under 800px wide, the desktop shell, measured for every Look) the row is two lines tall whether the words
   need one or two; wider than that it is one line for every state and takes one line. */
@media screen and (min-width:1024px){
 body[data-topbar=on] .massing-stage{container:massing-stage/inline-size}
 @container massing-stage (max-width:800px){.massing-stage-binding p{min-height:calc(2 * 1.35em + 14px)}}
}

/* ==== R1 of the assumptions workspace: one top bar, one overflow control, one pinned result strip ====
   Everything here is keyed on body[data-topbar=on], which workspace-topbar.mjs sets only while the
   nodes are actually rehomed (a fine pointer at 1024px or wider). Off, none of it applies and the
   phone and compact layouts are exactly what they were. */
.workspace-topbar,.workspace-strip{display:none}
@media screen and (min-width:1024px){
 body[data-topbar=on]{--topbar-h:52px}
 body[data-topbar=on] .workspace-topbar{display:flex;position:fixed;top:0;left:0;right:0;height:var(--topbar-h);z-index:1500;align-items:center;gap:12px;padding:0 16px;box-sizing:border-box;background:var(--dock-fill);border-bottom:1px solid var(--dock-stroke)}
 /* The column and the visual pane both start under the bar, so nothing is covered by it. */
 body[data-topbar=on] .wrap{margin-top:var(--topbar-h);height:calc(100dvh - var(--topbar-h));padding-top:0}
 body[data-topbar=on] .workspace-hero{inset:var(--topbar-h) 0 0 calc(var(--inspector) + var(--divider-w))}
 /* The visual pane starts under the bar, so the massing canvas gives up exactly the bar's height: everything
    below it (the facts, the quick controls, the dock) stays where it was in the first frame. */
 body[data-topbar=on] .massing-region-canvas{height:clamp(360px,calc(65vh - var(--topbar-h)),720px)}
 body[data-topbar=on] header{margin:12px 0 0}
 body[data-topbar=on][data-workspace-result] header{margin:0}
 /* An empty status line is a live region, not a 40px gap above the strip. */
 body[data-topbar=on] #note:empty{margin:0;min-height:0}
 .topbar-brand{flex:none}
 .topbar-brand .brand{margin:0;font-size:14px;line-height:20px;white-space:nowrap}
 .topbar-search{flex:0 1 560px;min-width:300px}
 body[data-topbar=on] .topbar-search .search{position:relative;max-width:none;margin:0}
 body[data-topbar=on] .topbar-search .search-row{display:grid;grid-template-columns:minmax(0,1fr) 110px;gap:6px}
 .topbar-search #addr,.topbar-search #go{min-height:36px;height:36px;padding-block:0}
 .topbar-search #ac{top:40px}
 .topbar-profile{flex:0 1 auto;min-width:0;max-width:320px}
 .topbar-profile .profile-strip{display:block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
 .topbar-status{margin:0 0 0 auto;font-size:12px;line-height:16px;color:var(--ink-subtle);white-space:nowrap}
 .topbar-more{position:relative;flex:none}
 .topbar-more-button{font:inherit;font-size:13px;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;min-height:36px;padding:0 12px;cursor:pointer}
 .topbar-more-button::after{content:"";display:inline-block;margin-left:8px;width:6px;height:6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:translateY(-2px) rotate(45deg)}
 .topbar-more-button[aria-expanded=true]{background:var(--s2);border-color:var(--accent)}
 .topbar-panel{position:absolute;top:calc(100% + 8px);right:0;width:264px;box-sizing:border-box;padding:8px;display:flex;flex-direction:column;gap:6px;background:var(--s1);border:1px solid var(--hair-strong);border-radius:8px;box-shadow:0 8px 24px #0004}
 .topbar-panel[hidden]{display:none}
 .topbar-panel .workspace-chrome{display:flex;flex-direction:column;align-items:stretch;gap:6px;margin:0;min-height:0}
 .topbar-panel .workspace-chrome>span,.topbar-panel .appearance-menu{display:block;width:100%}
 .topbar-panel .workspace-chrome .appearance-menu-trigger,.topbar-panel .workspace-chrome>button{width:100%;height:36px;min-height:36px;padding:0 12px;text-align:left;font-size:13px;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px}
 .topbar-panel .actions{display:flex;flex-direction:column;align-items:stretch;gap:6px;margin:0}
 .topbar-panel .actions button{width:100%;min-height:36px;padding:0 12px;text-align:left;font:inherit;font-size:13px;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;cursor:pointer}
 .topbar-panel .actions button:disabled{opacity:.45;cursor:default}

 /* THE PINNED STRIP: sticky at the very top of the column, above the tab bar, never scrolled away.
    The figure with its Change assumptions link on the left, the change sentence beside them, then the
    building line and the break-even line across. Every row is reserved even when empty and the sentence
    never outgrows the figure's two rows, so an accepted edit never moves the field under the pointer.
    Full-bleed background so nothing shows through the gutters. */
 body[data-topbar=on] .workspace-strip:not([hidden]){display:grid;grid-template-columns:auto minmax(0,1fr);grid-template-areas:"figure change" "assume change" "severity severity" "building building" "headroom headroom";column-gap:12px;row-gap:0;align-items:center;position:sticky;top:0;z-index:1200;margin:0 -24px;padding:5px 24px 3px;background:var(--dock-fill);border-bottom:1px solid var(--hair)}
}
/* THE STRIP'S OWN PARTS (figure, sentence, lines, chip, list) are the same at every shell that shows it; which shell shows it is decided by the container rules (the desktop shell above, the compact and phone shells in the R5 block below). */
.workspace-strip .strip-figure{grid-area:figure;display:flex;flex-direction:column;align-items:flex-start;gap:0;width:auto;min-height:0;padding:0;margin:0;text-align:left;font:inherit;background:none;border:0;border-radius:4px;color:var(--ink);cursor:pointer;white-space:normal;overflow:visible}
.workspace-strip .strip-k{font-size:10px;line-height:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-subtle)}
.workspace-strip .strip-v{font-family:var(--font-data);font-variant-numeric:tabular-nums;font-size:22px;line-height:24px;font-weight:600;letter-spacing:-.5px;color:var(--ink)}
.workspace-strip .strip-v.neg{color:var(--bad-ink)}
/* F2 (SPEC review 2026-09-2x, finding 2): the saved-read mark sits under the figure, in the
   same column-flex flow as strip-k/strip-v, so it needs its own top margin and no left indent
   (`.tag`'s default is meant for sitting inline after a word, not leading its own line). */
.workspace-strip .strip-cached{margin:2px 0 0}
/* THE CHANGE SENTENCE HAS THE WHOLE RIGHT-HAND COLUMN, TWO ROWS TALL, and three lines of it. It used to be squeezed between the figure and the
   Change assumptions link into 171px and cut off at two lines ("+$953K since the target margin went from 20% to..."), which lost
   the very part that says what it changed TO. The link now sits under the figure, so the sentence gets about 290px and
   the longest lever labels fit in two lines; three are allowed before any clamp, and none of the measured sentences reach it.
   TOP-ALIGNED, one line reserved: the box's top edge never moves, whether the sentence is empty, one line or two (a centred box shifted 7.5px when its
   first words arrived, which test_massing_r7_stability_e2e reads as layout movement). 7px + three lines is exactly the figure's two rows. */
.workspace-strip .strip-change{grid-area:change;align-self:start;margin:7px 0 0;min-height:15px;font-size:12px;line-height:15px;color:var(--ink);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;max-height:45px}
/* The link and the checks chip share the row under the figure. */
.workspace-strip .strip-assume-row{grid-area:assume;justify-self:start;display:flex;align-items:center;gap:8px;min-width:0}
.workspace-strip .strip-assume{display:flex}
/* a page the screen refuses outright has no assumptions to change: the link is not offered */
.workspace-strip[data-strip-refused=on] .strip-assume{display:none}
/* A link-weight button: it navigates, it does not act, and it must leave the change sentence its room. */
.workspace-strip .strip-assume button{position:relative;font:inherit;font-size:12px;line-height:16px;color:var(--accent-hi);background:none;border:0;border-radius:4px;min-height:16px;padding:0 2px;text-decoration:underline;text-underline-offset:.15em;cursor:pointer;white-space:nowrap}
/* The link is 16px tall so the strip stays short; its hit area is not: 4px more on every side makes a 24px target (WCAG 2.5.8). */
.workspace-strip .strip-assume button::after{content:"";position:absolute;inset:-4px}
.workspace-strip .strip-line{margin:0;min-height:15px;font-size:12px;line-height:15px;color:var(--ink-subtle);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.workspace-strip .strip-building{grid-area:building}
.workspace-strip .strip-headroom{grid-area:headroom}
.workspace-strip .strip-headroom::first-letter{text-transform:uppercase}
/* THE SEVERITY LINES (assumptions workspace R4): the critical warnings, one line each, directly under the figure they qualify. Each takes room only while it is there. The container is a
   live region that stays rendered with no height when empty (a region that is display:none cannot announce what arrives in it). The tones are the screen's own status pairs: stop (a refusal)
   in the bad ink, caution in the warn ink, each on its own tint. */
.workspace-strip .strip-severity{grid-area:severity;min-width:0}
.workspace-strip .strip-severity:empty{height:0;margin:0;padding:0;overflow:hidden}
.workspace-strip .strip-severity-line{margin:3px 0 0;padding:2px 8px;font-size:12px;line-height:15px;font-weight:600;color:var(--warn-ink);background:var(--warn-tint);border-left:3px solid var(--warn-ink);border-radius:0 3px 3px 0;overflow-wrap:anywhere}
.workspace-strip .strip-severity-line[data-tone=stop]{color:var(--bad-ink);background:var(--bad-tint);border-left-color:var(--bad-ink)}
/* a status line (register kind status: context buildings loading) is not a warning: plain ink on the neutral tint, regular weight */
.workspace-strip .strip-severity-line[data-tone=note]{font-weight:400;color:var(--ink);background:var(--s2);border-left-color:var(--hair-strong)}
.workspace-strip .strip-severity-line:last-child{margin-bottom:3px}
/* THE CHECKS CHIP AND ITS LIST. The chip counts the warnings that do not get a line; the list opens under the strip over the sheet (it never takes flow room, never takes focus). */
.workspace-strip .strip-checks-chip{position:relative;box-sizing:border-box;font:inherit;font-size:12px;line-height:14px;height:16px;padding:0 8px;color:var(--warn-ink);background:var(--warn-tint);border:1px solid var(--warn-ink);border-radius:9px;cursor:pointer;white-space:nowrap}
.workspace-strip .strip-checks-chip::after{content:"";position:absolute;inset:-4px -2px}
.workspace-strip .strip-checks-chip[aria-expanded=true]{background:var(--s2)}
.workspace-strip .strip-checks-chip[hidden]{display:none}
.workspace-strip .strip-checks-panel{position:absolute;left:0;right:0;top:100%;z-index:2;box-sizing:border-box;max-height:min(60vh,calc(100dvh - var(--topbar-h,52px) - var(--strip-h,0px) - 16px));overflow-y:auto;overscroll-behavior:contain;padding:8px 24px 12px;background:var(--s1);color:var(--ink);border-bottom:1px solid var(--hair-strong);box-shadow:0 10px 24px #0004}
.workspace-strip .strip-checks-panel[hidden]{display:none}
.workspace-strip .strip-checks-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:4px}
.workspace-strip .strip-checks-title{margin:0;font-size:12px;line-height:16px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-subtle)}
.workspace-strip .strip-checks-close{font:inherit;font-size:12px;line-height:16px;min-height:24px;padding:0 10px;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;cursor:pointer}
.workspace-strip .strip-checks-list{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:6px}
.workspace-strip .strip-checks-list[hidden],.workspace-strip .strip-checks-sub[hidden]{display:none}
.workspace-strip .strip-checks-sub{margin:10px 0 4px;font-size:12px;line-height:16px;font-weight:600;color:var(--ink-subtle)}
.workspace-strip .strip-checks-head+.strip-checks-list,.workspace-strip .strip-checks-head+.strip-checks-sub{margin-top:0}
.workspace-strip .strip-checks-item{margin:0;padding:6px 10px;font-size:13px;line-height:18px;color:var(--ink);background:var(--warn-tint);border-left:3px solid var(--warn-ink);border-radius:0 4px 4px 0;overflow-wrap:anywhere}
.workspace-strip .strip-checks-item[data-tone=stop]{background:var(--bad-tint);border-left-color:var(--bad-ink)}
.workspace-strip .strip-checks-item[data-tone=note]{background:var(--s2);border-left-color:var(--hair-strong)}
/* FOCUSING A CONTROL IN THE PINNED STRIP MUST NEVER SCROLL THE SHEET. The column's scroll-padding-top (the pinned bars' height, so a focused row is never left under them) applies to the strip's own controls too, and the strip can never be
   moved out from under it, so the browser scrolled the sheet a long way (600px to 141px, measured) every time a keyboard reader tabbed to Change assumptions, the figure or the checks chip. A margin this negative means these controls never need scrolling into view. */
.workspace-strip,.workspace-strip *{scroll-margin:-1000px}
.workspace-strip .strip-announce{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
@media screen and (min-width:1024px){
 /* The tab bar sticks directly under the strip; --strip-h is measured (workspace.mjs). */
 body[data-topbar=on] .workspace-resource{top:var(--strip-h,0px);margin:0 -24px;padding:0 24px;z-index:1100}
 body[data-topbar=on] .workspace-resource .workspace-tabs{margin:0;padding-top:2px}
 body[data-topbar=on] .workspace-resource .workspace-tabs button{min-height:30px;padding:2px 12px}
 body[data-topbar=on] .workspace-verdict-card{scroll-margin-top:calc(var(--strip-h,0px) + 48px)}
}
@media print{
 body[data-topbar=on] .workspace-topbar{display:block!important;position:static!important;height:auto!important;padding:0!important;border:0!important}
 body[data-topbar=on] .workspace-topbar>:not(.topbar-brand){display:none!important}
 .workspace-strip{display:none!important}
}

/* ==== R2 of the assumptions workspace: one assumption sheet ====
   Everything here is keyed on body[data-sheet=on], which assumption-sheet.mjs sets only while the panels are
   actually regrouped (a fine pointer at 1024px or wider). Off, none of it applies and the compact and phone
   layouts are exactly what they were. Reading order down the column: Building, Revenue, Costs, Finance, then
   Site, the verdict detail (the card, the revenue binder, Numbers) and Sources. Nothing is hidden. */
@media screen and (min-width:1024px){
 body[data-sheet=on] .workspace-region{display:block}
 /* WHAT THE PINNED STRIP AND JUMP BAR COVER IS THE SCROLLER'S OWN PADDING, so anything the browser scrolls to (a Tab, a Shift+Tab, a jump) lands below them.
    It used to be a scroll margin on the elements that had an id, which left every summary and Source chip without one: Shift+Tab could put the focus ring
    under the bar, out of sight (R2 iteration 2, B3). */
 body[data-sheet=on] .wrap{scroll-padding-top:calc(var(--pinned-h,0px) + 8px)}
 body[data-sheet=on] .wrap [id],body[data-sheet=on] .wrap .workspace-verdict-card{scroll-margin-top:0}
 /* The jump bar: eight short buttons on one line, the section names that are new beside the four old tab names. */
 body[data-sheet=on] .workspace-resource .workspace-tabs{flex-wrap:wrap;gap:2px}
 body[data-sheet=on] .workspace-resource .workspace-tabs button{flex:1 1 auto;min-width:0;min-height:26px;padding:1px 3px;font-size:12px;white-space:nowrap}
 body[data-sheet=on] .workspace-resource .workspace-tabs #workspace-tab-Levers{font-weight:600}
 /* The drawer that used to hold the assumptions is now just the sheet frame. */
 body[data-sheet=on] #d-inputs{border:0;background:none;box-shadow:none;margin:0}
 body[data-sheet=on] #d-inputs>summary{display:none}
 body[data-sheet=on] #d-inputs>.d-b{padding:0;font-size:var(--f2);line-height:1.4}
 body[data-sheet=on] #d-inputs>.d-b>.sub{margin:3px 0 0;font-size:12px;line-height:1.25}
 body[data-sheet=on] #d-inputs .toggles,body[data-sheet=on] #d-inputs .inputs{margin:0;display:block}
 .sheet-root{min-width:0}
 .sheet-section{margin:0}
 .sheet-heading{margin:6px 0 0;padding:0 0 2px;font:600 11px/13px var(--font-body);letter-spacing:.07em;text-transform:uppercase;color:var(--ink-subtle);border-bottom:1px solid var(--hair-strong)}
 .sheet-section+.sheet-section>.sheet-heading{margin-top:8px}
 .sheet-more{margin:0}
 .sheet-more>summary{cursor:pointer;padding:5px 0;font:500 12px/1.2 var(--font-body);color:var(--accent-hi);list-style:none}
 .sheet-more>summary::-webkit-details-marker{display:none}
 .sheet-more>summary::before{content:"";display:inline-block;width:5px;height:5px;margin:0 8px 2px 2px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform .1s}
 .sheet-more[open]>summary::before{transform:rotate(45deg)}
 .sheet-more>summary:focus-visible{outline:2px solid var(--focus-ink,var(--accent));outline-offset:2px}
 .sheet-tools-body{padding:4px 0 8px}
 #sheet-root>#reset{display:block;margin:10px 0 2px;min-height:32px;padding:0 12px;font-size:12px}
 .sheet-tools-body>#scenario-controls{margin-bottom:8px}
 .sheet-plate:empty{display:none}
 /* A section the jump bar sent focus to shows where it is when the reader is using the keyboard. */
 body[data-sheet=on] .wrap :is(.sheet-section,.workspace-region,.workspace-verdict-card)[tabindex="-1"]:focus{outline:none}
 body[data-sheet=on] .wrap :is(.sheet-section,.workspace-region,.workspace-verdict-card)[tabindex="-1"]:focus-visible{outline:2px solid var(--focus-ink,var(--accent));outline-offset:-2px}

 /* ONE ROW: name, number, unit, slider and source chip on one line. Anything else a row carries (a note, the original
    figure and its reset, a validation message) sits under the line and takes no room until it has words. */
 .sheet-row{position:relative;display:grid;grid-template-columns:minmax(0,1fr) 76px 46px 96px 54px;column-gap:4px;row-gap:1px;align-items:center;min-height:32px;margin:0;padding:3px 0;border-bottom:1px solid var(--hair)}
 .sheet-row>*{min-width:0}
 /* A market cost line has no slider: its long name (the jurisdiction's own label) takes the slider's column instead of wrapping to three lines in the narrow one. */
 .sheet-row[data-sheet-key^="market_line"]{grid-template-columns:minmax(0,1fr) 76px 46px 0 54px}
 .sheet-row>.row-name{grid-column:1;grid-row:1;font-size:13px;line-height:1.25;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:default}
 /* A market cost line is named by the jurisdiction's own long label ("Development management ...", "Third-party warranty ..."), which no short name stands in for: it wraps to a second line rather than being cut at "Development manag...". */
 .sheet-row[data-sheet-key^="market_line"]>.row-name{white-space:normal;overflow:visible;text-overflow:clip;overflow-wrap:anywhere}
 /* The real label keeps every word it always had for a screen reader and is out of the visual line; the short name above is what the eye reads. */
 .sheet-row .sheet-label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
 .sheet-row .sheet-main{grid-column:2;grid-row:1;width:100%}
 .sheet-row :is(input[type=number],select){box-sizing:border-box;min-height:28px;margin:0;padding:2px 6px;font:inherit;font-size:13px;color:var(--ink);background:var(--canvas);border:1px solid var(--hair);border-radius:var(--r-sm);font-variant-numeric:tabular-nums;transition:border-color .12s ease}
 .sheet-row input[type=number]{text-align:right}
 .sheet-row :is(input[type=number],select):hover{border-color:var(--hair-strong)}
 .sheet-row :is(input[type=number],select):focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-ring)}
 .sheet-row input[type=number]{-moz-appearance:textfield}
 .sheet-row input[type=number]::-webkit-outer-spin-button,.sheet-row input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
 .sheet-row input[aria-invalid="true"]{border-color:var(--bad);background:var(--bad-tint)}
 .sheet-row>.row-unit{grid-column:3;grid-row:1;font-size:12px;color:var(--ink-subtle);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
 .sheet-row>input[type=range]{grid-column:4;grid-row:1;box-sizing:border-box;width:100%;min-height:24px;margin:0;padding:0}
 .sheet-row .provenance-lever-host{display:contents}
 .sheet-row .provenance-tap{grid-column:5;grid-row:1;justify-self:end;min-height:22px;margin:0;padding:0 7px;font-size:11px;line-height:20px;border-radius:11px;white-space:nowrap}
 /* Citation prose leaves the row: the chip opens the Source sheet, which carries the sentence and the sources. */
 .sheet-row .provenance-inline{display:none}
 .sheet-row .printed-value{display:none}
 /* Selects take the room the number, unit and slider would; a checkbox sits where the number would. */
 .sheet-row[data-sheet-layout=select]>.sheet-main,.sheet-row[data-sheet-layout=gradient]>.sheet-main{grid-column:2 / 5;text-align:left;padding-right:22px}
 .sheet-row[data-sheet-layout=gradient]>input[type=number]{grid-column:2;grid-row:2;margin-top:2px}
 .sheet-row[data-sheet-layout=gradient]>input[type=range]{grid-row:2;grid-column:3 / 5;margin-top:2px}
 .sheet-row>.sheet-check{display:contents;font-size:0;color:var(--ink)}
 .sheet-row>.sheet-check>.sheet-main{grid-column:2;grid-row:1;width:auto;justify-self:start;margin:0;accent-color:var(--accent)}
 .sheet-row>.sheet-check>.why{grid-column:1 / -1;font-size:12px;line-height:1.4;margin:2px 0 0}
 /* Everything else a row carries flows under the line, full width, and only takes room when it has something to say. */
 .sheet-row>:not(.row-name,.row-unit,.sheet-label,.sheet-main,.sheet-check,input[type=range],.provenance-tap,.provenance-lever-host,.printed-value,.provenance-inline){grid-column:1 / -1;grid-row:auto}
 /* These qualifications explain how to read the row's own number (an audited-range caveat, a "this is a reference figure" note); they are essential
    text, not decoration, so they carry --ink-subtle (guarded >=4.5:1 on every surface in all six look/theme sets by appearance.test.mjs) rather than
    --ink-faint, which TOKEN_ROLES documents as decoration-only and which read about 2.33:1 in Paper and Blueprint light (Codex P2, 5158609a). */
 .sheet-row .limit,.sheet-row .profile-mark,.sheet-row .scenario-reset-reference,.sheet-row .why,.sheet-row #target-share-basis,.sheet-row .tier-note{display:block;margin:1px 0 0;font-size:12px;line-height:1.4;color:var(--ink-subtle)}
 .sheet-row .limit:empty,.sheet-row .profile-mark:empty,.sheet-row .why:empty,.sheet-row .tier-note:empty{display:none}
 /* WHAT A ROW SAYS BESIDES ITS LINE (assumption-sheet.mjs settleNotes). A long note (Edmonton's revenue disclosure runs to fourteen lines, a source's whole citation, a
    paragraph of help) is folded behind the row's one toggle: closed, none of it is drawn; open, all of it is. Nothing is taken out of the page. */
 .sheet-row :is(.why,.limit).sheet-folded{display:none}
 .sheet-row[data-note-open=true] :is(.why,.limit).sheet-folded{display:block}
 /* A folded note that the levers have since emptied leaves nothing for the toggle to open. */
 .sheet-row[data-note-long=true]:not(:has(.sheet-folded:not(:empty))) .row-note-toggle{display:none}
 /* A market cost line's "starting figure, source" caption is not in the row: the chip's Source sheet carries the same sentence and the source's own entry. */
 .sheet-row .why.sheet-moved{display:none}
 /* A transient message floats just under the row and adds nothing to its height: appearing when the reader arrives and going when they leave never moves the rows
    they are about to click (a double click on the next box used to land on the gap). It does not take the click either. */
 .sheet-row .limit.sheet-transient,.sheet-row:not([data-note-open=true]) .limit.sheet-folded.invalid{position:absolute;left:0;top:calc(100% - 3px);z-index:6;width:max-content;max-width:100%;box-sizing:border-box;margin:0;padding:2px 8px;pointer-events:none;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;box-shadow:0 2px 8px rgb(0 0 0 / .22)}
 .sheet-row .limit.sheet-transient:empty{display:none}
 .sheet-row .limit.sheet-transient.invalid,.sheet-row .limit.sheet-folded.invalid{color:var(--bad)}
 /* A rejection ("Outside 500 to 50,000. Still using 4,500.") is written into the row's note. When that note is a folded one (Edmonton's Sale value) the message is shown, floating like any
    other transient, while the fold is closed: it is the reader's answer, and a hidden note would swallow it. It goes on blur and the folded note comes back (views/levers.mjs). */
 .sheet-row:not([data-note-open=true]) .limit.sheet-folded.invalid{display:block}
 /* A short state caption keeps the line it had once the reader is in the row, however soon the levers clear it. */
 .sheet-row .limit.sheet-caption[data-sheet-held]:empty{display:block}
 .sheet-row .row-note-toggle{justify-self:start;margin:0;padding:0;min-height:20px;font:inherit;font-size:12px;color:var(--accent-hi);background:none;border:0;text-decoration:underline;text-underline-offset:.15em;cursor:pointer}
 .sheet-row .limit.invalid{color:var(--bad)}
 .sheet-row .profile-mark{color:var(--accent-hi)}
 .sheet-row .scenario-reset-reference[hidden],.sheet-row .profile-mark[hidden]{display:none}
 /* The choice "When changing storeys" sits on its own small line under the storeys number. */
 .sheet-row label[for=storeys-mode]{grid-column:1 / 3;grid-row:2;margin:2px 0 0;font-size:12px;color:var(--ink-subtle)}
 .sheet-row #storeys-mode{grid-column:3 / 6;grid-row:2;justify-self:stretch;min-height:26px;margin:2px 0 0;padding:0 6px;font-size:12px}
 /* Reset, Auto and Unset are there when they can do something: the value differs from where it started, or a value has been set. */
 .sheet-row button.scenario-lever-action{display:none;min-height:24px;margin:2px 4px 0 0;padding:0 9px;font-size:12px}
 .sheet-row:has(>.scenario-reset-reference:not([hidden])) button.scenario-lever-action:not([hidden]),
 .sheet-row:has(>.sheet-main[placeholder]:not(:placeholder-shown)) button.scenario-lever-action:not([hidden]){display:inline-flex;align-items:center}
 .sheet-row .why a{color:inherit}
 /* Rows that are only words: the market-line introduction, the read-only settings. */
 .sheet-row[data-sheet-layout=note]{display:block;padding:6px 0}
 .sheet-row[data-sheet-layout=note] .why{font-size:12px}
 .sheet-row[data-sheet-layout=note] h4{margin:6px 0 2px;font-size:12px}
 .scenario-readonly p{margin:4px 0;font-size:12px}
 /* ONE CEILING ON SCREEN. The pinned strip carries the ceiling for the reader's own scheme ("Land price ceiling, your scheme"). The recap bar at the head of the Numbers panel
    printed a second "Land price ceiling" beside it, the best feasible scheme's ("$352K, yours $143K"), so scrolling to Numbers put two ceilings within 130px of each other
    (R1 site-visual review, carried to R2). The best feasible figure is still on the page, labelled, in the verdict card; the recap keeps its address and its three buttons.
    Its value is taken out of the drawn line, not out of the page: a screen reader and the tests still read it, and on paper it prints. */
 body[data-sheet=on] #v-recap .recap-val{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
}
@media print{
 /* On paper a row is words, not controls: the ordinary labels return and the dense grid gives way. */
 body[data-sheet=on] .sheet-row{display:block;padding:2px 0;border:0}
 body[data-sheet=on] .sheet-row .sheet-label{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto;white-space:normal}
 body[data-sheet=on] .sheet-row>.row-name,body[data-sheet=on] .sheet-row>.row-unit,body[data-sheet=on] .sheet-more>summary{display:none}
 body[data-sheet=on] .sheet-row .sheet-main,body[data-sheet=on] .sheet-row input[type=range],body[data-sheet=on] .sheet-row select,body[data-sheet=on] .sheet-row .limit,body[data-sheet=on] .sheet-row button,body[data-sheet=on] .sheet-row .provenance-tap{display:none!important}
 body[data-sheet=on] .sheet-row .printed-value{display:inline!important}
 body[data-sheet=on] .sheet-heading{break-after:avoid}
 /* On paper nothing is folded or moved: the reader has no chip and no toggle to open. */
 body[data-sheet=on] .sheet-row .why:is(.sheet-folded,.sheet-moved){display:block!important}
}

/* R2: Undo, Redo and Reset all in the top bar, only while there is a priced result to edit. */
.topbar-edit{display:none}
@media screen and (min-width:1024px){
 body[data-topbar=on][data-workspace-result=priced] .topbar-edit{display:flex;flex:none;align-items:center;margin:0 4px 0 8px}
 .topbar-edit-group{display:flex;gap:4px}
 .topbar-edit-button{font:inherit;font-size:13px;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;min-height:36px;padding:0 10px;cursor:pointer}
 .topbar-edit-button:hover:not(:disabled){background:var(--s2);border-color:var(--accent)}
 .topbar-edit-button:disabled{opacity:.45;cursor:default}
}
@media print{.topbar-edit{display:none!important}}

/* ==== R3 of the assumptions workspace: the visual never scrolls ====
   The desktop shell is two panes and only one of them scrolls. The inspector (the column) is the document: it scrolls. The visual (the map, or the massing and its controls) is a
   viewport: it is exactly the window under the top bar, it clips instead of scrolling (`overflow:clip` cannot be scrolled even by a script), and the canvas in it never moves while
   the reader edits. What used to sit under the massing canvas and make the pane scroll (the state notes, the figures, the still, the exact-value controls, the downloads) is one
   tail that takes the room the canvas leaves and scrolls inside itself. Everything here is keyed on body[data-topbar=on] (a fine pointer at 1024px or wider); off, the compact and
   phone layouts are exactly what they were. */
/* Off everywhere but the desktop shell: not drawn, and (being display:none) not in the Tab order either. */
#workspace-divider,.topbar-report{display:none}
@media screen and (min-width:1024px){
 /* THE DIVIDER. A separator between the panes with its own gutter, so it covers neither the column (its scrollbar, the pinned strip and jump bar) nor the visual. */
 body[data-topbar=on] #workspace-divider{display:block;position:fixed;top:var(--topbar-h);bottom:0;left:var(--inspector);width:var(--divider-w);z-index:1400;box-sizing:border-box;background:var(--dock-fill);cursor:col-resize;touch-action:none}
 body[data-map-column=collapsed] #workspace-divider{display:none}
 #workspace-divider::after{content:"";position:absolute;left:50%;top:50%;width:2px;height:40px;margin:-20px 0 0 -1px;border-radius:1px;background:var(--hair-strong)}
 #workspace-divider:hover::after,#workspace-divider:focus-visible::after,body[data-split-dragging] #workspace-divider::after{height:64px;margin-top:-32px;background:var(--accent)}
 #workspace-divider:focus-visible{outline:2px solid var(--focus-ink,var(--accent));outline-offset:-2px}
 body[data-topbar=on] #workspace-divider[aria-disabled=true]{cursor:default;opacity:.5}
 /* While it is dragged the map and the massing must not take the pointer, and nothing selects. */
 body[data-split-dragging]{user-select:none;cursor:col-resize}
 body[data-split-dragging] .workspace-hero{pointer-events:none}

 /* The column is as wide as the split says it is: the flow layout's 1320px cap would leave a gap between the column and the visual on a very wide window once the reader (or Full report) makes it wider. */
 .wrap{max-width:none}
 /* THE VISUAL PANE. */
 body[data-topbar=on] .workspace-hero{overflow:clip}
 body[data-topbar=on] .workspace-hero:has(#massing-region:not([hidden])){display:flex;flex-direction:column;overflow:clip}
 /* One row across the top: the Map / Massing switch, the lens, and (in Massing) Present massing and Edit massing beside them (massing-region.mjs puts them there). */
 body[data-topbar=on] .workspace-hero:has(#massing-region:not([hidden])) .workspace-modes{flex:none;margin:8px 12px 4px;align-items:center}
 body[data-topbar=on] .workspace-modes .massing-primary-actions{padding:0 0 0 8px;align-items:center;flex-wrap:nowrap}
 body[data-topbar=on] .workspace-hero:not(:has(#massing-region:not([hidden]))) .workspace-modes .massing-primary-actions{display:none}
 body[data-topbar=on] .workspace-modes .massing-primary-actions button{padding:6px 12px;white-space:nowrap}
 /* The region is NOT a scroll container, at any window size or pane width (R3 iteration 2): `clip` cannot be scrolled by a wheel, a Tab or a script and draws no scrollbar, and the tail below is the
    only scroller in the pane. Everything in it is laid out as flow (two nested flex columns), so whatever sits above and below the canvas gives its own height back: a Map / Massing row that wraps
    to two lines, a binding sentence that wraps, a warning banner, a quick-controls row that wraps. There is no fixed budget for them any more. */
 body[data-topbar=on] .workspace-hero:has(#massing-region:not([hidden])) #massing-region{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:clip}
 /* The stage is the member that gives way (min-height:0 lets it shrink to what the tail leaves it) and inside it the canvas is the only member that shrinks: every other child of the stage is flex:none. */
 body[data-topbar=on] #massing-region>.massing-stage{flex:0 1 auto;min-height:0;display:flex;flex-direction:column}
 body[data-topbar=on] #massing-region>.massing-stage>*{flex:none}
 /* The canvas keeps the height it has always had whenever the room exists (R1: 65vh less the bar, and 300px in Edit so the dock fits under it, so the spec-size rects do not move) and absorbs the
    deficit when it does not, down to a 120px floor (a window under about 520px tall in View, 580px in Edit is the first that cannot hold the stack even then). The viewport inside it follows the
    host: its own inline 360px minimum would poke out of a shorter host. */
 body[data-topbar=on] #massing-region>.massing-stage>.massing-region-canvas{flex:0 1 auto;min-height:120px;height:clamp(300px,calc(65vh - var(--topbar-h)),720px)}
 body[data-topbar=on] .massing-stage[data-editing="true"] .massing-region-canvas{height:300px!important;min-height:120px!important}
 body[data-topbar=on] #massing-region .massing-viewport{min-height:0!important}
 /* With no WebGL the still is drawn in the host and the host is height:auto (massing-region.mjs), as tall as the drawing (713px at a 680px pane). It shrinks like the canvas does and the drawing
    scales down to fit it, instead of running over the banner, the quick controls and the tail. */
 body[data-topbar=on] .massing-region-canvas:has(>.massing-startup-fallback){overflow:hidden}
 body[data-topbar=on] .massing-startup-fallback{height:100%}
 body[data-topbar=on] .massing-startup-fallback>svg{height:100%!important}
 /* The tail never collapses to nothing: with no WebGL the still is drawn in the stage and is as tall as it is. It takes what the stage leaves (never under 64px) and scrolls inside itself. */
 body[data-topbar=on] #massing-region>.massing-tail{flex:1 1 0;min-height:64px;overflow-y:auto;overflow-x:hidden}
 .massing-navigation svg{display:block}

 /* THE SHEET AT A NARROW INSPECTOR (R3 iteration 2). A row is name | number | unit | slider | source, and the last four keep their size, so the name column is what a narrow inspector spends:
    at the divider's 420px clamp it was 68px wide and two Hard cost rows both read "Hard cos...". The sheet is a size container (desktop shell only: the phone and tablet layouts are untouched);
    below 448px of sheet (an inspector under about 520px) the slider drops to a line of its own, so the name gets its 96px back and the number, unit and source stay where they were, a select
    takes its own line the same way, and a name that is still too long wraps instead of being cut. From 448px up, which is an inspector of about 520px and wider, nothing here applies. */
 body[data-topbar=on] .sheet-root{container:sheet / inline-size}
 body[data-topbar=on] .sheet-root>.sheet-section:first-child>.sheet-heading{margin-top:0}
 @container sheet (max-width:448px){
  .sheet-row{grid-template-columns:minmax(0,1fr) 76px 46px 0 54px}
  .sheet-row>.row-name{white-space:normal;overflow:visible;text-overflow:clip;overflow-wrap:anywhere}
  .sheet-row>input[type=range]{grid-column:1 / -1;grid-row:2}
  .sheet-row[data-sheet-layout=select]>.row-name,.sheet-row[data-sheet-layout=gradient]>.row-name{grid-column:1 / 5}
  .sheet-row[data-sheet-layout=select]>.sheet-main,.sheet-row[data-sheet-layout=gradient]>.sheet-main{grid-column:1 / -1;grid-row:2;padding-right:0}
  .sheet-row[data-sheet-layout=gradient]>input[type=range]{grid-column:1;grid-row:3;margin-top:2px}
  .sheet-row[data-sheet-layout=gradient]>input[type=number]{grid-column:2;grid-row:3;margin-top:2px}
  .sheet-row label[for=storeys-mode],.sheet-row #storeys-mode{grid-row:3}
 }

 /* THE NUMBERS STRIP'S TOOLTIPS stay inside the column. The strip is two columns at the desktop shell, and the base rule opens every cell's tooltip rightwards and 320px wide, so a right-hand
    cell's ran 108px past the column and made the inspector scroll sideways at every width (R2 carry-forward). The right-hand cell opens leftwards; the left-hand one rightwards. */
 #v-strip .cell[data-help]::after{left:0;right:auto}
 #v-strip .cell:nth-child(2n)[data-help]::after{left:auto;right:0}

 /* FULL REPORT. One button in the top bar, only while there is a result to read. */
 body[data-topbar=on][data-workspace-result] .topbar-report{display:flex;flex:none;align-items:center;margin:0 4px 0 0}
 .topbar-report-button{font:inherit;font-size:13px;color:var(--ink);background:var(--s1);border:1px solid var(--hair-strong);border-radius:6px;min-height:36px;padding:0 12px;cursor:pointer;white-space:nowrap}
 .topbar-report-button:hover{background:var(--s2);border-color:var(--accent)}
 .topbar-report-button[aria-pressed=true]{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
 body[data-topbar=on][data-full-report=on]{--inspector:min(max(60vw,720px),calc(100vw - 520px - var(--divider-w)))}
}
@media print{#workspace-divider,.topbar-report{display:none!important}}

/* ==== R5 of the assumptions workspace: the compact result strip (the compact and phone shells) ====
   Below the desktop shell there is no top bar and the page itself scrolls, but the reader edits in the same place and needs the same answer in view. The strip is the
   same node as the desktop one (result-strip-ui.mjs), pinned to the top of the page and, on the phone, of the open field card (views/mobile.mjs moves it there): one row
   for the figure and the checks chip, one for what the last edit did, then one line under them for each critical warning. It is about 66px at 400px wide with nothing to
   say and takes room for a warning only while the warning is there. Keyed on body[data-shell], which views/mobile.mjs sets from the pointer and the width (the desktop
   shell's rules above are keyed on body[data-topbar=on] and never apply here). */
body[data-shell=mobile],body[data-shell=desktop-compact]{--topbar-h:0px}
/* A box that scrolls is where a sticky child sticks. html and body both carried overflow:auto below 1024px, which made body a scroll container that never scrolls (its height is its
   content), so nothing sticky was ever pinned: not this strip, and not the field card's Back button either (measured: Back at y -1788 with the page scrolled 1800). html alone scrolls the page. */
body[data-shell=desktop-compact]{overflow:visible}
body[data-shell=mobile] .workspace-strip:not([hidden]):not([data-strip-refused=on]),body[data-shell=desktop-compact] .workspace-strip:not([hidden]):not([data-strip-refused=on]){display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"figure chip" "change change" "severity severity";column-gap:8px;row-gap:0;align-items:center;position:sticky;top:0;z-index:1200;order:-3;box-sizing:border-box;background:var(--dock-fill);border-bottom:1px solid var(--hair-strong)}
/* full bleed: .wrap is at most 720px wide and centred (mobile.css), so on a phone on its side or a small tablet the strip fills the window and its words stay on the column's own edges */
body[data-shell=mobile] .workspace-strip:not([hidden]):not([data-strip-refused=on]){margin:-12px calc(50% - 50vw) 0;padding:4px calc(50vw - 50%)}
body[data-shell=desktop-compact] .workspace-strip:not([hidden]):not([data-strip-refused=on]){grid-template-columns:auto minmax(0,1fr) auto;grid-template-areas:"figure change chip" "severity severity severity";column-gap:16px;margin:-16px -16px 0;padding:4px 16px}
/* the phone's open card is a page of its own (.wrap is out of the way): the strip is its first child and fills the card's own 12px gutters */
body[data-shell=mobile] #field-sheet>.workspace-strip:not([hidden]):not([data-strip-refused=on]){margin:-12px -12px 0}
/* what the strip restates elsewhere on the page (the building it priced and the break-even line) does not fit a phone's pinned bar: the verdict card has both */
body[data-shell=mobile] .workspace-strip .strip-building,body[data-shell=mobile] .workspace-strip .strip-headroom,body[data-shell=desktop-compact] .workspace-strip .strip-building,body[data-shell=desktop-compact] .workspace-strip .strip-headroom{display:none}
/* the Change assumptions link is the page's own button here (the top bar that adopts it is a desktop part); the row carries only the checks chip */
body[data-shell=mobile] .workspace-strip .strip-assume,body[data-shell=desktop-compact] .workspace-strip .strip-assume{display:none}
body[data-shell=mobile] .workspace-strip .strip-assume-row,body[data-shell=desktop-compact] .workspace-strip .strip-assume-row{grid-area:chip;justify-self:end}
/* 12px, not the desktop's 10: the phone's readable-text floor (R7 AC8) */
body[data-shell=mobile] .workspace-strip .strip-k{font-size:12px;line-height:14px}
body[data-shell=mobile] .workspace-strip .strip-v{font-size:24px;line-height:26px}
body[data-shell=mobile] .workspace-strip .strip-change,body[data-shell=desktop-compact] .workspace-strip .strip-change{align-self:start;margin:0;min-height:15px;-webkit-line-clamp:2;max-height:32px;font-size:13px;line-height:16px}
body[data-shell=mobile] .workspace-strip .strip-change{grid-area:change}
body[data-shell=desktop-compact] .workspace-strip .strip-change{grid-area:change;align-self:center}
body[data-shell=mobile] .workspace-strip .strip-severity-line,body[data-shell=desktop-compact] .workspace-strip .strip-severity-line{margin:4px 0 0;padding:3px 8px;font-size:13px;line-height:17px;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
body[data-shell=mobile] .workspace-strip .strip-severity-line:last-child,body[data-shell=desktop-compact] .workspace-strip .strip-severity-line:last-child{margin-bottom:2px}
/* THE CHECKS CHIP AT A TOUCH SIZE. mobile.css makes every button on the phone a 44px box; the chip stays a small pill inside that box, so what the eye reads is a chip and what the thumb finds is a target. No overlapping hit area on top of it (the desktop chip's ::after is for a 16px pill). */
body[data-shell=mobile] .workspace-strip .strip-checks-chip{position:relative;isolation:isolate;height:auto;padding:0 14px;font-size:14px;line-height:20px;background:none;border:0;border-radius:0}
body[data-shell=mobile] .workspace-strip .strip-checks-chip::before{content:"";position:absolute;inset:8px 0;z-index:-1;background:var(--warn-tint);border:1px solid var(--warn-ink);border-radius:14px}
body[data-shell=mobile] .workspace-strip .strip-checks-chip[aria-expanded=true]::before{background:var(--s2)}
body[data-shell=mobile] .workspace-strip .strip-checks-chip::after{display:none}
body[data-shell=mobile] .workspace-strip .strip-checks-chip[hidden]{display:none}
/* the list opens over the page under the strip, at the strip's own width; it never takes flow room */
body[data-shell=mobile] .workspace-strip .strip-checks-panel,body[data-shell=desktop-compact] .workspace-strip .strip-checks-panel{padding:8px 12px 12px}
body[data-shell=mobile] .workspace-strip .strip-checks-item{font-size:14px;line-height:20px}
/* the pinned strip: a focused row is never left under it */
html[data-shell=mobile],html[data-shell=desktop-compact]{scroll-padding-top:calc(var(--strip-h,0px) + 8px)}
/* A PHONE ON ITS SIDE (about 375px of height): the strip is one row, figure then sentence then chip, so it costs about 50px of the screen rather than 68. */
@media screen and (max-height:520px){
 body[data-shell=mobile] .workspace-strip:not([hidden]):not([data-strip-refused=on]){grid-template-columns:auto minmax(0,1fr) auto;grid-template-areas:"figure change chip" "severity severity severity";column-gap:12px;padding-block:2px}
 body[data-shell=mobile] .workspace-strip .strip-change{align-self:center}
 body[data-shell=mobile] .workspace-strip .strip-v{font-size:22px;line-height:24px}
}
/* THE EDIT ACKNOWLEDGEMENT LINE RESERVES NOTHING HERE EITHER. R7 held two lines open under it on the compact and phone shells (39.2px with no message) so that "Accepted undo, revision 2." would never move the controls below;
   R4 took that reserve off at the desktop shell because the strip already says what an edit did. These shells have the strip now (above), pinned above the card, so the same reasoning applies. A rejection is written beside the field
   it is about (massing-edit-controls.mjs moves the line there), not here. The massing facts grid keeps its own grow-only height (massing-region.mjs reserveFactsHeight): it always has figures in it, and that keeps it from shrinking between edits, which is not room held for a warning. */
body[data-shell=mobile] .massing-edit-controls [data-massing-edit-status],body[data-shell=desktop-compact] .massing-edit-controls [data-massing-edit-status]{min-height:0}
body[data-shell=mobile] .massing-edit-controls [data-massing-edit-status]:empty,body[data-shell=desktop-compact] .massing-edit-controls [data-massing-edit-status]:empty{display:none}
@media print{body[data-shell] .workspace-strip{display:none!important}}

/* Custom-floor storey semantics persist beside the input, never over the next row. */
#storeys-mode-why[data-floor-stack="true"]{position:static!important;display:block!important;width:auto;max-width:100%;margin:3px 0;padding:0;border:0;border-radius:0;box-shadow:none;background:transparent;white-space:normal;pointer-events:auto}
