.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    z-index: 100;
    display: none;
    align-items: center;
    justify-content: center;
}
.modal-overlay.active { display: flex; }
/* The load-combinations editor grows to fit its full factor grid (one column per
   load case), and only scrolls when the grid genuinely cannot fit the viewport. */
.modal.combo-modal {
    width: max-content;
    min-width: min(720px, 96vw);
    max-width: 96vw;
    max-height: 92vh;
}
.modal {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 20px;
    max-width: 500px;
    width: 90%;
    max-height: 80vh;
    overflow-y: auto;
}
.modal h3 { color: var(--accent); margin-bottom: 12px; font-size: 16px; }
.modal .form-group { margin-bottom: 10px; }
.modal .form-group label {
    display: block;
    font-size: 11px;
    color: var(--text2);
    margin-bottom: 3px;
}
.modal .form-group input,
.modal .form-group select {
    width: 100%;
    padding: 6px 8px;
    background: var(--panel2);
    border: 1px solid var(--border);
    color: var(--text);
    border-radius: 4px;
    font-size: 12px;
}
/* The rule above is a TEXT-INPUT rule, and a checkbox or radio is an `input`,
   so either control inside a .form-group inherits width:100%; padding:6px 8px
   and renders stretched across the dialog, shoving its label to the far side.
   Companion rule. It wins on SPECIFICITY alone -- the attribute selector makes
   it (0,3,1) against the text rule's (0,2,1) -- so source order is irrelevant
   and it can be placed here, beside the rule it corrects. main.css restates
   these same rules verbatim and loads FIRST, so the fix is duplicated there. */
.modal .form-group input[type=checkbox],
.modal .form-group input[type=radio] {
    width: auto;
    padding: 0;
    margin: 0;
    flex: 0 0 auto;
    background: transparent;
    border: 0;
}
.modal .form-group input:focus,
.modal .form-group select:focus {
    outline: none;
    border-color: var(--accent);
}
/* Compact numeric form fields.
   A modal form field is 100% wide by default (the .form-group rule above), which
   is right for a full-width text box and wrong for a lone number sitting beside
   a unit. These two classes are the sanctioned way to size one.
   🔴 They exist so a numeric field does NOT need an inline `style="width:120px"`.
   A50 asserts the set of inline pixel widths on numeric inputs is exactly the
   three historical modal fields; a fourth inline width in a table renderer is
   what that assertion is for, and the fix for a genuinely new field is a class
   here, not a new exception in the test. */
.modal .form-group input.num-md { width: 120px; }
.modal .form-group input.num-sm { width: 88px; }
/* The DERIVED R readout (Wind Study). It is readonly — R comes from
   AS/NZS 1170.0 Table 3.3 for the chosen DWL and importance level, so it is a
   lookup and not a free number.
   🔴 It is WIDER than num-md on purpose, and it still gets a class rather than
   an inline width. A50 asserts the set of inline pixel widths on numeric inputs
   is exactly the three historical fields; a fourth inline width is a DECISION
   this assertion exists to force into the open, and the resolution is a class
   here — not a new exception in the test. It reads bold because it is the one
   derived quantity the engineer most needs to see at a glance. */
.modal .form-group input.num-derived {
    width: 96px;
    font-weight: 600;
    color: var(--accent2);
}
/* A number inside a data-table cell (the Wind Study member table's A_z column).
   🔴 It gets NO pixel width, not even here: A50 asserts that every value cell in
   a data-table goes through --cell-w, and a hardcoded width on the input would be
   the same defect as one on the cell, one level in. The only thing this rule does
   is stop the modal's 100%-wide form default from stretching the input across the
   whole column. */
.modal .data-table input.num-cell {
    width: var(--cell-w);
    padding: 3px 6px;
}
.modal .btn-row {
    display: flex;
    gap: 8px;
    margin-top: 14px;
    justify-content: flex-end;
}
/* The STAAD output viewer. This is the ONE definition of .report-text - the
   copy that used to live in main.css was loaded FIRST and modal.css is loaded
   LAST (index.html), so modal.css always won and the main.css copy was dead
   code waiting to be edited by someone who expected it to matter. Removed.

   `white-space: pre` and NOT `pre-wrap`, and that is the whole point. STAAD's
   output is a fixed-width report whose readability depends on column 40
   meaning the same thing on every line: the banner box, the numbered input
   echo, the EQUILIBRIUM summary and every code-check block. `pre-wrap` reflowed
   those at the panel edge and the tables came out as ragged fragments, so the
   text scrolled sideways instead - which is what a fixed-format file wants. */
.report-text {
    font-family: var(--mono);
    font-size: 11.5px;
    white-space: pre;
    background: var(--panel2);
    padding: 12px 14px;
    border-radius: 6px;
    overflow: auto;
    line-height: 1.45;
    color: var(--text);
    border: 1px solid var(--border);
    /* The pane takes what the dialog has left, so widening the dialog widens
       the text area rather than only the whitespace around it. */
    flex: 1 1 auto;
    min-height: 0;
    tab-size: 4;
}

/* The output viewer needs room for a 79-column report. .modal defaults to
   500px, which showed roughly 45 characters - the banner was cut in half and
   every table wrapped. This variant is applied by showReport() and removed by
   closeModal() alongside .combo-modal, so no other dialog inherits it. */
.modal.output-modal {
    width: min(1400px, 96vw);
    max-width: min(1400px, 96vw);
    max-height: 92vh;
    display: flex;
    flex-direction: column;
}

/* ---- The file tab strip ------------------------------------------------
   One tab per output file. The strip scrolls horizontally rather than
   wrapping, so adding a file never changes the height of the text pane and a
   tab that is scrolled out of view does not reflow the ones beside it. */
.out-tabbar {
    display: flex;
    gap: 2px;
    overflow-x: auto;
    flex: 0 0 auto;
    border-bottom: 1px solid var(--border);
    margin-bottom: 8px;
    scrollbar-width: thin;
}
.out-tab {
    font-family: var(--mono);
    font-size: 11px;
    padding: 5px 10px;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: 5px 5px 0 0;
    background: transparent;
    color: var(--text2);
    cursor: pointer;
    white-space: nowrap;
    flex: 0 0 auto;
}
.out-tab:hover { color: var(--text); background: var(--panel2); }
.out-tab.active {
    color: var(--accent);
    background: var(--panel2);
    border-color: var(--border);
    font-weight: 600;
}
/* A visible focus ring on the strip: these are real buttons and are reachable
   by keyboard, so the tab order must be visible when it moves. */
.out-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* What the selected file holds. A file nobody can interpret is worse than one
   nobody can find, so the description is always on screen under the strip. */
.out-desc {
    font-size: 11px;
    color: var(--text2);
    margin-bottom: 8px;
    flex: 0 0 auto;
    line-height: 1.4;
}
/* ============================================================================
   AS/NZS 1170.2 Wind Study — the dialog's own layout vocabulary
   ============================================================================
   🔴 WHY THIS BLOCK EXISTS. The Wind Study panel was assembled from inline
   styles, one declaration at a time, and it read as a wall of unrelated
   fragments: a coefficient table with four ragged columns, a second table of a
   different shape immediately below it, and two different font treatments for
   the same class of number. Every value was correct and the presentation was
   not, which is the worst combination - it trains the eye to distrust the
   panel.

   So: one class per ROLE, not per element. A number is `.ws-num`. A clause
   citation is `.ws-note`. A caveat is `.ws-flag`. A table is `.ws-table`. The
   markup says which role a thing plays and this file says what that role looks
   like, so a fifth coefficient column cannot arrive with a sixth font size. */
.ws-panel {
    border: 1px solid var(--line, var(--border));
    border-radius: 5px;
    padding: 9px 11px;
    margin-bottom: 10px;
    background: var(--panel2);
}
.ws-panel-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    font-size: 12px;
    font-weight: 600;
    margin-bottom: 6px;
}
/* The provenance line inside a heading. Deliberately the same size as the
   footnote text — a source is not a second headline. */
.ws-panel-sub {
    font-weight: normal;
    font-size: 10px;
    color: var(--text2);
}
.ws-sub {
    font-size: 11px;
    font-weight: 600;
    margin: 11px 0 4px;
}
.ws-sub2 {
    font-size: 10px;
    color: var(--text2);
    line-height: 1.3;
}
.ws-foot {
    font-size: 10px;
    color: var(--text2);
    line-height: 1.45;
    margin-top: 5px;
}
/* ONE table for every table in this dialog. The columns are sized by role so
   two tables of different shape still line up down the page: a symbol column,
   then numeric columns, then prose. */
.ws-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 11px;
    table-layout: auto;
}
.ws-table th {
    text-align: left;
    font-weight: 600;
    color: var(--text2);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 3px 6px;
    border-bottom: 1px solid var(--line, var(--border));
    white-space: nowrap;
}
.ws-table td {
    padding: 3px 6px;
    border-bottom: 1px solid rgba(42, 42, 90, 0.45);
    vertical-align: top;
    line-height: 1.35;
}
/* 🔴 EVERY CELL IN A `.ws-left` TABLE IS LEFT-ALIGNED, INCLUDING THE NUMBERS.
   Owner ruling 2026-10-13: "justify all tables within the AS1170 dialog box to
   the left, table headers and content."

   This OVERRIDES `.ws-num`'s right alignment for that table, and it is worth
   stating why the override is safe rather than assumed: right-aligned numbers
   were justified by "decimal points form a column", which is true of a column
   of magnitudes of the SAME physical quantity — V_R, M_c, p. These tables mix
   units in one column (m/s beside a dimensionless multiplier beside a Pa), so
   the decimal points were never going to line up, and right-aligning them only
   produced a ragged left edge that made a row's SYMBOL — the one thing read
   first — sit in a column of shifting indents.

   The selector is scoped to `.ws-left` rather than changing `.ws-num`
   globally, because `.ws-num` is used by the loads table and other panels
   where a genuine single-unit column still benefits from the alignment. */
.ws-left th,
.ws-left td {
    text-align: left !important;
}
/* The fixed-width input cells. `.ws-left td` sets text-align, and a number
   input in a left-aligned table would otherwise centre or right-align its own
   value by UA default — so the same alignment is stated on the control, not on
   its wrapper. A cell whose input sits on one edge and whose neighbouring
   plain text sits on the other is a misprint, not a preference. */
.ws-left input[type="number"],
.ws-left select {
    text-align: left;
}
/* The four derived wind-case bearings. It is a READOUT, not an input: there is
   nothing to click, because the cases are computed from the bearing field and
   an earlier version let the owner tick them (see the theta checkbox removal
   in core.js). Making it look editable would be the same defect in reverse. */
.ws-readout {
    display: flex;
    gap: 8px;
    align-items: baseline;
    flex-wrap: wrap;
    font-size: 11px;
    color: var(--text);
}
.ws-readout .ws-glyph {
    font-family: var(--mono);
    font-size: 12px;
}
/* Numbers still get the monospaced face inside a left-aligned table. The
   alignment changed, not the typeface — a decimal point is still a decimal
   point, and dropping the mono would make the values harder to scan for a
   different reason. */
.ws-left .ws-num {
    font-family: var(--mono);
    font-size: 11px;
    white-space: nowrap;
    text-align: left !important;
}
.ws-glyph {
    font-family: var(--mono);
    white-space: nowrap;
    width: 1%;
}
.ws-note {
    font-size: 10px;
    color: var(--text2);
    line-height: 1.35;
}
/* The SLS column, tinted and separated by a rule. Two columns of numbers that
   differ by a factor of about two need a visual boundary, or the eye reads
   them as a transcription error. */
.ws-sls {
    border-left: 1px solid var(--line, var(--border));
}
th.ws-sls { color: var(--accent2, var(--accent)); }
.ws-gov { font-weight: 600; }
/* 🔴 THE GOVERNING ROW IN EACH θ BLOCK (owner, 2026-10-14: "Highlight the row
   with highest wind loads for each cardinal direction").

   A BACKGROUND, NOT A BOLD FACE. The M_d table already uses `.ws-gov` for
   weight on a "governing" badge; reusing it here for a whole row would give the
   two the same appearance for two different meanings. A tinted row says "look
   here" from across the table, which is the whole point — the reader is scanning
   four θ blocks for the member that carries the bearing, not reading cells.

   `color-mix` against `--accent` keeps it legible in BOTH themes without a
   second declaration, and `--panel2` underneath means it still reads as a tint
   rather than a fill on a row that also has to hold `--mono` numerals. */
.ws-table tbody tr.ws-max td {
    background: color-mix(in srgb, var(--accent) 14%, var(--panel2));
}
.ws-table tbody tr.ws-max td:first-child { font-weight: 600; }
/* 🔴 THE RULE BETWEEN θ BLOCKS (owner, 2026-10-14: "Draw a line separating
   different theta in the loads on member table").

   On EVERY CELL, not on the row. `border-collapse: collapse` means a rule set on
   the `<tr>` collapses with the 1px `border-bottom` its neighbours already
   carry and wins by nothing — the separator has to be a per-cell border, which
   is also what lets it span the full width of the table instead of stopping at
   the text. `--accent` rather than the ordinary `--border`, because every other
   line in this table is a ROW line and a block boundary is a different kind of
   thing; a rule the reader cannot tell from the others does not separate
   anything.

   `border-top` on the cells, not `border-bottom` on the previous row: the first
   θ block must have NO rule above it, and with a bottom-border scheme that is a
   special case on row 0. This way "there is a rule above me ⟺ I am not the first
   row of the table" needs no exception at all. */
/* 🔴 `padding-top` GOES ON THE FIRST ROW OF THE NEW BLOCK, so the rule has air
   under it and the block reads as starting there. It is deliberately NOT
   `border-bottom` on the previous block: that scheme needs a "no rule above the
   very first row" exception, and the exception is where the separator quietly
   goes missing. */
.ws-table tbody tr.ws-new-theta td {
    border-top: 1px solid var(--accent);
    padding-top: 6px;
}
/* A member/direction where Table C.5 prints "—". Dimmed, because the point is
   that this member takes nothing there — it should not compete for attention
   with the rows that carry a real load, but it must never be invisible. */
.ws-skip td { opacity: .72; }
.ws-skip td:first-child { opacity: 1; }
/* Caveats and markers: the things that are NOT a number. */
.ws-flag {
    font-family: var(--text);
    font-size: 10px;
    font-weight: normal;
    color: var(--text2);
}
.ws-warn { color: var(--warn); }
.ws-ok { color: var(--ok, var(--success)); }
/* A refused member. It stays in the table rather than being hidden, because a
   member that silently gets no load is the failure this whole panel is for. */
.ws-refusal { color: var(--warn); font-size: 11px; line-height: 1.4; }
/* The member table gets its own scroll box and a sticky header: it can be
   taller than the dialog. */
.ws-member-wrap {
    max-height: 280px;
    overflow: auto;
    border: 1px solid var(--line, var(--border));
    border-radius: 4px;
}
.ws-member th {
    position: sticky;
    top: 0;
    background: var(--panel2);
    z-index: 1;
}
.ws-select {
    width: 100%;
    min-width: 190px;
    font-size: 11px;
    padding: 2px 4px;
}
/* 🔴 2026-10-14: THE WIND-STUDY DROPDOWNS ARE CONTENT-WIDTH (owner: "Make all
   dropdown have smaller widths (except for TBL C5 classify). Dropdown widths
   need to be sufficiently wide to contain all dropdown contents.").

   A `<select>` is sized by its WIDEST OPTION at `width: auto`, which is exactly
   the requirement stated: wide enough for the longest label and no wider. The
   previous `width: 100%` stretched a nine-character "TC2" across a whole grid
   column, so the dialog's left edge was a column of short words floating in
   empty space.

   🔴 `max-width: 100%` IS NOT OPTIONAL. `width: auto` sizes to CONTENT, and one
   option in this dialog — "Regions B2, C, D — one shared column" — is long
   enough to exceed the column it sits in. Without the cap the control would
   push the grid wider and start a horizontal scrollbar in the dialog.

   🔴 `:not(.ws-select)` IS THE OWNER'S EXCEPTION. `.ws-select` is the Table C.5
   classify dropdown: it carries the longest options in the dialog ("Double C —
   d = 1.6b"), lives in its own table column where it is the only control, and
   the owner kept it full width deliberately. Excluding it by class rather than
   by id keeps the rule working for the dropdowns that are rebuilt on every
   preview refresh, which carry generated ids. */
.modal.ws-study select:not(.ws-select) {
    width: auto;
    max-width: 100%;
}
.ws-select-narrow { min-width: 62px; width: auto; }
.ws-member input.num-cell {
    width: var(--cell-w);
    padding: 3px 6px;
    text-align: right;
}
/* The action row. `.small` keeps the buttons at the same size as every other
   dialog's; the full-size pair was taller than the theta checkbox row it sat
   under, which made the whole dialog look like a form with no hierarchy. */
.ws-actions { margin-top: 12px; }
.ws-actions .btn { padding: 5px 12px; font-size: 11px; }

/* === R — the derived recurrence interval, as PLAIN TEXT =================
   It was a readonly <input type=number> parked at the bottom of the dialog, so
   the number that governs every coefficient was both last on screen and shaped
   like something editable. It is a lookup out of AS/NZS 1170.0 Table 3.3. */
.ws-r {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 5px;
    font-size: 12px;
    line-height: 1.3;
}
.ws-r-uls { font-family: var(--mono); font-weight: 600; color: var(--accent2, var(--accent)); }
.ws-r-sub { font-family: var(--mono); font-size: 11px; color: var(--text2); }
.ws-r-sls { font-family: var(--mono); font-size: 11px; color: var(--text2); }
.ws-r-src { font-size: 10px; color: var(--text2); margin-left: auto; }

/* === Figure 3.1(A) popout ================================================
   The inline version capped the map at 320 px, which is about 20% of a
   1735 px figure: the region boundaries — the entire content of the figure —
   were illegible. This takes the viewport instead. */
.ws-fig {
    position: fixed;
    inset: 0;
    z-index: 1200;
    background: rgba(5, 5, 18, 0.96);
    display: flex;
    flex-direction: column;
}
.ws-fig[hidden] { display: none; }
.ws-fig-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 14px;
    border-bottom: 1px solid var(--border);
    font-size: 12px;
    font-weight: 600;
    flex: 0 0 auto;
}
.ws-fig-body {
    flex: 1 1 auto;
    overflow: auto;
    padding: 14px;
    text-align: center;
}
.ws-fig-body img {
    /*  Its OWN width, not 100% and not a scaled-down box: the figure is only
        readable at 1:1, and the container scrolls. */
    width: auto;
    height: auto;
    max-width: none;
    display: inline-block;
    background: #fff;
    border-radius: 4px;
}
.ws-fig-err {
    display: inline-block;
    margin: 40px auto;
    padding: 14px 18px;
    font-size: 12px;
    color: var(--warn);
    text-align: left;
}

/* === Collapsed gates =====================================================
   The engine's "not applied" register is worth keeping — it says what the
   result does NOT know — but rendered as a paragraph it was the loudest thing
   in the dialog. Closed by default, one click from the summary. */
.ws-gates {
    font-size: 11px;
    color: var(--text2);
    margin: 8px 0;
}
.ws-gates summary {
    cursor: pointer;
    color: var(--warn);
    font-weight: 600;
}
.ws-gates[open] summary { margin-bottom: 4px; }

/* === Disclosure register =================================================
   ONE LINE, 2026-10-16 (owner: "Do not crowd app"). The register was first
   rendered inline — all 18 items, ~8 kB — which pushed the coefficient and
   "Loads on member" tables down the dialog that exists to show them. The bar
   below carries the count, the warning count and the first few names; the text
   itself opens as its own document via `openWindStudyDisclosures()`.

   The old `.ws-disc` / `.ws-disc-detail` rules are GONE with the inline list
   and are not replaced: the document is self-contained by design, because a
   register that has to be printable months later next to the model should not
   reach back into the app for its styling.

   ⚠ THE TOKENS ARE `--warning` AND `--border`, WHICH EXIST. The rules around
   here use `var(--warn)` and `var(--line)`, and NEITHER IS DEFINED in any
   stylesheet — a var() with no definition is invalid at computed-value time, so
   those declarations silently fall back to `inherit`. Not corrected here: six
   existing uses, and a theme change is the owner's call. New code uses real
   tokens. */
/* 🔴 `.ws-disc-bar` AND `.btn-count` ARE GONE FROM THIS FILE (owner,
   2026-10-16: "Remove 'Assumptions and disclosures - 8 item(s) …' and save .md
   from the wind dialog"). The one-line bar this stylesheet served is no longer
   rendered anywhere — the register is reached from the status bar — and the
   warning badge that travelled with the button now sits next to a control in
   the STATUS BAR, so its rule moved to `main.css` beside `.status-bar`.

   They were not left behind as dead rules. A class no markup uses is a second
   place for a reader to look for a control that moved, and `.ws-disc-bar` was
   already the subject of a negative gate in A55 for the same reason. */

/* === Collapsed reference tables =========================================
   The M_d table is eight rows of reference data, not a result, and the
   GOVERNING value it contains is already printed in the Coefficients table
   above it. Same shape as the gates block — closed by default, one click
   away — but it is reference data rather than a warning, so the summary is
   neutral text, NOT the warn colour. Reusing .ws-gates here would make
   ordinary tabulated data read as something the engine refused to do. */
.ws-collapse {
    font-size: 11px;
    color: var(--text2);
    margin: 8px 0;
}
.ws-collapse summary {
    cursor: pointer;
    color: var(--text);
    font-weight: 600;
}
.ws-collapse[open] summary { margin-bottom: 4px; }
.ws-collapse .ws-table { margin-top: 6px; }
