/* AnchorDesign360 — UI on the navy/Inter design system */

/* Self-hosted Inter (latin + latin-ext + cyrillic + cyrillic-ext subsets).
   One file per subset; weights 400–700 share each file via the variable
   font. Browsers pick the matching subset by unicode-range. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('assets/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('assets/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('assets/fonts/inter-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('assets/fonts/inter-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

:root {
  --brand: #042C71;
  --brand-600: #083a94;
  --brand-500: #1a4bb5;
  --brand-300: #5a7fd1;
  --brand-50: #eef2fb;

  --icon-hover-bg: #dde7fa;

  --accent-amber:      #D97706;
  --accent-amber-soft: #E4A051;

  --bg: #ffffff;
  --bg-soft: #f7f8fa;

  --surface: rgba(4, 44, 113, 0.04);
  --surface-hover: rgba(4, 44, 113, 0.06);

  --border: rgba(17, 24, 39, 0.08);
  --border-strong: rgba(17, 24, 39, 0.12);

  --text: #0b1220;
  --text-muted: #556070;
  --text-subtle: #7a8496;

  --text-on-dark:        rgba(255, 255, 255, 0.95);

  --color-success: #047857;
  --color-error:   #b91c1c;
  --color-warn:    #b45309;   /* passing but > 0.85 */
  --bg-error-subtle:   #fdecec;
  --border-error:   #f4c5c5;

  --bg-warn-subtle: #fdf3e3;
  --border-warn:    #ecd9b0;

  --input-bg:       #f2f5fa;
  --input-bg-hover: #edf1f8;
  --input-bg-focus: #ffffff;

  --radius-2xs:  2px;
  --radius-xs:   3px;
  --radius-base: 4px;
  --radius-sm:   6px;
  --radius-md:  10px;
  --radius-xl:  16px;

  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --dur-fast: 150ms;

  --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
      Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas,
      "Liberation Mono", "Courier New", monospace;

  --fw-regular: 400;
  --fw-medium:  500;
  --fw-semibold: 600;
  --fw-bold:    700;

  /* ---- type scale: the ONLY place px font sizes live (2026-07-25).
     Change a step here and every use follows; never write a px
     font-size in a rule again. ---- */
  --fs-tag: 9px;          /* smallest chip */
  --fs-badge-sm: 9.5px;   /* small badges, panel headings */
  --fs-badge: 10px;       /* chips / uppercase tags */
  --fs-cap: 10.5px;       /* captions, meta rows */
  --fs-xs: 11px;          /* smallest copy (brand tag, tiles) */
  --fs-label: 11.5px;     /* field labels, secondary rows, hints */
  --fs-sm: 12px;          /* small controls, app tiles */
  --fs-note: 12.5px;      /* notes, checks, prose in panels */
  --fs-md: 13px;          /* card & dialog titles, inputs */
  --fs-body: 13.5px;      /* body copy */
  --fs-lg: 14px;          /* gate / profile inputs */
  --fs-xl: 15px;          /* zoom glyphs */
  --fs-h1: 16px;          /* brand name, headline numbers */
  --fs-glyph: 20px;       /* dialog close glyph */
  --fs-glyph-lg: 24px;    /* dialog close glyph, mobile */
  /* text on filled/dark surfaces */
  --text-inverse: #ffffff;
  /* utilization-bar data colours (defined once, used by the bars) */
  --util-ok: #23a559;
  --bar-gray-1: #b9bcc0;
  --bar-gray-2: #a9a9a9;
  --bar-track: #c4c9d1;
  --util-no: #e23636;      /* utilization bar FAIL fill */
  --bar-cap: #b7bcc5;      /* utilization bar cap segment */
  --bar-grid: #b7bec9;     /* comparison bar grid fill */
  --bar-grid-border: #9aa3b2;
  --gate-tint-mid: #fafaf9;/* auth-gate backdrop gradient stops */
  --gate-tint-end: #edeae5;
  --surface: #ffffff;      /* opaque white component surface */

  --ls-caps: 0.12em;

  /* frosted mobile bars (bottom nav, section chips) — a token so the
     dark theme can swap it; it used to be a literal white */
  --glass: rgba(255, 255, 255, .93);
  --appbar-h: 54px;
  --statusbar-h: 38px;

  /* action blue of the mock-ups' Report button — brighter than the
     navy brand so it reads as THE call to action */
  --action-blue: #2563eb;

  /* ONE global UI scale. Every viewport sets it once and both the
     zoomed panels and the calc()-based app-bar type follow — no
     per-element font sizes anywhere. Desktop 0.74 puts body/field
     text at ~10px and titles under 12px; mobile uses --mzoom. */
  --uiscale: 1;
}

*,
*::before,
*::after { box-sizing: border-box; }

/* ------------------------------------------------------------ dark mode
   Chosen, not inverted. Surfaces and text are a separate scale; the
   brand blue is lifted so it stays legible as accent TEXT on a dark
   card, and the few places that use it as a FILL get an explicit dark
   foreground below. Set by app.js from the account menu (remembered),
   defaulting to the system preference. */
:root { color-scheme: light; }
:root[data-theme="dark"] {
  color-scheme: dark;          /* native selects, date pickers, scrollbars */

  --brand: #6f95e2;
  --brand-600: #93b0ec;
  --brand-500: #6f95e2;
  --brand-300: #a8c0ea;
  --brand-50: #1e2637;
  --icon-hover-bg: #24304a;
  --accent-amber: #e0a04e;
  --accent-amber-soft: #c1802f;

  --bg: #171b24;
  --bg-soft: #10131a;
  --surface: #1c212c;
  --surface-hover: rgba(231, 236, 245, .07);

  --border: rgba(231, 236, 245, .12);
  --border-strong: rgba(231, 236, 245, .22);

  --text: #e7ecf5;
  --text-muted: #9fabc0;
  --text-subtle: #7f8ba1;
  --text-inverse: #0d1220;

  --color-success: #4ec48a;
  --color-error: #f38b8b;
  --color-warn: #e0a04e;
  --bg-error-subtle: rgba(243, 139, 139, .16);
  --bg-warn-subtle: rgba(224, 160, 78, .16);
  --border-error: rgba(243, 139, 139, .42);
  --border-warn: rgba(224, 160, 78, .42);

  --input-bg: #1c212c;
  --input-bg-hover: #222836;
  --input-bg-focus: #232a37;

  --util-ok: #4ec48a;
  --util-no: #f36b6b;
  --bar-gray-1: #5b6472;
  --bar-gray-2: #4d5561;
  --bar-track: #2a3140;
  --bar-cap: #5f6877;
  --bar-grid: #4d5561;
  --bar-grid-border: #6b7484;

  --gate-tint-mid: #141821;
  --gate-tint-end: #0b0e14;

  --action-blue: #5b8cf5;
  --glass: rgba(23, 27, 36, .93);
}
/* --brand doubles as a FILL in a few places; on dark it is a light blue,
   so those foregrounds must flip to dark rather than stay white */
/* These have a brand-coloured FILL, which is light on dark, so their
   text must go dark. NOT .mchip.active — that one is tinted with
   --brand-50, a DARK surface here, and needs light ink instead. */
:root[data-theme="dark"] .vt.active,
:root[data-theme="dark"] button.primary,
:root[data-theme="dark"] .up-btns .primary { color: #0d1220; }
:root[data-theme="dark"] .mchip.active { color: var(--brand-300); }
:root[data-theme="dark"] .mchip.active svg { color: var(--brand-300); }
/* The sign-in gate is a frosted card over a photograph. On dark it keeps
   the same frosted construction, but the glass goes smoked instead of
   white and the photo is pushed further back, so the card still reads as
   a pane of glass rather than a white box on a dark page. */
:root[data-theme="dark"] #gate {
  background:
    linear-gradient(rgba(6, 10, 18, .62), rgba(6, 10, 18, .78)),
    url("assets/images/keycloak-bg.png") center / cover no-repeat,
    #0b0e14;
}
:root[data-theme="dark"] .gate-card {
  background: rgba(23, 27, 36, .72);
  border-color: rgba(231, 236, 245, .14);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .5);
}
:root[data-theme="dark"] .gate-card input[type="email"],
:root[data-theme="dark"] .gate-card input[type="password"],
:root[data-theme="dark"] .gate-card .pw-wrap input {
  background: rgba(13, 18, 32, .55);
  border-color: rgba(231, 236, 245, .18);
  color: var(--text);
}
:root[data-theme="dark"] .gate-btns .secondary {
  background: rgba(231, 236, 245, .08);
  border-color: rgba(231, 236, 245, .18);
  color: var(--text);
}

/* the report is printed on paper: always light */
@media print { :root[data-theme="dark"] { color-scheme: light; } }

html, body { margin: 0; padding: 0; height: 100%; }
html {
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}
body {
  background: var(--bg-soft);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: 1.45;
  overflow: hidden;
  overflow-wrap: break-word;
}

/* ------------------------------------------------------- app bar */
.appbar {
  height: var(--appbar-h);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 26px;
  padding: 0 16px;
}
/* the brand is a "home" button: model + ISO view (user 2026-07-29) */
.brand { display: flex; align-items: center; gap: 12px; min-width: 0;
         cursor: pointer; user-select: none; }
.logo-img { display: block; width: 30px; height: 30px; flex: none; }
.brand-stack { display: flex; flex-direction: column; min-width: 0; }
.brand-tag {
  font-size: calc(var(--fs-xs) * var(--uiscale));
  color: var(--text-muted);
  letter-spacing: .02em;
  white-space: nowrap;
}
/* visually hidden, real for crawlers and screen readers */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}
.brand-name {
  font-size: calc(var(--fs-h1) * var(--uiscale));
  font-weight: var(--fw-bold); color: var(--text);
  white-space: nowrap;
}

.appbar-right { margin-left: auto; display: flex; align-items: center; gap: 14px; flex: none; }
/* Report reads at the SAME effective size as the ISO/Front/Side/Top
   view segments (user 2026-07-28); scoped here so the gate/popover
   primaries keep the global button.primary size */
.appbar-right button.primary { font-size: calc(var(--fs-sm) * var(--uiscale)); }
button.lang, select.lang {
  font: inherit; font-size: calc(var(--fs-note) * var(--uiscale));
  font-weight: var(--fw-semibold);
  color: var(--text); background: none; border: none;
  padding: 7px 8px; border-radius: var(--radius-sm); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
  width: auto; height: auto;
}
button.lang:hover, select.lang:hover { background: var(--bg-soft); }
button.user {
  display: flex; align-items: center; gap: 5px;
  background: none; border: none; padding: 3px 4px;
  border-radius: var(--radius-xl); cursor: pointer;
  color: var(--text-subtle);
  transition: background var(--dur-fast) var(--ease-out);
}
button.user:hover { background: var(--bg-soft); }
.avatar {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--bg-soft); color: var(--text);
  /* same effective size as the ISO/Front/Side/Top segments and the
     Report button (user 2026-07-28) */
  font-size: calc(var(--fs-sm) * var(--uiscale));
  font-weight: var(--fw-semibold); letter-spacing: .3px;
}
button.user .chev { width: 12px; height: 12px; }
button.primary {
  font: inherit; font-weight: var(--fw-semibold);
  font-size: calc(var(--fs-body) * var(--uiscale));
  background: var(--brand); color: var(--text-inverse);
  border: none; border-radius: var(--radius-sm);
  padding: 9px 22px; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
button.primary:hover { background: var(--brand-600); }
button.primary:disabled { background: var(--brand-300); cursor: wait; }
button.secondary {
  font: inherit; font-weight: var(--fw-semibold);
  font-size: calc(var(--fs-body) * var(--uiscale));
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  padding: 8px 18px; cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
button.secondary:hover { border-color: var(--brand-300); background: var(--brand-50); }

/* ⓘ info buttons + modal info dialogs (native <dialog>) */
.info-btn {
  /* the ring IS the button (border + radius), so the hover fill and the
     glyph are concentric by construction — no font-glyph offsets */
  display: inline-flex; align-items: center; justify-content: center;
  box-sizing: border-box;
  width: 16px; height: 16px; margin-left: 4px; padding: 0;
  border: 1.4px solid currentColor; border-radius: 50%;
  background: none; color: var(--brand-500);
  font: italic 700 var(--fs-badge)/1 Georgia, "Times New Roman", serif;
  cursor: pointer;
  /* bottom of the icon sits on the bottom of the label text */
  vertical-align: text-bottom;
  transition: background-color .15s ease, color .15s ease,
              box-shadow .15s ease;
}
/* optical centring nudge for the glyph — measured at the sidebar's
   zoom 0.74: Georgia's italic i rides 0.17px high of the ring centre,
   so drop it 0.25px pre-zoom (0.25 x 0.74 = 0.185) to balance */
.info-btn > span { transform: translateY(0.25px); }
.info-btn:hover, .info-btn:focus-visible {
  color: var(--text-inverse); background: var(--brand-500);
  border-color: var(--brand-500);
  box-shadow: 0 0 0 3px var(--brand-50);
}
.info-btn:active { background: var(--brand-600); border-color: var(--brand-600); }
.info-dlg {
  border: 0; border-radius: 14px; padding: 0;
  width: min(480px, calc(100vw - 32px));
  max-height: min(78vh, 640px);
  box-shadow: 0 18px 60px rgba(11, 18, 32, .28);
  color: var(--text);
}
.info-dlg[open] { display: flex; flex-direction: column; }
.info-dlg::backdrop { background: rgba(11, 18, 32, .45); }
.info-dlg-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 13px 16px; flex: none;
  border-bottom: 1px solid var(--border-strong); background: var(--surface);
}
.info-dlg-head h3 { margin: 0; font-size: var(--fs-md); font-weight: var(--fw-semibold); }
.info-dlg-x {
  border: 0; background: none; cursor: pointer;
  font-size: var(--fs-glyph); line-height: 1; color: var(--text-muted);
  padding: 2px 7px; border-radius: 6px;
}
.info-dlg-x:hover, .info-dlg-x:focus-visible { background: var(--bg-soft); color: var(--text); }
.info-dlg-body {
  padding: 12px 16px 16px; overflow-y: auto;
  font-size: var(--fs-body); line-height: 1.55;
}
.info-dlg-body p { margin: 0 0 10px; }
.info-dlg-body table { border-collapse: collapse; width: 100%; margin: 8px 0 12px; }
.info-dlg-body th, .info-dlg-body td {
  border: 1px solid var(--border-strong); padding: 4px 8px; text-align: left;
}
.info-dlg-body th { background: var(--bg-soft); }
.info-dlg-body .dlg-img {
  display: block; max-width: min(340px, 100%); margin: 10px auto 4px;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 8px;
  padding: 6px;
}
.info-dlg-body .dlg-img-cap {
  text-align: center; color: var(--text-muted); font-size: var(--fs-label); margin: 0 0 10px;
}
/* stand-off type table: schematic column + EQUAL row heights (the
   tallest row's height, fixed, so every row matches) */
.so-tab td { height: 140px; vertical-align: middle; }
.so-tab td:nth-child(2) { text-align: center; padding: 2px 4px; }
.so-fig { display: block; margin: 0 auto; }
@media (max-width: 900px) {
  /* phone: near-full-width sheet with comfortable height */
  .info-dlg { width: calc(100vw - 20px); max-height: 84dvh; border-radius: 16px; }
  .info-dlg-head { padding: 14px 16px; }
  .info-dlg-x { font-size: var(--fs-glyph-lg); padding: 4px 10px; }
  /* narrower text column wraps further — keep the rows equal
     (300px clears the longest RU/KK cell) */
  .so-tab td { height: 300px; }
}

/* user profile popover */
.appbar { position: relative; }
.avatar-ico { width: 16px; height: 16px; color: var(--text-muted); }
.user-pop {
  position: absolute; right: 12px; top: calc(100% + 6px);
  z-index: 30; width: 240px;
  background: var(--bg); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: 0 6px 24px rgba(11,18,32,.14);
  padding: 12px 14px;
}
.user-pop label { margin-bottom: 8px; }
.up-btns { display: flex; gap: 8px; margin-top: 10px; }
.up-btns .primary { padding: 7px 14px; font-size: var(--fs-note); flex: 1; }
.up-btns .secondary { padding: 6px 12px; font-size: var(--fs-note); flex: 1; }
.up-err { color: var(--color-error); font-size: var(--fs-label); margin: 8px 0 0;
          min-height: 0; }
.up-err:empty { display: none; }
.up-mail { font-size: var(--fs-note); font-weight: var(--fw-semibold);
           margin: 0 0 10px; word-break: break-all; }
/* account dropdown, app-standard anatomy (user 2026-07-27): header
   ("Signed in as" + address), then borderless hover ROWS with a leading
   icon; Sign out is the last row, divided off and in the danger colour */
.up-head {
  display: flex; align-items: center; gap: 10px;
  padding: 2px 10px 10px;
}
.up-avatar {
  width: 34px; height: 34px; flex: none; border-radius: 50%;
  background: var(--brand-50); color: var(--brand);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-sm); font-weight: var(--fw-semibold);
  letter-spacing: .02em;
}
.up-id { min-width: 0; }
/* About is an <a> now — keep the row anatomy of its button siblings */
a.up-item { text-decoration: none; }
.up-cap {
  display: block;
  font-size: var(--fs-cap); color: var(--text-subtle);
  margin-bottom: 1px;
}
.up-menu {
  display: flex; flex-direction: column;
  border-top: 1px solid var(--border);
  padding-top: 6px; margin-top: 2px;
}
/* the sign-in form sits above the menu when logged out */
#up-out { padding-bottom: 10px; }
.user-pop .up-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; margin: 0; box-sizing: border-box;
  padding: 9px 10px; min-height: 36px;
  border: none; border-radius: var(--radius-sm);
  background: none;
  font: inherit; font-size: var(--fs-note); font-weight: var(--fw-medium);
  color: var(--text); text-align: left; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.user-pop .up-item:hover { background: var(--bg-soft); }
/* dark-mode row: label on the left, switch pinned right */
.up-item.up-toggle { justify-content: space-between; }
.up-item .up-tl { display: flex; align-items: center; gap: 10px; }
.up-item .sw { position: relative; width: 34px; height: 20px; flex: none;
               border-radius: 999px; background: var(--border-strong);
               transition: background var(--dur-fast) var(--ease-out); }
.up-item .sw::after { content: ""; position: absolute; top: 3px; left: 3px;
               width: 14px; height: 14px; border-radius: 50%;
               background: var(--bg);
               transition: transform var(--dur-fast) var(--ease-out); }
.up-toggle[aria-pressed="true"] .sw { background: var(--brand-500); }
.up-toggle[aria-pressed="true"] .sw::after { transform: translateX(14px); }
.up-item .up-ico {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
  color: var(--text-muted);
}
/* the language value reads as part of its row, pinned right — bare
   text, no native select box/arrow (user 2026-07-28: "no box around
   EN letters") */
.up-item.up-lang select.lang {
  margin-left: auto; border: none; background: none;
  appearance: none; -webkit-appearance: none;
  padding: 0 2px; width: auto; height: auto;
  font: inherit; font-weight: var(--fw-semibold); color: var(--text);
  cursor: pointer;
}
.up-item.up-lang select.lang:hover { background: none; }
.user-pop .up-signout {
  margin-top: 6px; padding-top: 10px;
  border-top: 1px solid var(--border);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  color: var(--color-error);
}
.up-signout .up-ico { color: inherit; }
.up-mail { padding: 0; margin: 0; }
/* build identity, quietly at the foot of the menu */
.up-ver {
  margin: 8px 0 0; padding: 8px 10px 0 36px;
  border-top: 1px solid var(--border);
  font-size: var(--fs-label); color: var(--text-muted);
  text-align: left; letter-spacing: .02em;
}

/* ---------------------------------------------------- status bar */
.statusbar {
  position: relative;
  height: var(--statusbar-h);
  background: var(--bg);
  border-top: 1px solid var(--border);
  display: flex; align-items: center;
  gap: 12px;
  padding: 0 12px 0 0;
}
.sb-left { display: flex; align-items: stretch; height: 100%; min-width: 0;
           overflow: hidden; }
.sb-seg {
  display: flex; align-items: center; gap: 6px;
  padding: 0 12px;
  font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--text);
  border-right: 1px solid var(--border);
  white-space: nowrap;
}
.sb-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--color-success); flex: none;
  animation: livepulse 2.4s infinite;
}
@keyframes livepulse { 0%,100% {opacity:1} 50% {opacity:.35} }
.sb-status { font-weight: var(--fw-semibold); }
.sb-status.busy .sb-dot { background: var(--accent-amber); }
.sb-status.error .sb-dot { background: var(--color-error); animation: none; }
.sb-status.error { color: var(--color-error); }

.sb-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.zoomgrp {
  display: flex; align-items: center;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.zoomgrp button {
  font: inherit; font-size: var(--fs-xl); line-height: 1;
  color: var(--text); background: none; border: none;
  width: 30px; height: 26px; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.zoomgrp button:hover { background: var(--bg-soft); }
.zoomgrp button + button { border-left: 1px solid var(--border); }
.helpbtn {
  font: inherit; font-size: var(--fs-note); font-weight: var(--fw-semibold);
  color: var(--text-muted); background: none;
  border: 1px solid var(--border-strong); border-radius: 50%;
  width: 26px; height: 26px; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.helpbtn:hover { background: var(--bg-soft); color: var(--text); }
.help-pop {
  position: absolute; right: 10px; bottom: calc(100% + 8px);
  z-index: 20;
  /* wide enough that every bullet stays on ONE line (user 2026-07-17):
     the panel sizes itself to its longest line in any language */
  width: max-content;
  max-width: calc(100vw - 24px);
  background: var(--bg); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: 0 6px 24px rgba(11,18,32,.14);
  padding: 12px 14px;
  font-size: var(--fs-note);
}
.help-pop > b { display: block; margin-bottom: 6px; font-size: var(--fs-note); }
.help-pop ul { margin: 0; padding-left: 16px; }
.help-pop li { margin: 3px 0; color: var(--text-muted);
               white-space: nowrap; }
.help-terms {
  display: block;
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm); font-weight: var(--fw-semibold);
  color: var(--brand-500); text-decoration: none;
}
.help-terms:hover { text-decoration: underline; }
/* uniform weight — no bold inside the help bullets (user 2026-07-18) */
.help-pop li b { color: inherit; font-weight: var(--fw-regular); }

/* ---------------------------------------------------- workspace */
.workspace {
  --left-w: 330px;
  --right-w: 400px;
  display: grid;
  height: calc(100vh - var(--appbar-h) - var(--statusbar-h));
  grid-template-columns: var(--left-w) minmax(0, 1fr) var(--right-w);
}
/* collapse the grid track, but KEEP the panels as grid items —
   display:none would shift the remaining columns */
.workspace.hide-left { --left-w: 0px; }
.workspace.hide-left .sidebar { border-right: none; }
.workspace.hide-right { --right-w: 0px; }
.workspace.hide-right .respanel { border-left: none; padding: 0; }
.sidebar, .respanel { min-width: 0; }

/* ---------------------------------------------------- sidebar */
/* Both side panels are a soft-grey column of white section CARDS
   (rounded, hairline border, faint shadow) — no tinted headers. */
.sidebar {
  background: var(--bg-soft);
  border-right: 1px solid var(--border);
  overflow-y: auto;
  padding: 10px;
}
.panel-title {
  font-size: var(--fs-badge); font-weight: var(--fw-bold);
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  color: var(--text-subtle);
  margin: 2px 4px 8px;
}
.sec,
details.rescard {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 1px 3px rgba(11, 18, 32, .05);
  margin-bottom: 8px;
  overflow: hidden;         /* body corners clip to the card radius */
}
.sec > summary,
details.rescard > summary {
  cursor: pointer;
  display: flex; align-items: center; gap: 10px;
  padding: 9px 32px 9px 9px;
  font-size: var(--fs-md); font-weight: var(--fw-semibold);
  color: var(--text);
  list-style: none;
  position: relative;
  user-select: none;
  transition: background var(--dur-fast) var(--ease-out);
}
.sec > summary::-webkit-details-marker,
details.rescard > summary::-webkit-details-marker { display: none; }
/* chevron drawn with borders — the Inter subset has no ▾ / ▼ / ⌄
   (measured), so a glyph would silently fall back to another font */
.sec > summary::after,
details.rescard > summary::after {
  content: ""; position: absolute; right: 14px; top: 50%;
  width: 6px; height: 6px;
  border-right: 1.8px solid var(--text-subtle);
  border-bottom: 1.8px solid var(--text-subtle);
  transform: translateY(-70%) rotate(45deg);
  transition: transform var(--dur-fast) var(--ease-out);
}
.sec:not([open]) > summary::after,
details.rescard:not([open]) > summary::after {
  transform: translateY(-50%) rotate(-45deg);
}
.sec > summary:hover,
details.rescard > summary:hover { background: var(--bg-soft); }
.sec > summary:hover::after,
details.rescard > summary:hover::after { border-color: var(--text); }
/* the sprite itself never renders — only its <symbol>s, via <use> */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
/* menu-title icon in a soft rounded tile (mockup) */
.sec-ico {
  width: 32px; height: 32px; flex: none;
  padding: 8px;
  background: var(--brand-50);
  border-radius: var(--radius-sm);
  color: var(--brand-500);
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
/* title + live value summary, stacked */
.sec-t {
  display: flex; flex-direction: column;
  min-width: 0; line-height: 1.35;
}
.sec-t b { font-weight: var(--fw-semibold); }
.sec-sub {
  font-style: normal; font-size: var(--fs-label); font-weight: var(--fw-regular);
  color: var(--text-subtle);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sec-sub:empty { display: none; }
/* <sub> glyphs (K_tr, l_b) must not expand the line box — every card
   header stays exactly the same height */
.sec-t sub { line-height: 0; }

.sec-body { padding: 4px 12px 12px; }

/* align-items:end keeps inputs on one baseline when a label wraps */
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px;
         align-items: end; }
/* three labelled fields sharing one row (stirrup count / Ø / area) */
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px 10px;
         align-items: end; }
/* subscripts must not stretch a caption's line box — with them, labels
   whose captions carry a <sub> sat a few px lower than their
   neighbours and the row read as misaligned */
.sec-body label sub { line-height: 0; }
.span2 { grid-column: span 2; }
/* BASE-PLATE transverse-reinforcement card layout (user 2026-07-23):
   two 3-equal-column rows (#row-st: Ø | n_re | A_st and #row-vre:
   reinforced edge | e_s | l1) and full-width single rows for l1-in-cone,
   leg form, the shear-reinf select and Cover. Elsewhere the wrappers
   dissolve (display: contents) and the .grid2 layout stands unchanged. */
#row-st, #row-vre, #row-vst { display: contents; }
/* the display rules above outrank the UA [hidden] rule — restore it so
   the group-visibility toggling (syncReinfGroups) can hide whole rows */
#row-st[hidden], #row-vre[hidden], #row-vst[hidden] { display: none; }
body[data-conn="plate"] #row-st,
body[data-conn="plate"] #row-vre,
body[data-conn="plate"] #row-vst {
  display: grid; grid-column: span 2;
  grid-template-columns: 1fr 1fr 1fr; gap: 8px 12px;
  /* same rule as .grid2/.grid3: inputs stay on ONE line when a
     label ("Stirrup Ø (mm)") wraps (user 2026-07-27) */
  align-items: end;
}
body[data-conn="plate"] #row-st > label,
body[data-conn="plate"] #row-vre > label,
body[data-conn="plate"] #row-vst > label { grid-column: auto; }
body[data-conn="plate"] #lbl-l1re,
body[data-conn="plate"] #lbl-reform,
body[data-conn="plate"] #lbl-stcover { grid-column: 1 / -1; }
.subhead { font-size: var(--fs-label) !important; color: var(--text-subtle); padding-top: 4px;
           font-weight: var(--fw-regular); }

label { display: block; font-size: var(--fs-label); color: var(--text-muted); font-weight: var(--fw-semibold); }
[hidden] { display: none !important; }
label > input, label > select, label > .pair { margin-top: 3px; }
label.check, label.radio { display: flex; flex-direction: row; align-items: center; gap: 7px; font-size: var(--fs-note); color: var(--text);
                           font-weight: var(--fw-regular); }
.radio-col { display: flex; flex-direction: column; gap: 8px; }
.pair { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.triple { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; margin-top: 3px; }

input, select {
  font: inherit; font-size: var(--fs-md);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-base);
  padding: 5px 7px;
  background: var(--input-bg);
  width: 100%;
  /* one fixed height for text inputs AND selects — the native select
     otherwise renders a couple of px shorter and breaks row alignment */
  height: 31px;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
input[type="checkbox"], input[type="radio"] { height: auto; }
input:hover, select:hover { background: var(--input-bg-hover); }
input:focus, select:focus {
  outline: 2px solid var(--icon-hover-bg);
  border-color: var(--brand-500);
  background: var(--input-bg-focus);
}
input[type="checkbox"], input[type="radio"] { width: auto; accent-color: var(--brand); }

.hint { font-size: var(--fs-label); color: var(--text-subtle); font-weight: var(--fw-regular); margin: 4px 0 0; }

/* connection & application presets (PROFIS-style tiles) */
.applist { display: grid; grid-template-columns: 1fr; gap: 6px; }
.app {
  display: flex; align-items: center; gap: 7px;
  font: inherit; font-size: var(--fs-sm); font-weight: var(--fw-regular);
  color: var(--text);
  background: var(--input-bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-base);
  padding: 6px 8px; cursor: pointer; text-align: left;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.app:hover { background: var(--icon-hover-bg); border-color: var(--brand-300); }
.app.active {
  background: var(--brand-50); border-color: var(--brand);
  box-shadow: inset 0 0 0 1px var(--brand);
  color: var(--brand);
  font-weight: var(--fw-semibold);
}
/* application icons: SVG line drawings, 283 x 212.25 (4:3). 28px is the
   widest that keeps "Beam to column" on one line inside a tile */
/* icons ship in two aspects (32x32 and 283x212.25) — contain, never distort */
.app-ico { width: 26px; height: 26px; object-fit: contain; display: block; flex: none; user-select: none; }

/* inline dimension editor over the 3D view */
.dim-input {
  position: absolute;
  width: 90px;
  z-index: 8;
  font-family: var(--font-mono);
  font-size: var(--fs-note);
  padding: 4px 6px;
  border: 2px solid var(--brand);
  border-radius: var(--radius-base);
  background: var(--surface);
  box-shadow: 0 2px 10px rgba(4,44,113,.25);
}

/* ---------------------------------------------------- viewport */
.viewport { position: relative; min-width: 0; overflow: hidden;
  /* the WebGL canvas is transparent: this gradient is the scene
     background — cool light at the top, faintly warm ground glow at
     the bottom, the common neutral-studio look of 3D tools */
  background: linear-gradient(180deg,
    var(--bg-soft) 0%, var(--gate-tint-mid) 48%, var(--gate-tint-end) 100%); }
#view3d { position: absolute; inset: 0; }
#view3d canvas { display: block; }

/* two floating cards over the viewport: view presets left, display
   toggles right — same card recipe as the side-panel sections */
.viewtools {
  position: absolute; top: 10px; left: 12px; right: 12px;
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 8px; flex-wrap: nowrap;  /* NEVER a second line: cards shrink and
                                   labels ellipsize instead (long RU/KK
                                   translations must not wrap) */
  pointer-events: none;      /* only the cards themselves catch clicks */
}
.viewtools > * { pointer-events: auto; }
.vt-card {
  display: grid; grid-auto-flow: column;
  /* equal tracks that CAN compress: under a squeezed flex row the card
     gets a definite width and 1fr equalises; min 0 lets text ellipsize */
  grid-auto-columns: minmax(0, 1fr);
  min-width: 0; flex: 0 1 auto;
  align-items: stretch; gap: 2px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 1px 3px rgba(11, 18, 32, .05);
  padding: 4px;
}
/* fixed min-widths keep the segments of each card EQUAL — an auto-sized
   grid sizes 1fr tracks to their own content, so equality needs a floor
   wider than the longest label */
.vt {
  font: inherit; font-size: var(--fs-sm); font-weight: var(--fw-semibold);
  border: none; background: none; color: var(--text-muted);
  height: 28px; min-width: 0; padding: 0 8px;
  border-radius: var(--radius-sm); cursor: pointer;
  text-align: center; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out);
}
.vt:hover { background: var(--brand-50); color: var(--text); }
.vt.active { background: var(--brand); color: var(--text-on-dark); }
/* typography matches the view-preset buttons (.vt) exactly */
.vt-check { display: flex; flex-direction: row;
  align-items: center; justify-content: flex-start;
  gap: 5px; font-size: var(--fs-sm); font-weight: var(--fw-semibold);
  color: var(--text-muted);
  height: 28px; min-width: 0; padding: 0 7px;
  border: 1px solid #d3d3d359;
  border-radius: var(--radius-sm); cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out); }
/* the box never shrinks; the text truncates with an ellipsis instead of
   painting over it when the segment runs out of room */
.vt-check input { flex: none; }
.vt-check span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.vt-check:hover { background: var(--bg-soft); }

/* "current configuration" card over the 3D view */
.viewlegend {
  position: absolute; left: 12px; bottom: 12px;
  min-width: 200px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 6px 18px rgba(11, 18, 32, .10),
              0 1px 3px rgba(11, 18, 32, .06);
  padding: 10px 14px 11px;
  font-size: var(--fs-sm); color: var(--text);
}
.viewlegend .vl-h {
  font-size: var(--fs-badge-sm); font-weight: var(--fw-bold);
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  color: var(--text-subtle);
  margin-bottom: 7px;
}
.viewlegend .vl-row {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 18px; padding: 2px 0;
}
.viewlegend .vl-row .k { color: var(--text-muted); }
.viewlegend .vl-row .v { font-weight: var(--fw-semibold); text-align: right;
  font-variant-numeric: tabular-nums; }
.viewlegend .vl-embed {
  margin-top: 7px; padding-top: 7px;
  border-top: 1px solid var(--border);
}
.viewlegend .vl-embed .v { color: var(--brand-500); }

/* ============ DESKTOP global scale (user 2026-07-18) ============
   The px design system is authored at 100%; one zoom factor brings
   body/field text to ~10px and titles under 12px WITHOUT touching a
   single component rule. The grid tracks shrink by the same factor
   so the zoomed panels still fill their columns exactly. */
@media (min-width: 901px) {
  :root { --uiscale: 0.74; }
  /* help-pop lives INSIDE the zoomed statusbar — zooming it again
     would compound to ~0.67 */
  .sidebar, .respanel, .viewtools, .viewlegend,
  .user-pop { zoom: var(--uiscale); }
  /* the ⓘ dialogs read at the SAME effective scale as the panels
     they explain (they sit outside .sidebar, so the panel zoom never
     reached them — user 2026-07-25: dialog fonts looked oversized) */
  .info-dlg { zoom: var(--uiscale); }
  .workspace {
    --left-w: calc(330px * var(--uiscale));
    --right-w: calc(400px * var(--uiscale));
  }
  .statusbar { zoom: 0.9; }
}

/* ---------------- access gate: full-screen sign-in ----------------
   Background image: drop the artwork at assets/auth-bg.jpg and it
   covers every auth screen (desktop + mobile); until then the navy
   gradient fallback carries the brand. Containers on top are
   TRANSLUCENT (frosted) so the image shows through. */
#gate {
  position: fixed; inset: 0; z-index: 60;
  /* concrete texture (user-provided) with a whisper of brand navy */
  background:
    linear-gradient(rgba(4, 44, 113, .16), rgba(4, 44, 113, .30)),
    url("assets/images/keycloak-bg.png") center / cover no-repeat,
    var(--bar-gray-1);
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
}
.gate-card {
  position: relative;
  width: 100%; max-width: 360px;
  padding: 22px 24px;
  /* frosted card — the fields used to float directly on the concrete
     photo with no surface at all (user 2026-07-28) */
  background: rgba(255, 255, 255, .74);
  -webkit-backdrop-filter: blur(14px) saturate(1.15);
  backdrop-filter: blur(14px) saturate(1.15);
  border: 1px solid rgba(255, 255, 255, .65);
  border-radius: 14px;
  box-shadow: 0 18px 50px rgba(11, 18, 32, .28);
}
/* the T&C link inside the consent line rendered browser-default blue */
.gate-consent a { color: var(--brand-500); text-decoration: none; }
.gate-consent a:hover { text-decoration: underline; }
/* gate language switch: globe + bare select, top-right of the card —
   before this the gate offered no way to change language at all */
.gate-lang {
  position: absolute; top: 18px; right: 18px;
  display: flex; align-items: center; gap: 2px;
  color: var(--text-muted); margin: 0;
}
.gate-lang svg {
  width: 15px; height: 15px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.3;
  stroke-linecap: round;
  /* optically center on the EN/RU/KK caps: the select's line box
     sits lower than the icon under flex centering */
  transform: translateY(3px);
}
.gate-lang select {
  border: none; background: none; width: auto; height: auto;
  appearance: none; -webkit-appearance: none;
  font: inherit; font-size: var(--fs-sm); line-height: 1;
  font-weight: var(--fw-semibold); color: var(--text);
  padding: 0 2px; cursor: pointer;
}
.gate-card input[type="email"], .gate-card input[type="password"],
.gate-card .pw-wrap input {
  background: rgba(255, 255, 255, .55);
  border-color: rgba(255, 255, 255, .6);
}
.gate-btns .secondary {
  background: rgba(255, 255, 255, .45);
  border-color: rgba(255, 255, 255, .6);
}
.gate-brand {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 16px;
}
.gate-brand img { width: 34px; height: 34px; }
.gate-brand b { display: block; font-size: var(--fs-h1); }
.gate-brand i {
  display: block; font-style: normal;
  font-size: var(--fs-xs); color: var(--text-muted);
}
.gate-card label { display: block; font-size: var(--fs-sm); margin: 0 0 10px; }
.gate-card input[type="email"], .gate-card input[type="password"],
.gate-card .pw-wrap input {
  width: 100%; margin-top: 3px; font-size: var(--fs-lg); padding: 9px 11px;
}
.gate-consent { display: flex; gap: 8px; align-items: flex-start; }
.gate-consent input { margin-top: 2px; }
.gate-btns { display: flex; gap: 10px; margin: 12px 0 8px; }
.gate-btns .primary, .gate-btns .secondary {
  flex: 1; padding: 10px 12px; font-size: var(--fs-md);
}
.gate-link {
  font-size: var(--fs-sm); color: var(--brand-500);
  cursor: pointer; text-decoration: none;
}
.gate-link:hover { text-decoration: underline; }
.gate-msg { font-size: var(--fs-sm); color: var(--text-muted); margin: 10px 0 0; }
.gate-msg.err { color: var(--color-error); font-weight: 600; }
.gate-msg a { color: var(--brand-500); cursor: pointer; }

/* mobile bottom navigation: fixed app-style tab bar (Model / Inputs /
   Results), built by app.js buildMobileNav; hidden on desktop. */
.mbotnav { display: none; }
@media (max-width: 900px) {
  .mbotnav {
    display: flex;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 25;
    height: var(--mnav-h, 56px);
    background: var(--glass);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .mbotnav button {
    flex: 1 1 0;
    min-width: 0;                 /* EQUAL thirds in every language */
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 3px;
    border: none; background: none;
    font: inherit; line-height: 1.1;
    /* nav labels follow the mobile scale (bar itself is unzoomed) */
    font-size: calc(var(--fs-cap) * var(--mzoom, 1));
    color: var(--text-muted);
    cursor: pointer;
    padding: 0;
  }
  .mbotnav button span {
    max-width: 100%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .mbotnav svg {
    width: 24px; height: 24px; flex: none;
    fill: none; stroke: currentColor; stroke-width: 1.5;
    stroke-linecap: round; stroke-linejoin: round;
  }
  .mbotnav button.active { color: var(--brand); }
}

/* mobile section chips: horizontally scrollable icon tabs over the
   input cards (built by app.js buildMobileSecBar; hidden on desktop).
   Styled like a modern mobile app tab row — icon above a short
   label, active tab in the brand blue. */
.msecbar { display: none; }
@media (max-width: 900px) {
  .msecbar {
    display: flex;
    gap: 2px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    padding: 4px 6px;
    position: sticky; top: var(--appbar-h); z-index: 14;
  }
  .msecbar::-webkit-scrollbar { display: none; }
  /* equal SQUARE tiles (user: width = height); two-line clamped
     labels centred under the icon */
  /* sizes are PRE-zoom (the bars live in the --mzoom subtree):
     72px x 1.15 = ~83px on screen, 11px text -> ~12.7px on screen */
  .mchip {
    flex: 0 0 72px;
    width: 72px; height: 72px;
    display: flex; flex-direction: column; align-items: center;
    justify-content: flex-start;
    gap: 5px;
    padding: 11px 4px 5px;
    border: none; background: none;
    border-radius: 14px;
    font: inherit; font-size: var(--fs-xs); line-height: 1.2;
    color: var(--text-muted);
    cursor: pointer;
  }
  .mchip span {
    white-space: normal;
    text-align: center;
    word-break: break-word;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    max-width: 100%;
  }
  /* the global .sec-ico is a padded tile — in a chip it is a bare
     glyph (22px pre-zoom = ~16px on screen) */
  .mchip .sec-ico {
    width: 22px; height: 22px; flex: none;
    padding: 0; background: none; border-radius: 0;
    color: inherit; stroke: currentColor;
  }
  .mchip:hover { background: var(--surface-hover); }
  .mchip.active {
    color: var(--brand);
    background: var(--brand-50);
    font-weight: var(--fw-medium);
  }
  /* a results card with issues flags its chip (mobile has no card
     summary — the chip is the title) */
  .mchip.warn { color: var(--accent-amber); }
  .mchip.warn.active {
    color: var(--accent-amber);
    background: var(--bg-warn-subtle);
  }
}

/* collapse handles on the viewport edges */
.edge-toggle {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 20px; height: 64px;
  /* was a hard-coded white; it must follow the surface so it does not
     glow on a dark page */
  background: var(--bg);
  border: 1px solid var(--border-strong);
  color: var(--text-subtle);
  font-size: var(--fs-badge);
  cursor: pointer;
  z-index: 6;
  padding: 0;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.edge-toggle:hover { color: var(--brand); border-color: var(--brand-300); }
.edge-toggle.left { left: 0; border-left: none; border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.edge-toggle.right { right: 0; border-right: none; border-radius: var(--radius-sm) 0 0 var(--radius-sm); }

/* ---------------------------------------------------- results */
.respanel {
  background: var(--bg);
  border-left: 1px solid var(--border);
  overflow-y: auto;
  padding: 12px;
  position: relative;      /* anchors the calc spinner */
}
/* the results-panel activity wheel (top-right) was removed 2026-07-30:
   two spinners at once (this one + the PDF-wait overlay) read as two
   different jobs. The status-bar dot still carries the busy state. */
@keyframes calcspin { to { transform: rotate(360deg); } }

/* full-screen wait overlay while the PDF report renders — the only
   progress signal that is visible on EVERY screen, mobile included */
#pdf-wait {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(11, 18, 32, .45);
  display: flex; align-items: center; justify-content: center;
}
#pdf-wait .pdf-wait-box {
  display: flex; align-items: center; gap: 12px;
  background: var(--bg); border-radius: 14px;
  padding: 18px 26px;
  font-size: calc(var(--fs-body) * var(--uiscale));
  font-weight: var(--fw-semibold);
  box-shadow: 0 12px 40px rgba(11, 18, 32, .25);
}
#pdf-wait .pdf-spin {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  border: 3px solid var(--border-strong);
  border-top-color: var(--brand-500);
  animation: calcspin .8s linear infinite;
}

/* status-bar issue badge: geometry-check problems, visible wherever the
   status line is (incl. the mobile model screen) */
.sb-issues {
  color: var(--accent-amber);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}
.results-empty { color: var(--text-muted); padding: 16px 6px; }
.results-empty p { margin: 0; }
/* progress indicator under the placeholder line — same type size as the
   text above it, reusing the report overlay's spinner geometry */
.results-empty .re-progress {
  display: flex; align-items: center; gap: 8px; margin: 10px 0 0;
}
.re-spin {
  flex: none; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--border-strong);
  border-top-color: var(--brand-500);
  animation: calcspin .8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .re-spin { animation-duration: 2.4s; }
}

/* result cards take the SAME card rule as the sidebar sections (the
   shared .sec, details.rescard block) — no padding of their own, or a
   closed card sits 22px taller than its left-panel twin */


.design-box {
  background: var(--brand-50); border: 1px solid var(--brand-300);
  border-radius: var(--radius-md);
  padding: 10px 14px; margin-bottom: 10px; font-size: var(--fs-md);
}
.design-box b { font-family: var(--font-mono); font-size: var(--fs-h1); color: var(--brand); }

/* utilisation percentage — also used by the comparison card's
   scope badge (.upct.scope, below) */
.upct { font-family: var(--font-mono); font-weight: var(--fw-bold); font-size: var(--fs-body); }

/* design-space chart */
/* heading above each panel of the design-space card */
.chart-h {
  font-size: var(--fs-badge-sm); font-weight: var(--fw-bold);
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  color: var(--text-subtle);
  margin: 12px 0 3px; padding-top: 8px;
  border-top: 1px solid var(--border);
}
.chart-h:first-of-type { margin-top: 4px; padding-top: 0; border-top: none; }
.chart-note { font-size: var(--fs-note); margin-bottom: 6px; }
.chart-note b { font-family: var(--font-mono); color: var(--color-success); }
.legendrow { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-bottom: 4px; }
.legendrow .lg { display: flex; align-items: center; gap: 5px; font-size: var(--fs-label); color: var(--text-muted); }
.legendrow .lg i, .charttip i {
  display: inline-block; width: 10px; height: 10px; border-radius: var(--radius-2xs);
}
.lg-ned {
  background: none !important;
  border-top: 2px dashed var(--text-muted); height: 0 !important; border-radius: 0 !important;
}
.chartwrap { position: relative; }
.chartwrap svg { display: block; }
.chartwrap .tick {
  font: var(--fw-regular) var(--fs-label) var(--font-sans); fill: var(--text-subtle);
  font-variant-numeric: tabular-nums;
}
.chartwrap .mark { font: var(--fw-semibold) var(--fs-sm) var(--font-sans); }
/* Sits over the plot, so it must stay small and out of the way: the JS
   docks it in the top plot corner opposite the crosshair, and the tinted
   background lets a curve underneath still read through. */
.charttip {
  position: absolute; pointer-events: none; z-index: 4;
  background: color-mix(in srgb, var(--bg) 93%, transparent);
  backdrop-filter: blur(2px);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: 0 1px 6px rgba(11,18,32,.10);
  padding: 5px 8px; font-size: var(--fs-cap); min-width: 0; white-space: nowrap;
  font-variant-numeric: tabular-nums;
  /* no transition on `left`: animating the corner swap drags the panel
     straight across the curves it is meant to keep clear */
}
.charttip div { display: flex; align-items: center; gap: 5px; margin-top: 2px; }
.charttip b { padding-left: 10px; font-family: var(--font-mono); margin-left: auto; }

/* route comparison text — same size/tone as the details lines below
   (user 2026-07-18: no bold, no mono header) */
.cmp { margin: 0; font-size: var(--fs-label); line-height: 1.55;
  color: var(--text-muted); }
.cmp .good { color: var(--color-success); }

/* manufacturer comparison — 2026-07-16 mockup: big product name with a
   route pill at the right, one full-width demand/capacity pill bar with
   the governing mode beside it, and a blue "details" disclosure */
/* compact rows (user 2026-07-18: smaller height, less padding) */
.mrow { padding: 5px 0 6px; border-bottom: 1px solid var(--border); }
.mrow:last-child { border-bottom: none; }
.mrow.selected { border-left: 3px solid var(--brand); padding-left: 8px; margin-left: -11px; }
.mhead { display: flex; align-items: baseline; gap: 7px;
         cursor: pointer; }
.mhead:hover .mname { color: var(--brand-500); }
/* same size as the card's section title (.sec-t b: 13px semibold) */
.mhead .mname { font-size: var(--fs-md); font-weight: var(--fw-bold);
  letter-spacing: -.01em; color: var(--text); }
.mhead .meta { font-size: var(--fs-cap); color: var(--text-subtle); }
.mhead .route {
  font-size: var(--fs-badge-sm); font-weight: var(--fw-bold); letter-spacing: .4px;
  border-radius: var(--radius-xs); padding: 1px 6px; margin-left: auto;
  background: var(--surface); color: var(--text-muted); white-space: nowrap;
}
/* bar + governing-mode caption on one line */
/* full-width track: every bar starts on the SAME axis and its length
   is set inline as capacity/card-max — more capacity, longer bar */
.ctrack { flex: 1 1 100%; min-width: 0; }
.cbar {
  position: relative; height: 15px; min-width: 64px; width: 100%;
  border-radius: 8px; background: var(--bar-gray-2); overflow: hidden;
  font-size: var(--fs-badge); font-variant-numeric: tabular-nums;
}
/* hexes lifted from the user's mockup; convert nothing — plain CSS */
.cbar .cfill {
  position: absolute; top: 0; left: 0; height: 100%;
  background: var(--color-success); border-radius: 10px;
  display: flex; align-items: center; justify-content: flex-end;
}
.cbar .cl { white-space: nowrap; pointer-events: none; }
.cbar .cl-in { color: var(--text-inverse); padding-right: 8px; }
/* a fill too short for its own label: the value sits just right of it */
.cbar .cl-out { position: absolute; top: 0; line-height: 16px;
  padding-left: 6px; color: var(--text-on-dark); }
.cbar .cl-end { position: absolute; right: 8px; top: 0; line-height: 16px;
  color: var(--text); }
.cbar.over { background: var(--color-error); }
.cbar.over .cl-end { color: var(--text-inverse); }

/* --- comparison card redesign (user 2026-07-20): big product name + route
   badge, per-action DUAL bars (demand track over capacity track) with the
   governing mode at the right. Single click selects, double-click details. */
.mrow { user-select: none; cursor: pointer; padding-top: 11px; }
.mrow .mhead { align-items: center; }
.mrow .mhead .mname { font-size: var(--fs-md); font-weight: var(--fw-bold); letter-spacing: -.02em;
  color: var(--text); }
.mrow .mhead .route { font-size: var(--fs-sm); font-weight: var(--fw-bold); letter-spacing: .3px; }
.pc-line { display: flex; align-items: center; gap: 18px; margin: 6px 0; }
.pc-bar { flex: 1 1 52%; min-width: 0; }
.pc-gov { flex: 1 1 48%; min-width: 0; text-align: right; font-size: var(--fs-badge);
  font-weight: 400; color: var(--text-muted); white-space: normal;
  line-height: 1.2; }
.db { display: flex; flex-direction: column; gap: 0; }
/* full-width bordered track (white); the fill shows value/max within it.
   overflow:hidden guarantees a label can never spill past the track */
.dbt { position: relative; height: 21px; border: 1px solid var(--bar-track);
  background: var(--surface); overflow: hidden; font-variant-numeric: tabular-nums; }
.dbf { height: 100%; display: flex; align-items: center; justify-content: flex-end; }
.dbf.ok { background: var(--util-ok); }
.dbf.no { background: var(--util-no); }
.dbf.cap { background: var(--bar-cap); }
.dbf .in { font-size: var(--fs-md); font-weight: 400; white-space: nowrap;
  padding: 0 7px; color: var(--text-inverse); }
/* short fill: value on the WHITE remainder, pinned to the right edge (dark) */
.dbt .out { position: absolute; top: 0; right: 0; height: 100%; display: flex;
  align-items: center; padding: 0 6px; font-size: var(--fs-md); font-weight: 400;
  white-space: nowrap; color: var(--text); }
/* per-mode details now open on DOUBLE-click, not a summary click */
.mrow details.mbreak > summary { display: none; }
.mrow details.mbreak { margin-top: 6px; }
.mstats { display: flex; gap: 12px; align-items: baseline; margin-top: 1px;
  font-size: var(--fs-label); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.mstats .upct { font-size: var(--fs-note); }
/* failure-mode breakdown: collapsed by default; grouped by action.
   The row's summary line already gives the verdict, so this is opt-in. */
.mbreak { margin-top: 1px; }
/* per-bar utilisation grid in the double-click expansion */
.gridwrap { display: flex; align-items: center; gap: 8px; margin: 4px 0 2px; }
.bargrid { background: var(--bar-grid); border: 1px solid var(--bar-grid-border);
  border-radius: 0; flex: 0 0 auto; }
.bargrid circle { stroke: rgba(0,0,0,.35); stroke-width: .5; }
.gridleg { font-size: var(--fs-cap); color: var(--text-muted); }
/* flex row, so the marker centres on the text instead of being pinned
   to a guessed `top` offset */
/* carries no verdict of its own, so it reads as the plain "details" link
   the Utilization card uses (.urow summary) */
.mbreak > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: 6px;
  font-size: var(--fs-label); color: var(--action-blue, var(--brand-500));
  font-weight: var(--fw-semibold);
  padding: 2px 0; user-select: none;
  border-radius: var(--radius-xs);
}
/* "Not applicable," and the required length read as one sentence */
.mstats.na { gap: 6px; }
.mbreak > summary::-webkit-details-marker { display: none; }
/* Drawn with borders, not a glyph: the self-hosted Inter subset has no
   ▶ / ▸ / ► (measured), so a character would silently fall back to
   another font and change size between machines. */
.mbreak > summary::before {
  content: ""; flex: 0 0 auto;
  width: 0; height: 0;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  border-left: 6px solid currentColor;
  transition: transform var(--dur-fast) var(--ease-out);
}
.mbreak[open] > summary::before { transform: rotate(90deg); }
.mbreak > summary:hover { color: var(--brand-500); }
.mbody {
  margin: 3px 0 2px 4px; padding-left: 9px;
  border-left: 2px solid var(--border-strong);
}
.mgrp {
  font-size: var(--fs-badge-sm); font-weight: var(--fw-bold);
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  color: var(--text);
  margin: 6px 0 2px; }
.mgrp:first-child { margin-top: 0; }
/* The results panel is ~370 px wide: a name+value flex row wrapped the
   clause number onto its own line and pushed the numbers around. Stack
   instead — mode name, then its values indented beneath. */
.mline {
  font-size: var(--fs-label); color: var(--text-muted);
  padding: 2px 0;
}
/* name + chip on the left, utilisation pinned right */
.mline .mtop { display: flex; align-items: baseline; gap: 6px; }
.mline .mn { font-weight: inherit; flex: 1 1 auto; min-width: 0; }
.mline .mpct {
  flex: 0 0 auto; margin-left: auto;
  font-family: var(--font-mono); font-weight: var(--fw-bold);
  font-size: var(--fs-cap); font-variant-numeric: tabular-nums;
}
.mline .mpct.ok { color: var(--color-success); }
.mline .mpct.warn { color: var(--color-warn); }
.mline .mpct.bad { color: var(--color-error); }
.mline .mv {
  display: block; margin-left: 10px;
  color: var(--text); font-variant-numeric: tabular-nums;
}
.mline .nw { white-space: nowrap; margin-right: 10px; }
/* Governing and exceeded modes read like every other line: the chip and
   the red percentage carry the signal. Recolouring the whole line (name,
   driver, N_Rd) turned three plain facts into an alarm. */
.mline .govtag, .mline .badtag {
  flex: 0 0 auto;                 /* inside .mtop's flex row */
  display: inline-block; vertical-align: 1px;
  font-size: var(--fs-tag); font-weight: var(--fw-bold);
  text-transform: uppercase; letter-spacing: .06em;
  border-radius: var(--radius-2xs); padding: 0 4px;
}
.mline .govtag {
  color: var(--accent-amber);
  border: 1px solid var(--border-warn);
  background: var(--bg-warn-subtle);
}
/* the governing mode is also the one that failed */
.mline.bad .govtag {
  color: var(--color-error);
  border-color: var(--border-error);
  background: var(--bg-error-subtle);
}
.mline .badtag {
  color: var(--color-error);
  border: 1px solid var(--border-error);
  background: var(--bg-error-subtle);
}
/* not verified in this run: no number, only the short verdict.
   `.why` carries the full reason in its title attribute. */
.mline.skip { color: var(--text-subtle); }
.mline.skip .mv { color: var(--text-subtle); font-style: italic; }
.mline.skip .mv.why {
  cursor: help;
  text-decoration: underline dotted var(--border-strong);
  text-underline-offset: 2px;
}
.mline.note { color: var(--text-subtle); margin-top: 5px; }
/* ETA + verdict header lines at the TOP of the details body: same 11px
   rhythm and tight spacing as the mode lines that follow */
.mline.note.hdr { margin-top: 0; }
.mline.note.hdr .upct { font-size: var(--fs-label); }
.mnote { font-size: var(--fs-label); color: var(--text-subtle); margin-top: 2px; }
.mnote.err { color: var(--color-error); }
/* outside the assessed scope: not an overload, so neither green nor red */
.mnote.scope { color: var(--text-muted); font-weight: var(--fw-semibold); }
.upct.scope { color: var(--text-muted); font-family: inherit;
  font-size: var(--fs-note); letter-spacing: 0.01em; }

/* per-anchor load table (Derived forces) */
.dfwrap { overflow-x: auto; margin-top: 6px; }
.dftable {
  width: 100%; border-collapse: collapse;
  font-size: var(--fs-label); font-variant-numeric: tabular-nums;
}
.dftable th {
  font-size: var(--fs-cap); font-weight: var(--fw-regular);
  color: var(--text-subtle); line-height: 1.25;
  text-align: right; padding: 3px 7px;
  border-bottom: 1px solid var(--border-strong);
  white-space: nowrap;
}
.dftable td {
  font-family: var(--font-mono);
  font-weight: var(--fw-regular);
  text-align: right; padding: 3px 7px;
  border-bottom: 1px solid var(--border);
}
.dftable b, .dftable strong { font-weight: var(--fw-regular); }
.dftable th:first-child, .dftable td:first-child {
  text-align: left; font-family: var(--font-sans);
  color: var(--text-muted);
}
.dftable tbody tr:last-child td { border-bottom: none; }

/* checks */
.checkrow {
  display: flex; align-items: baseline; gap: 8px;
  padding: 5px 0; border-bottom: 1px solid var(--border); font-size: var(--fs-note);
}
.checkrow:last-child { border-bottom: none; }
.cmark { font-weight: var(--fw-bold); width: 16px; text-align: center; }
.cmark.ok { color: var(--color-success); }
.cmark.bad { color: var(--color-error); }
.checkrow .cname { flex: 1; }
.checkrow .cvals { font-family: var(--font-mono); font-size: var(--fs-label); color: var(--text-muted); white-space: nowrap; }

ul.notes { margin: 2px 0; padding-left: 18px; font-size: var(--fs-note); }
/* the amber highlight marks "something here is not ok" — it is worn by
   Geometry checks, never by Notes (scope reminders are not errors) */

/* WebGL-unavailable notice inside the viewport */
.webgl-msg {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center; padding: 40px;
  color: var(--text-muted); font-size: var(--fs-body); line-height: 1.7;
}

/* tension / shear group headers in the utilization card */
.modenote {
  font-size: var(--fs-note);
  color: var(--text-muted, var(--text-muted));
  line-height: 1.55;
  padding: 2px 0 6px;
}

/* collapsible result cards: summary wears the card-title style */

/* ------------------------------------------------------------------ */
/* unified collapsible styling: the right menu wears the SAME card look */
/* as the left sidebar sections (tile icon, chevron, hover, states)    */
.respanel { padding: 10px; background: var(--bg-soft); }
details.rescard > summary + * { margin-top: 4px; }
details.rescard[open] { padding-bottom: 12px; }
details.rescard[open] > *:not(summary) {
  margin-left: 12px;
  margin-right: 12px;
}
/* a card with a problem keeps the EXACT same card shape as everything
   else — only the icon tile and the value line turn amber */
details.rescard.warn > summary .sec-ico {
  background: var(--bg-warn-subtle);
  color: var(--accent-amber);
}
details.rescard.warn > summary .sec-sub {
  color: var(--accent-amber);
  font-weight: var(--fw-semibold);
}
/* the card TITLE carries the signal too (user 2026-07-27: "Geometry
   checks" heading must change colour when a check fails) */
details.rescard.warn > summary .sec-t > b {
  color: var(--accent-amber);
}

/* ---------------------------------------------------- mobile */
/* one column: 3D view on top, then inputs, then results; the page
   scrolls as a whole (the desktop layout pins panels full-height) */
@media (max-width: 900px) {
  body { overflow: auto; }
  .appbar {
    gap: 12px; padding: 0 10px;
    position: sticky; top: 0; z-index: 15;
  }
  .brand { gap: 9px; }
  .appbar-right { gap: 8px; }
  button.primary { padding: 8px 16px; }
  /* ============ app-style mobile shell (2026-07-17 v2) ============
     Three SCREENS switched by a fixed bottom nav (Model / Inputs /
     Results) — exactly ONE visible at a time, no duplicated
     navigation. body[data-mscreen] (set by app.js buildMobileNav)
     selects the screen:
       model    3D viewer filling the screen + slim status strip
       inputs   horizontal section chips + ONLY the active card
       results  the results cards                                   */
  /* --mzoom 1.15 puts body copy at ~15.5px and inputs at ~15px — the
     mobile-typography floor (Material/iOS ~16px body). Was 0.75
     ("too small", user 2026-07-27). */
  :root { --mnav-h: 56px; --mzoom: 1.15; --uiscale: var(--mzoom); }
  html, body { overflow-x: hidden; max-width: 100vw; }
  body { display: flex; flex-direction: column; min-height: 100dvh; }
  .workspace { display: contents; }

  /* compact app bar — nothing wraps or overlaps on a phone */
  /* harmonized app bar — type comes from the global --uiscale calc
     rules (no local font sizes), only spacing is set here */
  /* flex: none — the body is a flex column, so without a locked basis
     the bar shrinks below var(--appbar-h) on the tallest screens
     (Graphs / Comparison), making its height jump between cards
     (user 2026-07-20) */
  .appbar { gap: 8px; padding: 0 10px; flex: none; }
  .appbar .brand { min-width: 0; gap: 8px; flex: 1 1 auto; }
  .appbar .brand-name {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    /* fluid on narrow phones: the wordmark shrinks with the viewport
       instead of getting cut (user 2026-07-18) */
    font-size: min(calc(var(--fs-h1) * var(--uiscale)), 3.4vw);
  }
  .appbar .logo-img { width: 26px; height: 26px; }
  /* mobile: wordmark only (user 2026-07-18) */
  .brand-tag { display: none; }
  .appbar button.lang, .appbar select.lang { padding: 5px 5px; }
  /* scope to the bar row — the sign-in sheet ALSO lives inside the
     header and its buttons must keep their own equal-width layout */
  .appbar-right button.primary {
    padding: 6px 12px;
    border-radius: 999px;              /* pill, app-like */
    flex: none;
  }
  /* avatar circle matches the Report pill height so the two controls
     read as one row (user 2026-07-27: "not same size") */
  .appbar .avatar { width: 34px; height: 34px; }
  .appbar button.user { padding: 2px; gap: 3px; }
  .appbar button.user .chev { width: 10px; height: 10px; }
  /* the -/+ zoom buttons do not exist on a phone */
  .zoom-btn-hint { display: none; }

  /* the account / sign-in popover becomes a full-width sheet under
     the app bar with comfortable touch targets */
  .user-pop {
    position: fixed;
    left: 10px; right: 10px;
    top: calc(var(--appbar-h) + 8px);
    width: auto;
    border-radius: 16px;
    padding: 16px;
    box-shadow: 0 12px 40px rgba(11, 18, 32, .22);
  }
  .user-pop label { font-size: var(--fs-sm); margin-bottom: 10px; }
  .user-pop input { font-size: var(--fs-lg); padding: 10px 12px; }
  .up-btns { gap: 10px; margin-top: 12px; }
  .up-btns .primary, .up-btns .secondary {
    padding: 11px 12px; font-size: var(--fs-md);
  }
  .up-mail { font-size: var(--fs-md); }
  .user-pop .up-item { padding: 11px 10px; }

  /* ---- model screen ---- */
  .viewport, .statusbar { display: none; }
  body[data-mscreen="model"] .viewport {
    display: block;
    order: 1; flex: none;
    height: calc(100dvh - var(--appbar-h) - 34px - var(--mnav-h));
    /* 160, not 260 (2026-07-30): in LANDSCAPE the computed height is
       ~248 px, so a 260 px floor pushed the status strip down under the
       fixed bottom nav and half of it disappeared. Portrait is nowhere
       near the floor, so nothing changes there. */
    min-height: 160px;
    border-bottom: 1px solid var(--border);
  }
  body[data-mscreen="model"] .statusbar {
    display: flex;
    order: 2; flex: none;
    position: static; z-index: 1;
    height: 34px;
    padding: 0 10px 0 0;     /* breathing room for the ? button */
  }
  .statusbar .zoomgrp { display: none; }
  /* status strip sits OUTSIDE the zoom subtree — scale its type by
     the same factor so it reads like the rest of the mobile UI */
  .sb-seg { padding: 0 9px; font-size: calc(var(--fs-cap) * var(--mzoom)); }
  /* current configuration: a slim translucent ONE-LINE strip pinned
     to the bottom edge of the model area (scrolls sideways) instead
     of the desktop card that covered the whole model */
  .viewlegend {
    display: flex;
    position: absolute;
    left: 0; right: 0; bottom: 0; top: auto;
    width: auto; max-width: none;
    flex-wrap: nowrap; align-items: center; gap: 12px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding: 5px 10px;
    background: var(--glass);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: none; border-top: 1px solid var(--border);
    border-radius: 0; box-shadow: none;
    font-size: calc(var(--fs-badge) * var(--mzoom)); white-space: nowrap;
  }
  .viewlegend::-webkit-scrollbar { display: none; }
  .viewlegend .vl-h { display: none; }
  .viewlegend .vl-row {
    display: inline-flex; align-items: baseline; gap: 5px; flex: none;
    border: none; padding: 0; margin: 0;
  }
  /* a hairline divider between the four items so a long product name
     never runs into the next label on the scrolling strip (user 2026-07-20) */
  .viewlegend .vl-row + .vl-row {
    padding-left: 12px;
    border-left: 1px solid var(--border);
  }
  .viewlegend .vl-row .k { color: var(--text-muted); }
  .viewlegend .vl-row .v { font-weight: var(--fw-semibold); color: var(--text); }
  /* help opens as a full-width BOTTOM sheet on the model screen,
     just above the bottom nav (user 2026-07-18). Same --mzoom scale
     as the rest of the mobile UI; offsets divide by it so the
     rendered sheet spans the screen. */
  .help-pop {
    position: fixed;
    zoom: var(--mzoom);
    left: calc(10px / var(--mzoom));
    right: auto;
    top: auto;
    bottom: calc((var(--mnav-h) + 10px) / var(--mzoom));
    /* full width: insets of a zoomed fixed element resolve against
       the UNZOOMED viewport, so width is set explicitly in zoomed
       units instead of a right offset */
    width: calc((100vw - 20px) / var(--mzoom));
    max-width: none;      /* the desktop max-content clamp would win */
    border-radius: 16px;
    padding: 16px;
    box-shadow: 0 12px 40px rgba(11, 18, 32, .22);
    z-index: 30;
  }
  .help-pop li { white-space: normal; }

  /* ---- inputs screen ---- */
  .sidebar { display: none; }
  .msecbar { display: none; }
  body[data-mscreen="inputs"] .msecbar {
    display: flex;
    order: 1; flex: none;
    position: sticky; top: var(--appbar-h); z-index: 14;
  }
  body[data-mscreen="inputs"] .sidebar {
    display: block;
    order: 2; width: 100%;
    border-left: none; border-right: none;
    overflow: visible;
    padding-bottom: calc(var(--mnav-h) + 16px);
  }
  .sidebar .panel-title { display: none; }
  /* the chips ARE the navigation: only the selected card renders,
     already opened, FULL SCREEN HEIGHT (scrolls further if taller) */
  .sidebar details.sec { display: none; }
  .sidebar details.sec.msel {
    display: block;
    min-height: calc(100dvh - var(--appbar-h) - 80px - var(--mnav-h));
  }

  /* ---- results screen: same chip pattern as the inputs ---- */
  .respanel { display: none; }
  body[data-mscreen="results"] .respanel {
    display: block;
    order: 1; width: 100%;
    border-left: none; border-right: none;
    overflow: visible;
    padding-bottom: calc(var(--mnav-h) + 16px);
  }
  .respanel .panel-title { display: none; }
  body[data-mscreen="results"] #mresbar {
    display: flex;
    position: sticky; top: var(--appbar-h); z-index: 14;
  }
  .respanel details.rescard { display: none; }
  body[data-mscreen="results"] .respanel details.rescard.msel {
    display: block;
    margin: 8px 10px;
    min-height: calc(100dvh - var(--appbar-h) - 96px - var(--mnav-h));
  }

  /* the selected section is a plain FULL-SCREEN panel, not a card:
     no border/radius/margin and NO collapsible header — the chip bar
     already names the open section (user 2026-07-18) */
  body[data-mscreen="inputs"] .sidebar details.sec.msel,
  body[data-mscreen="results"] .respanel details.rescard.msel {
    margin: 0;
    border: none; border-radius: 0; box-shadow: none;
  }
  .sidebar details.sec.msel > summary,
  .respanel details.rescard.msel > summary { display: none; }
  .sidebar details.sec.msel .sec-body { padding: 12px 12px 20px; }

  /* ONE proportional scale for the whole mobile UI instead of
     hand-tuned per-element font sizes: zoom scales text, spacing
     and controls TOGETHER, so every desktop proportion is preserved
     (typical body text 13.5px x --mzoom) */
  /* ONE scale for every mobile surface, model screen included */
  .sidebar, .respanel, #msecbar, .viewtools { zoom: var(--mzoom); }
  /* ⓘ dialogs read at the same effective scale as the panels */
  .info-dlg { zoom: var(--mzoom); }
  /* zoomed sticky/height maths: lengths inside a zoomed subtree are
     multiplied by the zoom, so anything anchored to UNZOOMED bars
     (app bar, bottom nav) divides by --mzoom to compensate */
  body[data-mscreen="inputs"] #msecbar,
  body[data-mscreen="results"] #mresbar {
    top: calc(var(--appbar-h) / var(--mzoom));
  }
  body[data-mscreen="inputs"] .sidebar details.sec.msel {
    min-height: calc((100dvh - var(--appbar-h) - 80px
                      - var(--mnav-h)) / var(--mzoom));
  }
  body[data-mscreen="results"] .respanel details.rescard.msel {
    min-height: calc((100dvh - var(--appbar-h) - 96px
                      - var(--mnav-h)) / var(--mzoom));
  }

  /* the "details" disclosure pill is gone on mobile too — the rows
     read the same as desktop, and a double-tap (dblclick) opens the
     per-product breakdown (user 2026-07-28; supersedes 2026-07-20) */

  /* type scale comes from the uniform --mzoom above — no per-element
     font sizes here */
  /* the collapse handles manage grid tracks — meaningless here */
  .edge-toggle { display: none; }
  .viewtools { flex-wrap: wrap; }
  /* each card spans the screen; the fixed desktop min-widths would
     overflow a phone, so tracks share the card width equally instead —
     minmax(0, 1fr) equalizes because the container width is now fixed */
  .viewtools .vt-card { flex: 1 1 100%; grid-auto-columns: minmax(0, 1fr); }
  .viewtools .vt,
  .viewtools .vt-check { min-width: 0; padding: 0 2px; }
  .viewtools .vt-check { font-size: var(--fs-xs); gap: 4px; overflow: hidden; }
  /* the global 16px mobile input rule is anti-zoom for TEXT fields —
     on these checkboxes it only wastes segment width */
  .viewtools .vt-check input { font-size: var(--fs-xs); width: 14px; height: 14px; }
  /* input size comes from the uniform --mzoom scale; iOS focus-zoom
     is disabled by maximum-scale=1 in the viewport meta */
}

/* narrow phones: status + the live application/code segments matter
   most; the units badge and the view label (already shown by the
   viewport toolbar) go first */
@media (max-width: 560px) {
  #sb-view,
  .sb-left .sb-seg:nth-child(3) { display: none; }
}

/* show/hide password (2026-08-05). The input's type flips between
   "password" and "text", so every gate rule that matched on
   input[type="password"] also matches .pw-wrap input. */
.pw-wrap { position: relative; display: block; margin-top: 3px; }
.pw-wrap input { margin-top: 0; padding-right: 40px; }
.pw-eye {
  position: absolute; right: 5px; top: 0; bottom: 0;
  width: 30px; display: grid; place-items: center;
  padding: 0; border: none; background: none;
  color: var(--text-muted); cursor: pointer;
  border-radius: var(--radius-sm);
  transition: color var(--dur-fast) var(--ease-out);
}
.pw-eye:hover { color: var(--text); }
.pw-eye:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 1px; }
.pw-eye svg {
  width: 18px; height: 18px; fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
.pw-eye .pw-slash { display: none; }
.pw-eye[aria-pressed="true"] .pw-slash { display: block; }
