/* ============================================================================
   guidance.css — the "do not destroy your own archive" layer.
   Owner: GUIDANCE. Consumes ONLY the names in tokens.css.

   Components, all prefixed gd- :

     .gd-banner    persistent, collapsible info banner (mountGuidance)
     .gd-tip       the ONE shared hover/focus path tooltip (attachPathTooltip)
     .gd-helpview  the padding frame for the #/help route — see PAGE PADDING
     .gd-help      "How this works" panel + flow diagram (mountHelp / openHelp)
     .gd-rules     the every-login rules modal with the animated illustration
     .gd-alarm     the non-dismissible data-root alarm

   Shared pieces used by all of the above:

     .gd-srv       the "NA SERVERU (VPS)" chip. Never a path without one.
     .gd-srvcard   the full "this is the company server, reached with Remote
                   Desktop; the portal is a window onto it, not a copy" card.
     .gd-en        the courtesy English echo under a Croatian-pinned line.
     .gd-langpin   the note saying why one screen is always in Croatian.

   PAGE PADDING — THE CONTRACT LIVES IN app.css, NOT HERE.
   Every routed view is mounted inside <div class="view-pad">, and that wrapper
   is the only thing that pays for the page gutter. Nothing in this file adds
   page padding. The single exception is `.view > .gd-helpview`, which fires
   only when this screen is a DIRECT child of the scroll container — i.e. only
   when no wrapper has paid — so the help screen is correct under either
   arrangement and doubled under neither. See section 3.

   RULES OBSERVED THROUGHOUT
     · --accent is FILL-ONLY. Coloured TEXT uses --accent-text. The server chip
       is amber as a FILL with --accent-text on top, which is why it can be the
       loudest thing in a row of grey text without failing contrast.
     · Colour is never the only signal — every green/red state also carries an
       icon and words.
     · Light and dark both come from tokens; nothing is hard-coded except the
       transparent white/black used inside color-mix().
     · prefers-reduced-motion: tokens.css already clamps every animation to
       0.01ms with iteration-count 1. Every animation below is therefore
       authored so that its 100% keyframe equals the element's RESTING state —
       which makes the reduced-motion rendering a correct static frame rather
       than a broken one. Movement-only decoration is additionally suppressed.

   Z-INDEX MAP (the app's existing scale: modal scrim 250, boot splash 320,
   sr-only skip link 400, viewers 900/901, files context menu 900, files
   shortcut overlay 950)
     .gd-tip        990    above every view chrome, below our own overlays
     .gd-rules-scrim 1200  the login gate
     .gd-alarm      1400   nothing may ever cover this
   ========================================================================= */

/* ==========================================================================
   0. SHARED PIECES
   ========================================================================== */

/* Tone carriers. A component sets .gd-tone-ok / .gd-tone-no (or the modifier
   classes below) and everything inside reads --gd-tone / --gd-tone-soft. */
.gd-tone-ok {
  --gd-tone: var(--ok);
  --gd-tone-soft: var(--ok-soft);
}
.gd-tone-no {
  --gd-tone: var(--danger);
  --gd-tone-soft: var(--danger-soft);
}

/* Small round icon chip used by rules, banner and help. */
.gd-chip {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-pill);
  background: var(--gd-tone-soft, var(--surface-2));
  color: var(--gd-tone, var(--text-dim));
}
.gd-chip--lg { width: 34px; height: 34px; border-radius: var(--radius); }

/* Pill: icon + word. The word is what makes it colour-blind safe. */
.gd-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1-5);
  height: 22px;
  padding: 0 var(--sp-2) 0 var(--sp-1-5);
  border: 1px solid color-mix(in srgb, var(--gd-tone, var(--border-hi)) 42%, transparent);
  border-radius: var(--radius-pill);
  background: var(--gd-tone-soft, var(--surface-2));
  color: var(--gd-tone, var(--text-dim));
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: .02em;
  line-height: 1;
  white-space: nowrap;
}

/* A server path, everywhere it appears. Wraps at any character because these
   paths carry spaces AND are long; never let one push the layout sideways. */
.gd-path {
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.45;
  color: var(--text);
  overflow-wrap: anywhere;
  word-break: break-all;
  -webkit-user-select: text;
  user-select: text;
}
.gd-path--boxed {
  display: inline-block;
  padding: 3px var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}

/* --------------------------------------------------------------------------
   THE SERVER CHIP  —  "NA SERVERU (VPS)"

   A path on its own is not information to somebody who is not a computer
   person: C:\Daten\portal could be their own PC, it could be "the website".
   So no path is ever printed in this app without this chip beside it. It is
   deliberately the ONE amber thing in a row of neutral text — the eye finds
   it before the sentence is read, and the association path -> machine is made
   without reading at all.

   Amber here is a FILL under --accent-text, never amber text on the page
   background. role="img" + aria-label (set in JS) means a screen reader gets
   the whole sentence, not the abbreviation.
   -------------------------------------------------------------------------- */
.gd-srv {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1-5);
  flex: 0 0 auto;
  height: 22px;
  padding: 0 var(--sp-2) 0 var(--sp-1-5);
  border: 1px solid color-mix(in srgb, var(--accent) 50%, transparent);
  border-radius: var(--radius-sm);
  background: var(--accent-dim);
  color: var(--accent-text);
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .05em;
  line-height: 1;
  white-space: nowrap;
  vertical-align: middle;
}
.gd-srv svg { flex: 0 0 auto; }
.gd-srv--lg    { height: 26px; padding: 0 var(--sp-2-5) 0 var(--sp-2); font-size: 11.5px; }
.gd-srv--tight { height: 20px; padding: 0 var(--sp-1-5); font-size: 10px; letter-spacing: .04em; }

/* --------------------------------------------------------------------------
   THE SERVER CARD  —  the full "which machine is this?" answer

   Three things, in the order a person needs them:
     1. This is a folder on the COMPANY SERVER. Not your PC. Not the website.
     2. This is the path, and here is the machine you Remote-Desktop into.
     3. The portal shows that SAME folder — it is a window, not a copy.
   The third line is the one that prevents "which one is the real file?", and
   that question is how archives get duplicated and then half-deleted.
   -------------------------------------------------------------------------- */
.gd-srvcard {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border));
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent) 7%, var(--surface)) 0%,
    var(--surface) 68%);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.gd-srvcard__top {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--sp-3);
  align-items: start;
}
.gd-srvcard__ico {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius);
  background: var(--accent-dim);
  color: var(--accent-text);
}
.gd-srvcard__heads { min-width: 0; }
.gd-srvcard__t {
  font-size: 15px;
  font-weight: 650;
  line-height: 1.3;
  letter-spacing: -.01em;
  color: var(--text);
}
.gd-srvcard__b {
  margin-top: 2px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-dim);
}

.gd-srvcard__pathrow {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.gd-srvcard__label {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--text-mute);
}
.gd-srvcard__pathrow .gd-path { flex: 1 1 240px; min-width: 0; }

.gd-srvcard__facts {
  display: grid;
  gap: var(--sp-2);
  list-style: none;
  margin: 0;
  padding: 0;
}
.gd-srvcard__facts li {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  gap: var(--sp-2);
  align-items: start;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-dim);
}
.gd-srvcard__facts li svg { margin-top: 2px; color: var(--accent-text); }

/* Inside the rules gate and the alarm, where vertical space is the scarce
   thing and the card is support material rather than the subject. */
.gd-srvcard--compact { gap: var(--sp-2-5); padding: var(--sp-3); }
.gd-srvcard--compact .gd-srvcard__ico { width: 30px; height: 30px; }
.gd-srvcard--compact .gd-srvcard__t { font-size: 14px; }
.gd-srvcard--compact .gd-srvcard__b,
.gd-srvcard--compact .gd-srvcard__facts li { font-size: 12.5px; }

@media (max-width: 560px) {
  /* The chip drops under the heading rather than squeezing it to two words. */
  .gd-srvcard__top { grid-template-columns: auto minmax(0, 1fr); }
  .gd-srvcard__top > .gd-srv { grid-column: 2; justify-self: start; }
}

/* --------------------------------------------------------------------------
   THE COURTESY ECHO  —  .gd-en

   The rules gate and the data-root alarm are pinned to Croatian (see
   guidance.js). When the interface is in English, the English sentence is
   appended underneath in this style: smaller, dimmer, never bold, never
   italic, and always SECOND. Croatian is the text; this is the footnote.
   -------------------------------------------------------------------------- */
.gd-en {
  display: block;
  margin-top: 3px;
  font-family: var(--font-sans);
  font-size: .855em;
  font-weight: 400;
  font-style: normal;
  line-height: 1.45;
  letter-spacing: 0;
  color: var(--text-mute);
}
.gd-en--inline { display: inline; margin: 0 0 0 .45em; }

/* A dashed note, only ever shown when the interface is NOT Croatian, saying
   why this one screen is not in the language the rest of the app is in. */
.gd-langpin {
  flex: 0 0 auto;
  align-self: flex-start;
  max-width: 26ch;
  padding: var(--sp-1) var(--sp-2);
  border: 1px dashed var(--border-hi);
  border-radius: var(--radius-sm);
  font-size: 11px;
  line-height: 1.35;
  color: var(--text-mute);
}
.gd-langpin[hidden] { display: none; }

/* ==========================================================================
   1. THE BANNER  (mountGuidance)
   Sits at the top of the file browser. Collapses to one line that still
   carries the warning mark, so it can never be dismissed into invisibility.
   ========================================================================== */

.gd-banner {
  position: relative;
  border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--border));
  border-radius: var(--radius-lg);
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--accent) 8%, var(--surface)) 0%,
      var(--surface) 62%);
  box-shadow: var(--shadow-1), inset 0 1px 0 var(--hairline);
  overflow: hidden;
}

/* The amber spine: an unmissable edge that survives greyscale printing as a
   solid bar, and reads as "advisory", not "error". */
.gd-banner::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: var(--accent);
}

.gd-banner__bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-3) var(--sp-4) var(--sp-3) calc(var(--sp-4) + 4px);
  text-align: left;
  cursor: pointer;
  color: var(--text);
  border-radius: var(--radius-lg);
}
.gd-banner__bar:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }

.gd-banner__mark {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: var(--radius);
  background: var(--accent-dim);
  color: var(--accent-text);
}

.gd-banner__heads { min-width: 0; flex: 1 1 auto; }
.gd-banner__title {
  font-size: 15px;
  font-weight: 650;
  line-height: 1.3;
  letter-spacing: -.01em;
  color: var(--text);
}
/* The one-line summary. Visible only while collapsed — when the body is open
   it would just repeat what is directly underneath it. */
.gd-banner__lead {
  margin-top: 1px;
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.gd-banner:not(.is-collapsed) .gd-banner__lead { display: none; }

.gd-banner__caret {
  flex: 0 0 auto;
  color: var(--text-mute);
  transition: transform var(--dur) var(--ease), color var(--dur-fast) var(--ease);
}
.gd-banner.is-collapsed .gd-banner__caret { transform: rotate(-90deg); }
.gd-banner__bar:hover .gd-banner__caret { color: var(--text); }

.gd-banner__body {
  padding: 0 var(--sp-4) var(--sp-4) calc(var(--sp-4) + 4px);
  display: grid;
  gap: var(--sp-3);
  animation: gd-band-open var(--dur-slow) var(--ease) both;
}
@keyframes gd-band-open {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
.gd-banner.is-collapsed .gd-banner__body { display: none; }

/* the two verdict rows */
.gd-rulelist { display: grid; gap: var(--sp-2); }
.gd-rule {
  display: grid;
  grid-template-columns: 26px 1fr;
  align-items: start;
  gap: var(--sp-3);
  padding: var(--sp-2-5) var(--sp-3);
  border: 1px solid color-mix(in srgb, var(--gd-tone) 26%, var(--border));
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--gd-tone) 5%, var(--surface));
}
.gd-rule__label {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--gd-tone);
}
.gd-rule__body {
  margin-top: 1px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-dim);
}

/* the why + where strip */
.gd-why {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-2-5);
  align-items: start;
  padding: var(--sp-3);
  border-radius: var(--radius);
  background: var(--accent-dim);
  color: var(--text);
  font-size: 13.5px;
  line-height: 1.5;
}
.gd-why__ico { color: var(--accent-text); margin-top: 1px; }
.gd-why strong { font-weight: 650; }

.gd-where {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding-top: var(--sp-1);
}
.gd-where__label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1-5);
  font-size: 12.5px;
  color: var(--text-mute);
}
.gd-where .gd-path { flex: 1 1 220px; min-width: 0; }
/* Its own line under the path, inside the same flex row. */
.gd-where__note {
  flex: 1 0 100%;
  margin-top: var(--sp-1);
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--text-mute);
}

.gd-copy {
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-dim);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease),
              transform var(--dur-press) var(--ease);
}
.gd-copy:hover { color: var(--text); border-color: var(--border-hi); background: var(--surface-2); }
.gd-copy:active { transform: scale(.92); }
.gd-copy.is-done { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 50%, transparent); }

.gd-banner__foot {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

/* Narrow: the bar keeps its warning mark, the lead line is dropped rather
   than truncated to two useless words. */
@media (max-width: 560px) {
  .gd-banner__lead { display: none !important; }
  .gd-rule { grid-template-columns: 22px 1fr; gap: var(--sp-2); }
  .gd-chip { width: 22px; height: 22px; }
}

/* ==========================================================================
   2. THE SHARED PATH TOOLTIP  (attachPathTooltip)
   ONE element for the whole app. Positioned by JS in viewport coordinates.
   ========================================================================== */

.gd-tip {
  position: fixed;
  z-index: 990;
  top: 0;
  left: 0;
  width: max-content;
  max-width: min(360px, calc(100vw - 24px));
  padding: var(--sp-3);
  border: 1px solid var(--border-hi);
  border-radius: var(--radius);
  background: var(--bg-elev);
  color: var(--text);
  box-shadow: var(--shadow-2), inset 0 1px 0 var(--hairline);
  /* JS writes these three. The defaults keep the element sane if it is ever
     painted before the first measurement. */
  transform-origin: var(--gd-tip-ox, 50%) var(--gd-tip-oy, 0%);
  /* Resting state = fully shown, so a reduced-motion render (which collapses
     the entrance animation to its 100% frame) lands exactly here. */
  opacity: 1;
}
.gd-tip[hidden] { display: none; }

.gd-tip__arrow {
  position: absolute;
  left: var(--gd-tip-ax, 50%);
  width: 10px;
  height: 10px;
  margin-left: -5px;
  border: 1px solid var(--border-hi);
  background: var(--bg-elev);
  transform: rotate(45deg);
}
/* Tooltip below the anchor -> arrow points up, out of the top edge. */
.gd-tip.is-below .gd-tip__arrow { top: -6px;    border-right: 0; border-bottom: 0; }
.gd-tip.is-above .gd-tip__arrow { bottom: -6px; border-left: 0;  border-top: 0; }

.gd-tip__name {
  display: block;
  font-size: 13px;
  font-weight: 650;
  line-height: 1.35;
  letter-spacing: -.005em;
  color: var(--text);
  overflow-wrap: anywhere;
}

/* The chip sits where the old plain "On the server" label did — same slot,
   but it now names the MACHINE rather than describing the string underneath.
   `display: flex` (not the chip's default inline-flex) so it is a block-level
   box on its own line: an atomic inline here would have its vertical margin
   folded into a line box and sit a couple of pixels off. */
.gd-tip > .gd-srv {
  display: flex;
  width: max-content;
  max-width: 100%;
  margin-top: var(--sp-2);
}

/* One sentence: which machine this is, and that the portal is showing that
   same file rather than a copy of it. */
.gd-tip__note {
  margin-top: var(--sp-2);
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-dim);
}

.gd-tip__pathrow {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  margin-top: var(--sp-1-5);
  padding: var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.gd-tip__pathrow .gd-path { flex: 1 1 auto; min-width: 0; font-size: 12px; }
.gd-tip__pathrow .gd-copy { width: 24px; height: 24px; }

.gd-tip__warn {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-2);
  align-items: start;
  margin-top: var(--sp-2-5);
  padding-top: var(--sp-2-5);
  border-top: 1px solid var(--border);
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-dim);
}
.gd-tip__warn svg { color: var(--accent-text); margin-top: 1px; }

/* entrance / exit — a short spring out of the anchor edge */
.gd-tip.is-in  { animation: gd-tip-in  210ms var(--ease-spring) both; }
.gd-tip.is-out { animation: gd-tip-out 110ms var(--ease-in) both; }

@keyframes gd-tip-in {
  from { opacity: 0; transform: scale(.9) translateY(var(--gd-tip-dy, 6px)); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}
@keyframes gd-tip-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: scale(.97) translateY(calc(var(--gd-tip-dy, 6px) * .4)); }
}

/* Fade only, no travel. (tokens.css also clamps the duration to ~0, so this
   is belt and braces — but it keeps the intent explicit and readable.) */
@media (prefers-reduced-motion: reduce) {
  .gd-tip.is-in  { animation-name: gd-fade-in; }
  .gd-tip.is-out { animation-name: gd-fade-out; }
}
@keyframes gd-fade-in  { from { opacity: 0; } to { opacity: 1; } }
@keyframes gd-fade-out { from { opacity: 1; } to { opacity: 0; } }

/* ==========================================================================
   3. "HOW THIS WORKS"  (mountHelp / openHelp)
   ========================================================================== */

/* --------------------------------------------------------------------------
   THE FRAME  —  .gd-helpview, and the page-padding contract

   app.css owns exactly one rule about page gutters: every routed view is
   mounted inside a <div class="view-pad"> and THAT wrapper is the only thing
   that pays for them. mountHelp() therefore adds no page padding: .gd-helpview
   is a plain block, and .gd-help below has no padding of its own either.

   The one rule that DOES pay is scoped to `.view > .gd-helpview` — i.e. it
   fires only if this frame is ever a DIRECT child of the scroll container,
   which is the arrangement in which nothing above it has paid. Under the
   current shell that selector never matches (there is always a .view-pad in
   between), so there is no double gutter; if the shell ever mounts a view
   unwrapped again, the screen still comes up correctly padded instead of
   going flush against the sidebar with its <h2> under the top bar. Both
   arrangements are right, neither has to know about the other.

   The bottom is never claimed here: .view pays that, once, for wrapped and
   unwrapped views alike. The only thing added is a little air after the last
   card so the recap does not sit on the fold.
   -------------------------------------------------------------------------- */
.gd-helpview {
  display: block;
  min-width: 0;
}
.view > .gd-helpview {
  padding-top: var(--view-pad-t, var(--sp-8));
  padding-right: var(--gutter-r);
  padding-left: var(--gutter-l);
}

.gd-help {
  display: grid;
  gap: var(--sp-5);
  max-width: 880px;
  min-width: 0;
}
.gd-help__head { display: grid; gap: var(--sp-1); }
.gd-help__title { font-size: 22px; font-weight: 650; line-height: 1.2; letter-spacing: -.02em; }
.gd-help__intro { font-size: 14px; line-height: 1.55; color: var(--text-dim); }
/* The heading block and the server card belong together — the card answers
   the question the title raises — so they sit closer than the 20px rhythm. */
.gd-help__head + .gd-srvcard { margin-top: calc(var(--sp-3) - var(--sp-5)); }

/* --- the flow: PC -> portal -> server folder -> nightly backup ----------- */
.gd-flow {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: stretch;
  gap: var(--sp-2);
  counter-reset: gd-step;
}

/* grid-auto-rows rather than a fixed three-row template: step 3 carries the
   server chip as a fourth child, and a template that hands `1fr` to row three
   would have stretched the CHIP instead of the body text. .gd-flow already
   stretches the cards to equal height; align-content keeps the contents at
   the top of whatever height that turns out to be. */
.gd-step {
  position: relative;
  display: grid;
  grid-auto-rows: max-content;
  align-content: start;
  gap: var(--sp-2);
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: inset 0 1px 0 var(--hairline);
}
/* The chip belongs to the title above it, not to the body below. */
.gd-step > .gd-srv { justify-self: start; margin-top: calc(var(--sp-1) * -1); }
/* the connector arrow between cards */
.gd-step + .gd-step::before {
  content: "";
  position: absolute;
  top: 50%;
  left: calc(var(--sp-2) * -1 - 1px);
  width: calc(var(--sp-2) + 2px);
  height: 2px;
  background: color-mix(in srgb, var(--border-hi) 70%, transparent);
  transform: translateY(-50%);
}
.gd-step + .gd-step::after {
  content: "";
  position: absolute;
  top: 50%;
  left: -5px;
  width: 6px;
  height: 6px;
  border-top: 2px solid color-mix(in srgb, var(--border-hi) 70%, transparent);
  border-right: 2px solid color-mix(in srgb, var(--border-hi) 70%, transparent);
  transform: translateY(-50%) rotate(45deg);
}

.gd-step__ico {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--text-dim);
}
.gd-step--key .gd-step__ico { background: var(--accent-dim); color: var(--accent-text); }
.gd-step--safe .gd-step__ico { background: var(--ok-soft); color: var(--ok); }

.gd-step__n {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--text-mute);
}
.gd-step__t { font-size: 14px; font-weight: 650; line-height: 1.3; }
.gd-step__b { font-size: 13px; line-height: 1.5; color: var(--text-dim); }
.gd-step__b .gd-path { font-size: 11.5px; }

@media (max-width: 800px) {
  .gd-flow { grid-template-columns: 1fr; gap: var(--sp-4); }
  /* connector rotates to point downwards */
  .gd-step + .gd-step::before {
    top: calc(var(--sp-4) * -1 - 1px);
    left: 30px;
    width: 2px;
    height: calc(var(--sp-4) + 2px);
    transform: none;
  }
  .gd-step + .gd-step::after {
    top: -6px;
    left: 27px;
    transform: rotate(135deg);
  }
}

.gd-punch {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-3);
  align-items: start;
  padding: var(--sp-4);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border));
  border-radius: var(--radius-lg);
  background: var(--accent-dim);
  font-size: 14px;
  line-height: 1.55;
  color: var(--text);
}
.gd-punch__ico { color: var(--accent-text); }

/* the do / don't recap reuses .gd-rulelist */
.gd-help__recap { display: grid; gap: var(--sp-2-5); }
.gd-help__recapT { font-size: 13px; font-weight: 650; color: var(--text); }

/* --- the same panel inside a dismissible overlay ------------------------- */
.gd-help-scrim {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: max(var(--sp-4), env(safe-area-inset-top)) max(var(--sp-4), env(safe-area-inset-right))
           max(var(--sp-4), env(safe-area-inset-bottom)) max(var(--sp-4), env(safe-area-inset-left));
  background: var(--scrim);
  animation: gd-fade-in var(--dur-fast) var(--ease) both;
}
@supports (backdrop-filter: blur(1px)) { .gd-help-scrim { backdrop-filter: blur(4px); } }
.gd-help-scrim.is-closing { animation: gd-fade-out var(--dur-fast) var(--ease-in) both; }

.gd-help-card {
  position: relative;
  width: min(880px, 100%);
  max-height: calc(100dvh - 2 * var(--sp-6));
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  padding: var(--sp-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-elev);
  box-shadow: var(--shadow-2), inset 0 1px 0 var(--hairline);
  animation: gd-card-in var(--dur) var(--ease-spring) both;
}
@keyframes gd-card-in {
  from { opacity: 0; transform: translateY(10px) scale(.98); }
  to   { opacity: 1; transform: none; }
}
.gd-help-card .gd-help { max-width: none; }

.gd-help-card__x {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius);
  color: var(--text-mute);
  cursor: pointer;
}
.gd-help-card__x:hover { color: var(--text); background: var(--surface-2); }

@media (max-width: 560px) {
  .gd-help-card { padding: var(--sp-5) var(--sp-4) var(--sp-6); }
}

/* ==========================================================================
   4. THE RULES MODAL  (mountRulesModal) — seen on every single login
   ========================================================================== */

.gd-rules-scrim {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: grid;
  place-items: center;
  padding: max(var(--sp-4), env(safe-area-inset-top)) max(var(--sp-4), env(safe-area-inset-right))
           max(var(--sp-4), env(safe-area-inset-bottom)) max(var(--sp-4), env(safe-area-inset-left));
  background: color-mix(in srgb, var(--scrim) 88%, var(--bg));
  animation: gd-fade-in var(--dur) var(--ease) both;
}
@supports (backdrop-filter: blur(1px)) {
  .gd-rules-scrim { backdrop-filter: blur(10px) saturate(.9); }
}
.gd-rules-scrim.is-closing { animation: gd-fade-out var(--dur-fast) var(--ease-in) both; }

.gd-rules {
  display: flex;
  flex-direction: column;
  width: min(900px, 100%);
  max-height: calc(100dvh - 2 * var(--sp-5));
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-elev);
  box-shadow: var(--shadow-2), inset 0 1px 0 var(--hairline);
  overflow: hidden;
  animation: gd-card-in var(--dur-slow) var(--ease-spring) both;
}
.gd-rules-scrim.is-closing .gd-rules {
  animation: gd-card-out var(--dur-fast) var(--ease-in) both;
}
@keyframes gd-card-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(6px) scale(.985); }
}

.gd-rules__head {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  /* the "always Croatian" note is the third item and wraps below on a phone */
  flex-wrap: wrap;
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  border-bottom: 1px solid var(--border);
}
.gd-rules__mark {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: var(--radius);
  background: var(--accent-dim);
  color: var(--accent-text);
}
.gd-rules__heads { min-width: 0; }
.gd-rules__title {
  font-size: 19px;
  font-weight: 650;
  line-height: 1.25;
  letter-spacing: -.02em;
  color: var(--text);
}
.gd-rules__sub {
  margin-top: 2px;
  font-size: 13px;
  line-height: 1.4;
  color: var(--text-mute);
}

.gd-rules__heads { flex: 1 1 260px; }
/* The Croatian headline stays the headline; the English echo is a footnote. */
.gd-rules__title .gd-en { font-size: 14px; letter-spacing: 0; }
.gd-rules__sub .gd-en { font-size: 12px; }

.gd-rules__body {
  padding: var(--sp-5);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}
/* "Which machine" comes first, then the four rules. */
.gd-rules__body > .gd-srvcard { margin-bottom: var(--sp-4); }

.gd-beats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
}

/* ---------------------------- one beat card ----------------------------- */
.gd-beat {
  position: relative;
  display: grid;
  gap: var(--sp-2-5);
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: hidden;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.gd-beat.is-live {
  border-color: color-mix(in srgb, var(--gd-tone) 55%, var(--border));
  box-shadow: 0 0 0 3px var(--gd-tone-soft);
}

/* the 3.6s sweep across the top of whichever card is speaking */
.gd-beat__progress {
  position: absolute;
  inset: 0 auto auto 0;
  height: 2px;
  width: 100%;
  transform-origin: 0 50%;
  transform: scaleX(0);
  background: var(--gd-tone);
}
.gd-beat.is-live .gd-beat__progress { animation: gd-sweep 3600ms linear infinite; }
@keyframes gd-sweep { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.gd-beat__scene {
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface-2) 70%, transparent);
  overflow: hidden;
}
/* The height is CAPPED, not left to the 220:132 aspect ratio. Four scenes at
   their natural height push the fourth caption below the fold on a 900px-tall
   laptop, and this screen is only worth anything if all four rules are read.
   The viewBox letterboxes inside the box (preserveAspectRatio defaults to
   xMidYMid meet), so nothing is ever cropped. */
.gd-beat__scene svg {
  display: block;
  width: 100%;
  height: clamp(126px, 21vh, 196px);
}

.gd-beat__t {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  font-size: 14.5px;
  font-weight: 650;
  line-height: 1.3;
  color: var(--text);
}
/* The Croatian title plus (in an English UI) its echo underneath. */
.gd-beat__t > [data-gd] { min-width: 0; flex: 1 1 auto; }
.gd-beat__t .gd-en { font-size: 12px; }
.gd-beat__b {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-dim);
}
.gd-beat__b .gd-en { font-size: 11.5px; }

/* ------------------------- the illustration ----------------------------- */
/* Palette for the drawing. Everything resolves from tokens so the scenes are
   correct in both themes without a second set of rules. */
.gd-scene {
  --gd-line: var(--border-hi);
  --gd-fill: var(--surface);
  --gd-ink: var(--text-dim);
  --gd-dim: var(--text-mute);
  /* Mixed toward --bg, not toward --text: beat 4's window has to read as
     "went dark and died" in BOTH themes. Mixing toward text made it lighter
     than its own scene panel in dark mode, which said "highlighted". */
  --gd-dead: color-mix(in srgb, var(--text-mute) 22%, var(--bg));
  --gd-dead-line: var(--text-mute);
  --gd-cursor: var(--text);
  --gd-cursor-edge: var(--surface);
}
.gd-scene .gd-win     { fill: var(--gd-fill); stroke: var(--gd-line); stroke-width: 1.4; }
.gd-scene .gd-winbar  { stroke: var(--gd-line); stroke-width: 1.2; }
.gd-scene .gd-dot     { fill: var(--gd-dim); }
.gd-scene .gd-label   { font-family: var(--font-mono); font-size: 8.5px; fill: var(--gd-ink); }
.gd-scene .gd-outside { fill: none; stroke: var(--gd-dim); stroke-width: 1.2;
                        stroke-dasharray: 4 4; opacity: .65; }
.gd-scene .gd-folder  { fill: color-mix(in srgb, var(--accent) 22%, var(--surface));
                        stroke: var(--ft-dir-c); stroke-width: 1.4;
                        stroke-linejoin: round; }
.gd-scene .gd-sheet   { fill: var(--surface); stroke: var(--gd-line); stroke-width: 1.4;
                        stroke-linejoin: round; }
.gd-scene .gd-sheet-l { stroke: var(--gd-dim); stroke-width: 1.2; stroke-linecap: round; }
.gd-scene .gd-cursor  { fill: var(--gd-cursor); stroke: var(--gd-cursor-edge); stroke-width: 1.4;
                        stroke-linejoin: round; }
.gd-scene .gd-ghost   { fill: none; stroke: var(--gd-dim); stroke-width: 1.2; stroke-dasharray: 3 3; }
.gd-scene .gd-plus    { stroke: var(--ft-dir-c); stroke-width: 1.6; stroke-linecap: round; }
/* the rename field the cursor types into in beat 4 */
.gd-scene .gd-editbox { fill: var(--surface); stroke: var(--accent); stroke-width: 1.4; }

/* Opaque, not the rgba --*-soft tokens: these sit on the scene's own tinted
   panel, and a translucent fill there loses the contrast that makes the tick
   and the cross readable at 26px. */
.gd-scene .gd-badge-bg   { fill: color-mix(in srgb, var(--ok) 18%, var(--surface));
                           stroke: var(--ok); stroke-width: 1.4; }
.gd-scene .gd-badge-mark { stroke: var(--ok); stroke-width: 2; stroke-linecap: round;
                           stroke-linejoin: round; fill: none; }
.gd-scene .gd-badge--no .gd-badge-bg   { fill: color-mix(in srgb, var(--danger) 18%, var(--surface));
                                         stroke: var(--danger); }
.gd-scene .gd-badge--no .gd-badge-mark { stroke: var(--danger); }

.gd-scene .gd-crack { stroke: var(--danger); stroke-width: 2.4; fill: none;
                      stroke-linecap: round; stroke-linejoin: round; }
.gd-scene .gd-dead  { fill: var(--gd-dead); stroke: var(--gd-dead-line); stroke-width: 1.4; }

/* Elements whose RESTING state is the end of the story. Animations below run
   only while the card is live, and every one of them ends at `none` / the
   resting value — so the reduced-motion (0.01ms) render is the correct final
   frame, not a half-drawn one. */
.gd-a { will-change: transform, opacity; }

/* --- beat 1: drag a file in ------------------------------------------- */
.gd-beat.is-live .gd-a-in     { animation: gd-in     3600ms var(--ease) infinite both; }
/* --- beat 2: create a folder ----------------------------------------- */
.gd-beat.is-live .gd-a-cur2   { animation: gd-cur2   3600ms var(--ease) infinite both; }
.gd-beat.is-live .gd-a-pop    { animation: gd-pop    3600ms var(--ease-spring) infinite both; }
.gd-beat.is-live .gd-a-plus   { animation: gd-plusout 3600ms var(--ease) infinite both; }
/* --- beat 3: drag a folder out --------------------------------------- */
.gd-beat.is-live .gd-a-out    { animation: gd-out    3600ms var(--ease) infinite both; }
.gd-beat.is-live .gd-a-grey   { animation: gd-grey   3600ms linear infinite both; }
.gd-beat.is-live .gd-a-hole   { animation: gd-late   3600ms var(--ease) infinite both; }
/* --- beat 4: rename the folder itself -------------------------------- */
.gd-beat.is-live .gd-a-cur4   { animation: gd-cur4   3600ms var(--ease) infinite both; }
.gd-beat.is-live .gd-a-oldlbl { animation: gd-oldout 3600ms var(--ease) infinite both; }
.gd-beat.is-live .gd-a-newlbl { animation: gd-newin  3600ms var(--ease) infinite both; }
.gd-beat.is-live .gd-a-die    { animation: gd-die    3600ms var(--ease) infinite both; }
.gd-beat.is-live .gd-a-crack  { animation: gd-crackin 3600ms var(--ease) infinite both; }
.gd-beat.is-live .gd-a-dim    { animation: gd-dimout 3600ms var(--ease) infinite both; }
/* the verdict badge, shared by all four */
.gd-beat.is-live .gd-a-badge  { animation: gd-badge  3600ms var(--ease-spring) infinite both; }

/* Offsets are in the scene's own user units (viewBox 0 0 220 132) — they put
   the moving piece inside the dashed "outside the portal" box at 0% and at
   its resting place from ~50% on. */
@keyframes gd-in {
  0%, 6%   { transform: translate(126px, -10px); }
  46%, 100% { transform: none; }
}
@keyframes gd-cur2 {
  0%, 6%   { transform: translate(70px, 22px); }
  44%      { transform: translate(0, 3px); }
  50%, 100% { transform: none; }
}
@keyframes gd-pop {
  0%, 44%  { transform: scale(.2); opacity: 0; }
  62%      { transform: scale(1.06); opacity: 1; }
  70%, 100% { transform: none; opacity: 1; }
}
@keyframes gd-plusout {
  0%, 62%  { opacity: 1; }
  78%, 100% { opacity: 0; }
}
@keyframes gd-out {
  0%, 8%   { transform: translate(-124px, 6px); }
  52%, 100% { transform: none; }
}
@keyframes gd-grey {
  0%, 34%  { opacity: 1; }
  62%, 100% { opacity: .48; }
}
@keyframes gd-late {
  0%, 30%  { opacity: 0; }
  56%, 100% { opacity: 1; }
}
@keyframes gd-cur4 {
  0%, 6%   { transform: translate(52px, 52px); }
  34%      { transform: translate(0, 2px); }
  40%, 100% { transform: none; }
}
@keyframes gd-oldout {
  0%, 34%  { opacity: 1; }
  42%, 100% { opacity: 0; }
}
@keyframes gd-newin {
  0%, 40%  { opacity: 0; }
  50%, 100% { opacity: 1; }
}
@keyframes gd-die {
  0%, 52%   { fill: var(--gd-fill);  stroke: var(--gd-line); }
  70%, 100% { fill: var(--gd-dead);  stroke: var(--gd-dead-line); }
}
@keyframes gd-crackin {
  0%, 56%  { opacity: 0; }
  72%, 100% { opacity: 1; }
}
@keyframes gd-dimout {
  0%, 52%   { opacity: 1; }
  70%, 100% { opacity: .28; }
}
@keyframes gd-badge {
  0%, 52%  { opacity: 0; transform: scale(.4); }
  66%      { opacity: 1; transform: scale(1.12); }
  74%, 100% { opacity: 1; transform: none; }
}

/* Scaling groups need an explicit origin. `transform-box: view-box` (the SVG
   default) makes transform-origin lengths resolve in the element's OWN user
   space — which, after the outer positional <g transform="translate(x,y)">, is
   measured from the piece's own top-left corner. So:
     · the badge is drawn centred on (0,0)  -> origin 0 0
     · the folder spans (0,0)-(38,26)       -> origin 19px 13px = its centre
   `fill-box` would be equivalent here but is the one of the two that older
   Safari gets wrong on <g>, so it is deliberately not used. */
.gd-a-badge { transform-box: view-box; transform-origin: 0 0; }
.gd-a-pop   { transform-box: view-box; transform-origin: 19px 13px; }

/* Reduced motion: no cycling, no sweep — four calm static frames. JS also
   stops the timer and marks every card live, so all four read at once. */
@media (prefers-reduced-motion: reduce) {
  .gd-beat__progress { display: none; }
  .gd-beat.is-live { box-shadow: none; border-color: var(--border); }
}

/* ------------------------------- footer --------------------------------- */
.gd-rules__foot {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border);
  background: var(--bg-elev);
}
/* The footer never scrolls, so this is the line that stays on screen for the
   whole time the gate is up: chip, then where, then the path itself. */
.gd-rules__where {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  /* The path drops to its own line before it ever squeezes the chip. */
  flex-wrap: wrap;
  flex: 1 1 260px;
  min-width: 0;
  font-size: 12.5px;
  color: var(--text-mute);
}
.gd-rules__where svg { flex: 0 0 auto; }
.gd-rules__where .gd-path { font-size: 12px; min-width: 0; }
.gd-rules__whereLbl { min-width: 0; }
.gd-rules__whereLbl .gd-en { display: inline; margin-left: .35em; }

.gd-rules__ack { flex: 0 0 auto; min-width: 168px; }
/* Two stacked lines inside one flex item, never two flex items side by side. */
.gd-rules__ack > span,
.gd-alarm__foot .btn > span { display: block; text-align: center; }
.btn .gd-en {
  display: block;
  margin-top: 0;
  font-size: 11.5px;
  font-weight: 500;
  color: inherit;
  opacity: .72;
}

.gd-rules__dots {
  display: flex;
  align-items: center;
  gap: var(--sp-1-5);
  justify-content: center;
  padding-top: var(--sp-4);
}
.gd-sdot {
  width: 26px;
  height: 22px;
  padding: 8px 5px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  background: none;
  border: 0;
}
.gd-sdot i {
  display: block;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--border-hi);
  opacity: .5;
  transition: opacity var(--dur) var(--ease), background var(--dur) var(--ease);
}
.gd-sdot[aria-current="true"] i { opacity: 1; background: var(--accent); }
.gd-sdot:hover i { opacity: .85; }

@media (max-width: 720px) {
  .gd-beats { grid-template-columns: 1fr; gap: var(--sp-3); }
  .gd-rules { max-height: calc(100dvh - var(--sp-4)); }
  .gd-rules__head { padding: var(--sp-4); }
  .gd-rules__body { padding: var(--sp-4); }
  .gd-rules__foot { padding: var(--sp-3) var(--sp-4) var(--sp-4); }
  .gd-rules__ack { width: 100%; height: 46px; }
  .gd-rules__title { font-size: 17px; }
}

/* ==========================================================================
   5. THE DATA-ROOT ALARM  (mountRootAlarm) — never dismissible
   ========================================================================== */

.gd-alarm {
  position: fixed;
  inset: 0;
  z-index: 1400;
  overflow-y: auto;
  overscroll-behavior: contain;
  background:
    radial-gradient(120% 80% at 50% -10%,
      color-mix(in srgb, var(--danger) 16%, transparent) 0%,
      transparent 62%),
    color-mix(in srgb, var(--danger) 6%, var(--bg));
  color: var(--text);
  animation: gd-fade-in var(--dur-slow) var(--ease) both;
}
/* solid danger rule along the top edge — reads as an alarm even in greyscale */
.gd-alarm::before {
  content: "";
  position: fixed;
  inset: 0 0 auto 0;
  height: 6px;
  background: var(--danger);
}

.gd-alarm__inner {
  width: min(760px, 100%);
  margin: 0 auto;
  padding: max(var(--sp-10), calc(env(safe-area-inset-top) + var(--sp-10)))
           max(var(--sp-5), env(safe-area-inset-right))
           max(var(--sp-10), calc(env(safe-area-inset-bottom) + var(--sp-8)))
           max(var(--sp-5), env(safe-area-inset-left));
  display: grid;
  gap: var(--sp-5);
}

.gd-alarm__topline {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.gd-alarm__mark {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-lg);
  background: var(--danger);
  color: var(--on-danger);
  box-shadow: var(--shadow-1);
}

/* THE FIRST AND LARGEST THING ON THE SCREEN. Sentence case on purpose:
   text-transform: uppercase makes screen readers spell short words out and
   makes long Croatian words unreadable. Size carries the emphasis. */
.gd-alarm__safe {
  font-size: clamp(30px, 6vw, 46px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -.03em;
  color: var(--text);
}
/* .855em of a 46px headline would be a second headline. The echo is a line of
   reading text, so it gets an absolute size instead of a relative one. */
.gd-alarm__safe .gd-en {
  margin-top: var(--sp-2);
  font-size: 17px;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -.005em;
}
.gd-alarm__safeSub {
  margin-top: var(--sp-2);
  font-size: 16px;
  line-height: 1.5;
  color: var(--text-dim);
  max-width: 56ch;
}
.gd-alarm__safeSub .gd-en { font-size: 13.5px; max-width: 60ch; }
.gd-alarm__reason .gd-en,
.gd-alarm__held .gd-en { font-size: 12.5px; }
/* .gd-alarm__contact puts the heading in a <strong>; the echo must not inherit
   the weight or it reads as a second heading. */
.gd-alarm__contact strong .gd-en { font-weight: 400; color: var(--text-mute); }

.gd-alarm__what {
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-4);
  border: 1px solid color-mix(in srgb, var(--danger) 40%, var(--border));
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.gd-alarm__whatT {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 15px;
  font-weight: 650;
  color: var(--danger);
}
.gd-alarm__reason { font-size: 14px; line-height: 1.55; color: var(--text); }
.gd-alarm__held   { font-size: 13.5px; line-height: 1.5; color: var(--text-dim);
                    font-variant-numeric: tabular-nums; }

.gd-alarm__cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
}
.gd-card {
  display: grid;
  gap: var(--sp-2-5);
  align-content: start;
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.gd-card__t {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 13.5px;
  font-weight: 650;
  color: var(--text);
}
.gd-card--do    .gd-card__t { color: var(--ok); }
.gd-card--dont  .gd-card__t { color: var(--danger); }

.gd-list { display: grid; gap: var(--sp-2); list-style: none; padding: 0; margin: 0; }
.gd-list li {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: var(--sp-2);
  align-items: start;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-dim);
}
.gd-list li svg { margin-top: 2px; }
.gd-card--do   .gd-list li svg { color: var(--ok); }
.gd-card--dont .gd-list li svg { color: var(--danger); }

.gd-alarm__contact {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-3);
  align-items: start;
  padding: var(--sp-4);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-dim);
}
.gd-alarm__contact strong { display: block; color: var(--text); font-weight: 650; margin-bottom: 2px; }

.gd-alarm__foot { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center; }

@media (max-width: 700px) {
  .gd-alarm__cols { grid-template-columns: 1fr; }
  .gd-alarm__inner { gap: var(--sp-4); }
  .gd-alarm__foot .btn { width: 100%; height: 46px; }
}

/* ==========================================================================
   6. FORCED COLOURS (Windows High Contrast)
   box-shadow and background-color are discarded there, so the "which card is
   speaking" and "which verdict" cues must survive as borders and text.
   ========================================================================== */
@media (forced-colors: active) {
  .gd-banner,
  .gd-beat,
  .gd-card,
  .gd-step,
  .gd-srvcard,
  .gd-alarm__what { border: 1px solid CanvasText; }
  .gd-beat.is-live { outline: 3px solid Highlight; outline-offset: 2px; }
  .gd-banner::before { background: CanvasText; }
  .gd-tip { border: 1px solid CanvasText; }
  .gd-sdot[aria-current="true"] i { background: Highlight; }
  /* The chip loses its amber fill here, so the border has to carry it — it is
     the only thing tying a path to the machine it is on. */
  .gd-srv { border: 1px solid CanvasText; color: CanvasText; }
  .gd-srvcard__ico { border: 1px solid CanvasText; }
  .gd-langpin { border-color: CanvasText; }
  .gd-en { color: GrayText; }
}
