/* Design tokens and the shell. Palette from the approved mockup: warm paper,
   one warm accent for risk and warning, one green for "good". The dark theme
   inverts the paper, not the accents. */
.gs-light {
  --bg: #FAF9F5; --sf: #FFFFFF; --sf2: #F0EEE6; --sf3: #E8E6DC;
  /* Three weights of line, because the mockup uses three: hairline dividers
     inside a surface, the border of a card, the border of a field. */
  --ln0: rgba(31, 30, 29, 0.10); --ln: rgba(31, 30, 29, 0.12); --ln2: rgba(31, 30, 29, 0.15);
  --ink: #141413; --ink2: #5E5D59; --ink3: #73726C;
  --ac: #B4552F; --ac-h: #8F3F22; --ac-w: #F0EEE6;
  /* TWO ALARM LEVELS, BOTH READABLE AS TEXT. The mockup's #D97757 is a fill
     colour: as 13px text on this paper it measures 3.1:1, under the 4.5:1 body
     text needs, so it lives in --bad-f and paints only fills, dots and bars.
     The text tokens are the clay family a step apart - #B4552F at 4.6:1 for
     «warn» and #8F3F22 at 6.8:1 for «bad» - because the two levels sit side by
     side in the same strip (tone-extra-notable next to tone-extra-strong) and
     one colour for both would lose the distinction the tones exist to draw. */
  --ok: #558A42; --warn: #B4552F; --bad: #8F3F22; --bad-f: #D97757;
  --ok-w: #EDF2E8; --warn-w: #F8ECE6; --bad-w: #FAEDE7;
}
.gs-dark {
  --bg: #1A1918; --sf: #262624; --sf2: #30302D; --sf3: #3A3936;
  --ln0: rgba(250, 249, 245, 0.10); --ln: rgba(250, 249, 245, 0.14); --ln2: rgba(250, 249, 245, 0.20);
  --ink: #FAF9F5; --ink2: #C2C0B6; --ink3: #9C9A92;
  /* Lighter link pair: #B4552F on #1A1918 is barely visible. */
  --ac: #D97757; --ac-h: #E4937A; --ac-w: #38271F;
  /* On dark paper the louder level is the lighter one. */
  --ok: #558A42; --warn: #D97757; --bad: #E4937A; --bad-f: #E4937A;
  --ok-w: #242C20; --warn-w: #332420; --bad-w: #362521;
}

/* No webfont: both families are on every machine this runs on, and the screen
   is numbers in a dense grid - a font that arrives late reflows all of them. */
:root {
  --sans: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --serif: Georgia, 'Times New Roman', Times, serif;
}

body { margin: 0; background: #FAF9F5; font-family: var(--sans); -webkit-font-smoothing: antialiased; }
.gs *, .gs *::before, .gs *::after { box-sizing: border-box; }
.gs a { color: var(--ac); text-decoration: none; }
.gs a:hover { color: var(--ac-h); text-decoration: underline; }

.gs { display: flex; flex-direction: column; height: 100vh; min-height: 640px; background: var(--bg); color: var(--ink); font-family: var(--sans); font-size: 14px; line-height: 1.45; }
/* Numbers line up in columns everywhere on this screen. */
.gs-n, .gs-inp, .gs-title { font-variant-numeric: tabular-nums; }
.gs-boot { padding: 24px; color: var(--ink3); }
/* Ordinary lowercase labels. */
.gs-micro { font-size: 12px; color: var(--ink3); }
.gs-hint { font-size: 13px; color: var(--ink3); }
/* One rule for both block headings: the band's own and a panel's. */
.gs-h2, .gs-panel-t { margin: 0; font-family: var(--serif); font-size: 18px; font-weight: 500; }
.gs-sub { font-size: 12px; color: var(--ink3); }

.gs-hd { display: flex; align-items: center; gap: 12px; height: 60px; flex: none; padding: 0 24px; border-bottom: 1px solid var(--ln0); }
.gs-title { width: 300px; padding: 6px 8px; border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--ink); font-family: var(--serif); font-size: 20px; }
.gs-title:hover { border-color: var(--ln2); }
.gs-title:focus { outline: none; border-color: var(--ln2); background: var(--sf); }
.gs-hd-sp { flex: 1; }

.gs-import { display: flex; align-items: center; gap: 8px; height: 36px; padding: 0 6px 0 12px; background: var(--sf); border: 1px solid var(--ln2); border-radius: 8px; }
.gs-import.is-error { border-color: var(--bad); }
.gs-import-l { font-size: 12px; color: var(--ink3); }
.gs-import-i { border: 0; background: transparent; color: var(--ink); font-family: inherit; font-size: 13px; width: 240px; padding: 0; }
.gs-import-i:focus { outline: none; }
.gs-import-e { font-size: 12px; color: var(--bad); padding-right: 4px; }

.gs-status { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; color: var(--ink2); }
.gs-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink3); flex: none; }
.gs-status.is-ok .gs-dot { background: var(--ok); }
.gs-status.is-warn .gs-dot { background: var(--warn); }
.gs-status.is-bad .gs-dot { background: var(--bad-f); }
.gs-status.is-busy .gs-dot { background: var(--ink2); animation: gsPulse 0.9s ease-in-out infinite; }
@keyframes gsPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

/* The target formula is a band across the whole width, above the two columns.
   It grows with the target, so past 45vh it scrolls inside itself; the body
   keeps a floor of its own so neither squeezes the other out. */
.gs-band { flex: none; padding: 12px 24px; border-bottom: 1px solid var(--ln0); max-height: 45vh; overflow-y: auto; }

.gs-body { flex: 1; min-height: 260px; display: grid; grid-template-columns: minmax(0, 1fr) 276px; }
.gs-col { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.gs-col-c { background: var(--bg); overflow-y: auto; }
.gs-col-r { background: var(--bg); padding: 16px 20px 14px 0; }

.gs-panel { display: flex; flex-direction: column; min-height: 0; flex: 1 1 0; gap: 10px; }
.gs-panel-hd { display: flex; align-items: baseline; gap: 8px; }
.gs-panel-hd-right { display: flex; align-items: baseline; gap: 8px; margin-left: auto; }
.gs-panel-body { display: flex; flex-direction: column; gap: 12px; }
/* The list is the part of the column that gives: it grows into whatever the
   header and the footer leave, and scrolls inside that. flex-basis 0 and
   min-height 0 are both load-bearing - with either missing the list is sized by
   its content and pushes the footer off the bottom of the column. */
.gs-panel-body.is-scroll { overflow-y: auto; flex: 1 1 0; min-height: 0; }
.gs-count { font-size: 12px; color: var(--ink3); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------- primitives */

.gs-btn { border: 1px solid var(--ln2); background: transparent; color: var(--ink); font-family: inherit; font-size: 13px; height: 34px; padding: 0 12px; border-radius: 7px; cursor: pointer; }
.gs-btn:hover { border-color: var(--ink3); }
.gs-btn.is-ghost { border-color: transparent; background: transparent; color: var(--ink2); }
.gs-btn.is-ghost:hover { background: var(--sf2); color: var(--ink); }
.gs-btn.is-icon { width: 26px; height: 26px; padding: 0; border-color: transparent; background: transparent; color: var(--ink2); font-size: 14px; }
.gs-btn.is-icon:hover { background: var(--sf2); color: var(--ink); }
.gs-btn.on { border-color: var(--ink2); color: var(--ink); }

/* A button that reads as a link, for the quiet "show me the rest" affordances
   the mockup draws as anchors. */
.gs-link { border: 0; background: transparent; padding: 0; cursor: pointer; font-family: inherit; font-size: 13px; color: var(--ac); }
.gs-link:hover { color: var(--ac-h); text-decoration: underline; }

.gs-inp { width: 84px; padding: 4px 9px; border: 1px solid var(--ln2); border-radius: 7px; background: var(--sf); color: var(--ink); font-family: inherit; font-size: 15px; text-align: right; }
.gs-inp:hover { border-color: var(--ink3); }
.gs-inp:focus { outline: none; border-color: var(--ink2); }

.gs-search { width: 100%; height: 36px; padding: 0 12px; border: 1px solid var(--ln2); border-radius: 8px; background: var(--sf); color: var(--ink); font-family: inherit; font-size: 13px; }
.gs-search:focus { outline: none; border-color: var(--ink2); }

.gs-check { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; min-width: 0; }
.gs-check input { position: absolute; opacity: 0; width: 0; height: 0; }
.gs-check-box { position: relative; width: 15px; height: 15px; flex: none; border-radius: 4px; border: 1px solid var(--ln2); background: var(--sf); }
.gs-check input:checked + .gs-check-box { background: var(--ink); border-color: var(--ink); }
.gs-check input:checked + .gs-check-box::after { content: ''; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px; border: solid var(--sf); border-width: 0 1.5px 1.5px 0; transform: rotate(45deg); }
.gs-check input:focus-visible + .gs-check-box { outline: 2px solid var(--ink2); outline-offset: 1px; }
.gs-check-l { font-size: 13px; color: var(--ink); min-width: 0; }

/* Signed deviation as a centre-anchored bar. Still here because the UMF strip
   and the KNaO row draw a sign and a magnitude in one cell; the target rows
   print the signed number instead. */
.gs-dbar { position: relative; display: inline-block; width: 44px; height: 6px; flex: none; }
.gs-dbar-mid { position: absolute; left: 50%; top: -1px; width: 1px; height: 8px; background: var(--ln2); }
/* A zero deviation must still read as "measured and fine", not as an empty
   column, so the fill keeps a minimum visible width. */
.gs-dbar-fill { position: absolute; top: 1px; height: 4px; border-radius: 2px; background: var(--ink3); min-width: 2px; }
.gs-dbar.tone-ok .gs-dbar-fill { background: var(--ink3); }
.gs-dbar.tone-warn .gs-dbar-fill { background: var(--warn); }
.gs-dbar.tone-bad .gs-dbar-fill { background: var(--bad-f); }

.gs-bar { position: relative; display: block; height: 6px; background: var(--sf3); border-radius: 3px; overflow: hidden; }
.gs-bar.is-thin { height: 4px; }
.gs-bar-fill { position: absolute; left: 0; top: 0; height: 100%; background: var(--ink3); border-radius: 3px; }
.gs-bar-fill.tone-ok { background: var(--ok); }
.gs-bar-fill.tone-warn { background: var(--warn); }
.gs-bar-fill.tone-bad { background: var(--bad-f); }

.gs-seg { display: inline-flex; background: var(--sf2); border-radius: 7px; padding: 2px; gap: 2px; }
.gs-seg-b { border: 0; background: transparent; color: var(--ink2); font-family: inherit; font-size: 13px; height: 28px; padding: 0 10px; border-radius: 5px; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; }
.gs-seg-b:hover { color: var(--ink); }
.gs-seg-b.on { background: var(--sf); color: var(--ink); }
.gs-seg-n { font-style: normal; font-variant-numeric: tabular-nums; color: var(--ink3); }

.gs-x { border: 0; background: transparent; color: var(--ink3); cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 3px; border-radius: 4px; }
.gs-x:hover { background: var(--bad-w); color: var(--bad); }
.gs-empty { padding: 20px 0; color: var(--ink3); font-size: 13px; }

/* ------------------------------------------------- target band, left half */

/* TWO HALVES, SIDE BY SIDE, AND THEY DO NOT WRAP. The right half is where the
   ratios, the ceilings and the imported recipe live - that is what the space
   behind the vertical rule is for. Letting it wrap laid it across the whole
   width under the groups, left the right half of the band empty, and grew the
   band past half the screen. The left half is sized by its content and does not
   shrink; what gives is the right half, which is text and reflows. */
.gs-target { display: flex; flex-wrap: nowrap; gap: 12px 24px; align-items: flex-start; }
.gs-target-main { display: flex; flex-direction: column; gap: 8px; flex: 0 0 auto; }
.gs-target-hd { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.gs-target-div { width: 1px; flex: none; background: var(--ln0); align-self: stretch; }
.gs-target-side { flex: 1 1 280px; min-width: 190px; display: flex; flex-direction: column; gap: 10px; }
.gs-target-act { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-left: auto; align-self: center; }

/* The three Seger groups stand side by side, left to right, the way the
   formula is written on paper: fluxes, stabilisers, glass formers. They keep
   that row down to the narrowest desktop this screen supports - it is the
   COLUMNS of a row that narrow, not the row that breaks. */
.gs-groups { display: flex; flex-wrap: nowrap; gap: 24px; align-items: flex-start; }
.gs-gcol { display: flex; flex-direction: column; gap: 6px; }
.gs-ogroup { display: flex; flex-direction: column; gap: 6px; }
.gs-ogroup-hd { font-size: 12px; line-height: 1.2; color: var(--ink3); }

/* value | oxide | delivered | delta | role | remove. The classic notation puts
   the number first and the oxide after it - "0.717 CaO" - so the input is the
   first column and the name is a label to its right. */
.gs-orow { display: grid; grid-template-columns: 80px 62px 46px 44px 50px 14px; gap: 8px; align-items: center; }
.gs-orow.is-plain { grid-template-columns: 80px 62px 50px 14px; }
.gs-orow.has-div { padding-bottom: 6px; border-bottom: 1px dashed var(--ln0); }
.gs-orow.is-pass .gs-ox { color: var(--ink2); }
/* An oxide formula is one word, subscripts included. */
.gs-fx { white-space: nowrap; }
/* Subscripts must not stretch the line box: a fixed-height dense row would
   otherwise overlap the row below it (Al2O3 / B2O3 in the R2O3 group). */
.gs sub { line-height: 0; font-size: 0.72em; }
.gs-ox { font-size: 15px; color: var(--ink); line-height: 1.15; }
/* The field fills its grid cell, less the «≤» a ceiling row puts in front of
   it, so one media query narrows every input on the band at once. */
.gs-inp-wrap { display: flex; align-items: center; justify-content: flex-end; gap: 3px; min-width: 0; }
.gs-inp-wrap .gs-inp { flex: 1 1 auto; width: auto; min-width: 0; }
/* The role button says «треб.» / «попут.» while there is width for the words,
   and the same thing as «=» / «≤» when there is not. Both spellings live in the
   markup and this pair of rules picks one; the flip is in the 1360px query at
   the end of the file. That query has to come after these defaults - placed
   before them, it would lose to them and the glyph would never appear. */
.gs-role-i { display: none; }
.gs-role-t { display: inline; }
.gs-le { font-size: 13px; color: var(--ink3); }
.gs-actual { font-size: 14px; text-align: right; color: var(--ink2); font-variant-numeric: tabular-nums; }
.gs-actual.tone-none { color: var(--ink3); }
.gs-delta { font-size: 13px; text-align: right; color: var(--ink3); font-variant-numeric: tabular-nums; }
.gs-delta.tone-warn { color: var(--warn); }
.gs-delta.tone-bad { color: var(--bad); }

.gs-add { height: 26px; padding: 0 10px; border: 1px solid var(--ln2); border-radius: 7px; background: transparent; color: var(--ink2); font-family: inherit; font-size: 13px; cursor: pointer; }
.gs-add:hover { border-color: var(--ink3); color: var(--ink); }
/* Anchored overlay, not a block in the heading row: see AddOxide in gs-app.jsx
   for what it did to the band's width when it was laid out in flow. */
.gs-addwrap { position: relative; display: inline-flex; }
.gs-addbox { position: absolute; top: calc(100% + 6px); right: 0; z-index: 20; width: 300px; max-height: 240px; overflow-y: auto; border: 1px solid var(--ln2); border-radius: 8px; padding: 8px; background: var(--sf); box-shadow: 0 6px 20px rgba(20, 20, 19, 0.10); }
.gs-addlist { display: flex; flex-wrap: wrap; gap: 6px; }
.gs-addopt { border: 1px solid var(--ln2); background: var(--sf); color: var(--ink); font-family: inherit; font-size: 13px; padding: 3px 8px; border-radius: 6px; cursor: pointer; }
.gs-addopt:hover { border-color: var(--ink3); }

/* ------------------------------------------------ target band, right half */

.gs-ratios { display: flex; flex-wrap: wrap; gap: 6px 32px; }
.gs-ratio { display: flex; flex-direction: column; gap: 1px; }
.gs-ratio-k { font-size: 12px; color: var(--ink3); }
.gs-ratio-v { font-size: 17px; font-variant-numeric: tabular-nums; }


/* ------------------------------------------------------- materials column */

/* The search stays put while the list under it scrolls - it filters the whole
   base, including the part that is folded away. */
.gs-mfilter { display: flex; flex-direction: column; gap: 6px; position: sticky; top: 0; z-index: 1; padding-bottom: 4px; background: var(--bg); }
.gs-mgroup { display: flex; flex-direction: column; gap: 8px; }
.gs-mgroup + .gs-mgroup { margin-top: 14px; }
.gs-mgroup-hd { font-size: 12px; color: var(--ink3); }
.gs-mrow { display: flex; align-items: center; gap: 9px; min-height: 22px; }
.gs-mname { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gs-mrow .gs-check { flex: 1; min-width: 0; }
.gs-mrow .gs-check-l { color: var(--ink2); }
.gs-mrow.on .gs-check-l { color: var(--ink); }
/* The footer is a fixed part of the column, never part of the scroll: the list
   above it stops where the footer starts. It is kept short for the same reason
   - every line here is a line the list does not get. */
.gs-mfoot { display: flex; flex-direction: column; gap: 6px; flex: none; padding-top: 10px; border-top: 1px solid var(--ln0); }
.gs-mfoot-l { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--ink2); }
.gs-mfoot-l .gs-link { margin-left: auto; }
.gs-mact { display: flex; flex-wrap: wrap; gap: 2px 8px; }
.gs-mact .gs-btn { height: 28px; padding: 0 8px; }

/* ---------------------------------------------------------- results column */

.gs-results { padding: 18px 24px 40px; display: flex; flex-direction: column; gap: 12px; }

/* One line and not a coloured box: the dot carries the verdict, the sentence
   says it, and the measurement sits on the right. Everything the server added
   to it - the ceilings it measured, its warnings, the unreachable rows - is
   underneath in small text, and none of it was dropped. */
.gs-verdictwrap { display: flex; flex-direction: column; gap: 8px; }
/* The verdict line is one row and its two halves can both be long: let the
   sentence wrap rather than push the measurement off the card. */
.gs-verdict { display: flex; align-items: center; gap: 10px; padding: 11px 16px; background: var(--sf); border: 1px solid var(--ln); border-radius: 10px; font-size: 14px; flex-wrap: wrap; row-gap: 4px; }
.gs-verdict .gs-dot { width: 8px; height: 8px; }
.gs-verdict.tone-ok .gs-dot { background: var(--ok); }
.gs-verdict.tone-bad .gs-dot { background: var(--bad-f); }
.gs-verdict-t { min-width: 0; }
.gs-verdict-s { margin-left: auto; color: var(--ink2); font-size: 13px; font-variant-numeric: tabular-nums; text-align: right; }
.gs-verdict-more { display: flex; flex-direction: column; gap: 6px; padding: 0 2px; }
.gs-unreach { display: flex; flex-direction: column; gap: 5px; }
.gs-unreach-row { display: grid; grid-template-columns: 70px 110px 130px minmax(0, 1fr) 190px; gap: 12px; align-items: center; font-size: 13px; color: var(--ink2); }
.gs-unreach-fix { color: var(--bad); text-align: right; }
.gs-verdict-act { display: flex; gap: 8px; }

/* Toolbar: the hint next to «Варианты» is a whole sentence, so it reflows
   instead of stretching the row. */
.gs-toolbar { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; row-gap: 4px; }
.gs-toolbar .gs-hint { flex: 1 1 320px; min-width: 0; }

.gs-cards { display: flex; flex-direction: column; gap: 12px; }
.gs-card { display: flex; flex-direction: column; gap: 16px; padding: 20px 22px; background: var(--sf); border: 1px solid var(--ln); border-radius: 12px; cursor: pointer; }
.gs-card:hover { border-color: var(--ln2); }
.gs-card.on { border-color: var(--ink3); }
.gs-card-top { display: flex; flex-direction: column; gap: 5px; }
.gs-card-hd { display: flex; align-items: baseline; gap: 14px; }
.gs-label { margin: 0; font-family: var(--serif); font-size: 20px; font-weight: 500; }
.gs-card-act { margin-left: auto; display: flex; align-items: baseline; gap: 14px; }
.gs-err-v { font-size: 15px; font-variant-numeric: tabular-nums; }
.gs-cost { font-size: 15px; color: var(--ink2); font-variant-numeric: tabular-nums; }
.gs-mcount { font-size: 15px; color: var(--ink2); }

.gs-recipe { display: flex; flex-direction: column; gap: 9px; }
.gs-rrow { display: grid; grid-template-columns: minmax(0, 250px) 96px minmax(0, 1fr) 54px 16px; gap: 16px; align-items: center; }
.gs-rname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gs-rrow .gs-rname { font-size: 15px; }
.gs-rwhy { font-size: 13px; color: var(--ink3); white-space: nowrap; }
.gs-rval { font-size: 15px; text-align: right; font-variant-numeric: tabular-nums; }
.gs-rrow.is-junk .gs-rval { color: var(--bad); }

/* ---------------------------------------------------------------- overlays */

.gs-drawer { position: absolute; right: 312px; bottom: 16px; width: 440px; max-height: 60vh; overflow: auto; border: 1px solid var(--ln); border-radius: 12px; background: var(--sf); box-shadow: 0 8px 28px rgba(20, 20, 19, 0.14); }
.gs-drawer-hd { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--ln0); position: sticky; top: 0; background: var(--sf); }
.gs-drawer-in { margin-left: auto; font-size: 13px; color: var(--ink2); }
.gs-batch { padding: 10px 16px 16px; }
.gs-batch-row { display: grid; grid-template-columns: minmax(0, 1fr) 80px; gap: 12px; align-items: center; height: 24px; font-size: 14px; }
.gs-batch-g { text-align: right; font-variant-numeric: tabular-nums; }

/* --------------------------------------- row actions, contribution matrix */

/* Row-level destructive affordances stay out of the way until the row is
   hovered - a dozen always-on crosses is pure noise in a dense table. */
.gs-orow .gs-x, .gs-rrow .gs-x { opacity: 0; transition: opacity 0.1s ease; }
.gs-orow:hover .gs-x, .gs-rrow:hover .gs-x, .gs-x:focus-visible { opacity: 1; }

.gs-matrix-wrap { padding-top: 12px; border-top: 1px solid var(--ln0); overflow-x: auto; }
.gs-matrix { width: 100%; border-collapse: collapse; font-size: 13px; }
.gs-matrix th, .gs-matrix td { padding: 4px 8px; text-align: right; white-space: nowrap; }
.gs-matrix thead th { font-size: 12px; font-weight: 400; color: var(--ink3); border-bottom: 1px solid var(--ln0); }
.gs-matrix td { font-variant-numeric: tabular-nums; }
.gs-matrix .gs-matrix-name { text-align: left; max-width: 230px; }
.gs-matrix .gs-matrix-name .gs-rname { display: inline-block; max-width: 210px; vertical-align: middle; font-size: 13px; }
.gs-matrix tbody tr:hover { background: var(--sf2); }
.gs-matrix-total td { border-top: 1px solid var(--ln0); font-weight: 500; color: var(--ink2); }

/* --- target rows: the controls that live on them ------------------------ */

/* Quiet by default, outlined only when it says something unusual ("this one is
   a ceiling, not an aim"). */
.gs-role {
  border: 1px solid transparent; background: transparent; cursor: pointer;
  font-family: inherit; font-size: 12px; color: var(--ink3);
  padding: 3px 6px; border-radius: 6px; white-space: nowrap;
}
.gs-role:hover { border-color: var(--ln2); color: var(--ink); }
.gs-role.is-pass { color: var(--ink2); border-color: var(--ln2); border-style: dashed; }
/* The words-or-glyph pair of this button is declared next to the row grid,
   whose width decides which of the two is shown. */

/* The flux sum under the group it belongs to: a rule, the sum standing in the
   same column as the inputs above it, and the offer to normalise as plain
   text. A yellow-bordered chip made an ordinary fact look like a defect. */
.gs-fluxsum { display: flex; flex-direction: column; gap: 6px; }
/* As wide as the two columns it stands under - the field and the oxide name. */
.gs-fluxsum-ln { height: 1px; width: 150px; background: var(--ln2); }
.gs-fluxsum-row { display: flex; align-items: center; gap: 10px; }
.gs-fluxsum-v { width: 80px; font-size: 14px; text-align: right; font-variant-numeric: tabular-nums; }
.gs-fluxsum-h { font-size: 13px; color: var(--ink3); }
.gs-normalize {
  display: flex; align-items: center; gap: 10px;
  border: 0; background: transparent; padding: 0; cursor: pointer;
  font-family: inherit; color: var(--ink); text-align: left;
}
.gs-normalize:hover .gs-fluxsum-h { color: var(--ac); }

/* the +-sigma band sits behind the deviation bar */
.gs-dbar-band { position: absolute; top: 1px; height: 4px; background: var(--ln2); opacity: 0.5; border-radius: 2px; }

/* how the two alkalis are judged: a property of the target, sitting under the
   rows it speaks for. The KNaO line reuses the .gs-orow grid so its numbers
   stand in the same columns as the oxide rows above it */
.gs-knao { display: flex; flex-direction: column; gap: 7px; }
.gs-knao-hd { display: flex; align-items: center; }
.gs-knao-k { font-weight: 600; color: var(--ink); }
.gs-knao-t { font-size: 15px; text-align: right; color: var(--ink2); font-variant-numeric: tabular-nums; }
.gs-knao-row .gs-ox { color: var(--ink2); }

/* ceilings the screen sent on the user's behalf - shown, and switchable off */
/* The divider belongs to this block and not to the column, so that a target
   with no automatic ceilings (the strip renders nothing at all then) does not
   leave a rule hanging over empty space. */
.gs-autopass { display: flex; flex-direction: column; gap: 6px; border-top: 1px solid var(--ln0); padding-top: 10px; }
.gs-autopass-hd { display: flex; align-items: baseline; gap: 10px; }
.gs-autopass-hd .gs-role { margin-left: auto; }
.gs-autopass-l { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 13px; color: var(--ink); }
.gs-pass-i { display: inline-flex; align-items: baseline; gap: 4px; font-variant-numeric: tabular-nums; }

/* the imported recipe, kept as a compact reference card in the band */
.gs-ref { border: 1px solid var(--ln); border-radius: 10px; background: var(--sf); padding: 10px 12px; display: flex; flex-direction: column; gap: 4px; }
.gs-ref-hd { display: flex; align-items: baseline; gap: 8px; }
.gs-ref-hd .gs-x { margin-left: auto; }
.gs-ref-name { font-size: 14px; font-weight: 500; }
.gs-ref-meta { font-size: 13px; color: var(--ink3); }
.gs-ref-rows { display: flex; flex-wrap: wrap; gap: 3px 12px; }
.gs-ref-row { display: inline-flex; align-items: baseline; gap: 5px; font-size: 13px; color: var(--ink2); }
.gs-ref-act { display: flex; }
.gs-ref-note { margin-top: 2px; }
.gs-ref-note.tone-warn { color: var(--warn); }
.gs-ref-note.tone-bad { color: var(--bad); }

/* --- the quality line of a card ----------------------------------------- */

/* One grey sentence instead of a row of chips: the same five or six facts, the
   same tooltips, none of the boxes. */
.gs-qline { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 6px; font-size: 13px; color: var(--ink3); font-variant-numeric: tabular-nums; }
.gs-qi.tone-warn { color: var(--warn); }
.gs-qi.tone-bad { color: var(--bad); }
.gs-qsep { color: var(--ink3); }

/* what tells a variant apart from the recommended one: the "differs" block of
   POST /api/solve, and the count of the recipes folded into this card */
.gs-card-diff { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; font-size: 13px; color: var(--ink2); }
.gs-diff-i { display: inline-flex; align-items: baseline; min-width: 0; }
.gs-diff-i.is-merged { color: var(--ink3); }

/* --- what the recipe rests on ------------------------------------------- */

.gs-sens { display: flex; flex-direction: column; gap: 7px; padding: 12px 14px; background: var(--bg); border-radius: 9px; }
.gs-sens-lead { font-size: 13px; color: var(--ink2); }
.gs-sens-lead b { color: var(--ink); font-weight: 500; }
.gs-sens-rows { display: flex; flex-direction: column; gap: 7px; }
.gs-sens-row {
  display: grid; grid-template-columns: minmax(0, 250px) 140px 52px minmax(0, 1fr);
  gap: 16px; align-items: center; font-size: 14px;
}
.gs-sens-v { text-align: right; font-variant-numeric: tabular-nums; }
.gs-sens-why { font-size: 13px; color: var(--ink2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gs-sens-none { font-style: italic; }

/* --- umf strip: one compact line, three contamination levels ------------ */

.gs-ustrip { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; padding-top: 12px; border-top: 1px solid var(--ln0); }
.gs-ustrip-t { font-size: 12px; color: var(--ink3); flex: none; }
.gs-ustrip-g { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; }
.gs-ustrip-g + .gs-ustrip-g { padding-left: 12px; border-left: 1px solid var(--ln0); }
.gs-ucell { display: inline-flex; align-items: baseline; gap: 5px; font-size: 13px; }
.gs-ucell-n { color: var(--ink2); }
.gs-ucell-v { color: var(--ink); font-variant-numeric: tabular-nums; }
.gs-ucell-x { display: inline-flex; align-items: center; }
.gs-ucell-tr { font-size: 12px; color: var(--ink3); }
.gs-ucell.tone-bad .gs-ucell-v { color: var(--bad); }
.gs-ucell.tone-warn .gs-ucell-v { color: var(--warn); }
.gs-ucell.tone-extra-trace .gs-ucell-v { color: var(--ink3); }
.gs-ucell.tone-extra-notable .gs-ucell-v { color: var(--warn); }
.gs-ucell.tone-extra-strong .gs-ucell-v { color: var(--bad); }
.gs-ucell.tone-miss .gs-ucell-v { color: var(--ink3); }
/* an oxide shown but not judged: the value, in the muted ink that says no
   verdict was passed on it */
.gs-ucell.tone-none .gs-ucell-v { color: var(--ink3); }
.gs-promote {
  border: 1px solid var(--ln2); background: transparent; cursor: pointer;
  font-family: inherit; font-size: 12px; color: var(--ink2);
  padding: 1px 6px; border-radius: 6px; white-space: nowrap;
}
.gs-promote:hover { border-color: var(--ink3); color: var(--ink); }

/* --- materials ---------------------------------------------------------- */

.gs-flag { font-size: 12px; color: var(--warn); flex: none; }

/* --- batch -------------------------------------------------------------- */
.gs-batch-row.is-total { border-top: 1px solid var(--ln0); font-weight: 500; margin-top: 4px; padding-top: 4px; }

/* --- the states a screen fed by constants never had --------------------- */

.gs-loading { display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 13px; color: var(--ink3); }
.gs-spin { width: 8px; height: 8px; border-radius: 50%; background: var(--ink3); flex: none; animation: gsPulse 0.9s ease-in-out infinite; }

.gs-fail { border: 1px solid var(--bad); border-radius: 10px; background: var(--bad-w); padding: 11px 14px; }
.gs-fail-hd { display: flex; align-items: baseline; gap: 8px; }
.gs-fail-t { font-weight: 500; font-size: 14px; color: var(--bad); }
.gs-fail-c { font-size: 12px; color: var(--bad); opacity: 0.75; margin-left: auto; font-variant-numeric: tabular-nums; }
/* Russian heading by error code; the server's own sentence follows it as the
   detail and steps back a tone when it does. */
.gs-fail-w { font-size: 13px; font-weight: 500; color: var(--ink); margin-top: 4px; }
.gs-fail-m { font-size: 13px; color: var(--ink); margin-top: 3px; }
.gs-fail-w + .gs-fail-m { color: var(--ink2); }
.gs-fail-act { margin-top: 8px; }

/* warnings ride along with success too, so they are a note and not an alarm */
.gs-warns { display: flex; flex-direction: column; gap: 4px; }
.gs-warn { display: flex; gap: 6px; align-items: baseline; font-size: 13px; color: var(--ink2); }
.gs-warn-m { font-weight: 600; font-style: normal; color: var(--bad); flex: none; }

/* what the saved shelf lost coming back from localStorage. Reuses .gs-warn for
   the line itself - it is the same register, a note rather than an alarm - and
   only adds the box that separates it from the list it is talking about */
.gs-shelf-note { display: flex; flex-direction: column; gap: 6px; align-items: flex-start;
  border: 1px solid var(--ln); border-radius: 10px; background: var(--sf); padding: 10px 12px; }
.gs-shelf-note b { color: var(--ink); font-weight: 500; }

/* what arrives with the materials and was never asked for: a fact, not a miss,
   so it sits outside the red list and carries no verdict vocabulary */
.gs-arrive { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; font-size: 13px; color: var(--ink3); }
.gs-arrive-l { color: var(--ink3); }
.gs-arrive-i { display: inline-flex; align-items: baseline; gap: 3px; color: var(--ink2); font-variant-numeric: tabular-nums; }

/* where the passengers of the verdict actually landed */
.gs-pass { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 13px; color: var(--ink2); }
.gs-pass-i.is-over { color: var(--bad); }
.gs-pass-a { color: var(--ink3); }

.gs-err-v.tone-ok { color: var(--ink); }
.gs-err-v.tone-warn { color: var(--warn); }
/* the oxides the recipe carries none of: a name, next to the percentage and
   never inside it. Muted rather than alarming - the reason it is missing is the
   verdict's business, and the verdict is already loud about it */
.gs-miss { font-size: 13px; color: var(--warn); white-space: nowrap; }

/* --- narrow desktop ------------------------------------------------------ */

/* The queries stay at the end of the file: they override defaults declared
   above at the same specificity, and above those defaults they would lose. */

/* The screen is used at about 1150 CSS px. Across 1150-1360 the three Seger
   groups stay in a row, the band keeps its right half beside them and the shelf
   stays on screen; the width comes out of the oxide row's columns, and the role
   button shows its glyph instead of words (click, title and tone kept). */
@media (max-width: 1360px) {
  .gs-target { gap: 10px 16px; }
  .gs-groups { gap: 18px; }
  .gs-orow { grid-template-columns: 72px 58px 42px 40px 24px 12px; gap: 6px; }
  .gs-orow.is-plain { grid-template-columns: 72px 58px 24px 12px; }
  .gs-role-t { display: none; }
  .gs-role-i { display: inline; }
  .gs-ratios { gap: 6px 20px; }
  .gs-role { padding: 3px 4px; }
  .gs-fluxsum-ln { width: 136px; }
  .gs-fluxsum-v { width: 72px; }
}

/* Below 1120px the band stacks its two halves: 870 of groups + 16 of gap + 1 of
   divider + 190 of minimum right half + 48 of padding = 1117. The groups keep
   their row, and the band's max-height keeps the stack off the recipes. */
@media (max-width: 1120px) {
  .gs-target { flex-wrap: wrap; }
  .gs-target-main { flex: 1 1 100%; }
  .gs-target-div { display: none; }
  .gs-target-side { flex: 1 1 100%; }
}

/* Below this the groups give up the row and the shelf leaves it too. Not a size
   this screen is designed for - it is a dense desktop table - but it must not
   be broken. */
@media (max-width: 900px) {
  .gs-groups { flex-wrap: wrap; }
  .gs-body { grid-template-columns: minmax(0, 1fr); }
  .gs-col-r { display: none; }
}
