/* ==========================================================================
   Cyberpunk Server Dashboard — styles.css
   Fully self-hosted: fonts are local (@font-face below), no CDNs.
   ========================================================================== */

@font-face {
  font-family: "JetBrains Mono Local";
  src: url("fonts/JetBrainsMono-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono Local";
  src: url("fonts/JetBrainsMono-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   DEFAULT PALETTE.
   Don't edit this block to recolour the dashboard — override it in theme.css,
   which loads after this file and survives updates to styles.css.

   Accent colours are declared as raw "R G B" channels so every border, glow
   and gradient in the stylesheet can derive its own alpha from them via
   rgb(var(--accent-rgb) / 0.35). Change the channel, and all 28 derived
   colours follow.
   -------------------------------------------------------------------------- */
:root {
  /* Accent channels — the four knobs that define the mood */
  --accent-rgb: 0 255 245;        /* primary   : cyan  */
  --accent-alt-rgb: 255 43 214;   /* secondary : magenta */
  --warn-rgb: 255 183 0;          /* warning   : amber */
  --ok-rgb: 57 255 136;           /* healthy   : green */
  --alert-rgb: 255 59 92;         /* critical  : red   */

  /* Solid forms of the above */
  --accent: rgb(var(--accent-rgb));
  --accent-alt: rgb(var(--accent-alt-rgb));
  --warn: rgb(var(--warn-rgb));
  --ok: rgb(var(--ok-rgb));
  --alert: rgb(var(--alert-rgb));

  /* Semantic aliases used throughout the stylesheet */
  --cyan: var(--accent);
  --cyan-dim: rgb(var(--accent-rgb) / 0.65);
  --magenta: var(--accent-alt);
  --amber: var(--warn);
  --green: var(--ok);
  --red: var(--alert);
  --border-cyan: rgb(var(--accent-rgb) / 0.35);

  /* Surfaces and text */
  --bg-void: #05070c;
  --bg-panel: rgba(10, 18, 28, 0.55);
  --bg-panel-solid: #0a1220;
  --text-primary: #d9fbff;
  --text-dim: #6f97a3;

  /* Effects */
  --scanline-opacity: 0.18;
  --noise-opacity: 0.035;
  --glow-strength: 1;

  --font-mono: "JetBrains Mono Local", ui-monospace, "Cascadia Mono", "Share Tech Mono",
    Consolas, "SFMono-Regular", "Courier New", monospace;
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg-void);
  color: var(--text-primary);
  font-family: var(--font-mono);
  overflow: hidden;
}

body {
  background:
    radial-gradient(ellipse at 20% 0%, rgb(var(--accent-rgb) / 0.07), transparent 55%),
    radial-gradient(ellipse at 85% 100%, rgb(var(--accent-alt-rgb) / 0.06), transparent 55%),
    var(--bg-void);
}

::selection {
  background: var(--cyan);
  color: #000;
}

/* ---------------- Overlays: scanlines / vignette / noise ---------------- */
.scanlines {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 100;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0) 0px,
    rgba(0, 0, 0, 0) 1px,
    rgb(0 0 0 / var(--scanline-opacity)) 2px,
    rgba(0, 0, 0, 0) 3px
  );
  mix-blend-mode: overlay;
  animation: scan-drift 9s linear infinite;
}
@keyframes scan-drift {
  0% { background-position-y: 0; }
  100% { background-position-y: 100px; }
}

.vignette {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 99;
  box-shadow: inset 0 0 220px rgba(0, 0, 0, 0.85);
}

.noise {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 98;
  opacity: var(--noise-opacity);
  background-image: radial-gradient(circle, #fff 1px, transparent 1px);
  background-size: 3px 3px;
}

/* -------------------------------- Layout -------------------------------- */
.hud {
  position: relative;
  z-index: 1;
  height: 100vh;
  padding: 14px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Angled "HUD frame" corners via clip-path, plus a glowing border */
.hex-frame {
  position: relative;
  background: var(--bg-panel);
  border: 1px solid var(--border-cyan);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  clip-path: polygon(
    16px 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%,
    0 100%, 0 16px
  );
  box-shadow:
    0 0 0 1px rgb(var(--accent-rgb) / 0.05) inset,
    0 0 18px rgb(var(--accent-rgb) / 0.06),
    0 4px 24px rgba(0, 0, 0, 0.4);
}
.hex-frame::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  clip-path: polygon(
    16px 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%,
    0 100%, 0 16px
  );
  border: 1px solid transparent;
  background:
    linear-gradient(135deg, rgb(var(--accent-rgb) / 0.5), transparent 30%, transparent 70%, rgb(var(--accent-alt-rgb) / 0.35))
    border-box;
  -webkit-mask:
    linear-gradient(#fff 0 0) padding-box,
    linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

/* ------------------------------- Header --------------------------------- */
.hud-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 26px 12px 18px;
  flex-shrink: 0;
  gap: 20px;
}

.header-left {
  flex: 0 0 auto;
  min-width: 0;
}
.alias {
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--cyan);
  text-shadow: 0 0 8px rgb(var(--accent-rgb) / 0.7), 0 0 22px rgb(var(--accent-rgb) / 0.35);
}
.hostname {
  font-size: 0.72rem;
  color: var(--text-dim);
  letter-spacing: 0.08em;
  margin-top: 2px;
}
.hostname span { color: var(--text-primary); }

.header-mid {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  justify-content: center;
}
/* A GRID, not a wrapping flex row. Wrapping produced a ragged, centred
   staircase (one spec on the first line, two on the next...); fixed column
   counts keep the block aligned at every width. Tracks are minmax(0, auto):
   they size to their content when there is room, and only the specs that
   actually overflow get truncated. */
.spec-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, auto));
  justify-content: center;
  align-items: center;
  gap: 2px clamp(16px, 2.4vw, 52px);
  width: 100%;
}
.spec {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
}
.spec-label {
  font-size: 0.6rem;
  color: var(--text-dim);
  letter-spacing: 0.15em;
}
.spec-value {
  font-size: 0.82rem;
  color: var(--text-primary);
  margin-top: 2px;
  letter-spacing: 0.03em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.empty-state {
  color: var(--text-dim);
  text-align: center;
  padding: 20px;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  width: 100%;
}

/* -------------------------------- Scrollbars ------------------------------ */
* {
  scrollbar-width: thin;
  scrollbar-color: rgb(var(--accent-rgb) / 0.4) transparent;
}
*::-webkit-scrollbar { width: 6px; height: 6px; }
*::-webkit-scrollbar-thumb {
  background: rgb(var(--accent-rgb) / 0.35);
  border-radius: 3px;
}

/* ==========================================================================
   HUB — everything below is specific to hubergine.

   The block above is phantop's shared foundation — fonts, palette, reset,
   CRT overlays, .hud, .hex-frame, the header, the empty state and the
   scrollbars — carried over unchanged except that the rules for elements
   hubergine does not have (the clock, the connection indicator, the panels)
   are deleted. Recolour in theme.css, never here.
   ========================================================================== */

/* Motion tokens from DESIGN.md §2. The foundation predates them and inlines
   its durations; everything below uses these instead. */
:root {
  --t-fast: 0.15s;
  --t-med: 0.3s;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Two specs here, not the foundation's four — and they anchor the right
   end of the header now that the clock is gone. Left centred they would read
   as off-centre against the much wider brand block opposite. */
.header-mid { justify-content: flex-end; }
.hud-header .spec-row {
  grid-template-columns: repeat(2, minmax(0, auto));
  /* Both of these are needed. flex-end on .header-mid only places the
     spec-row BOX; the foundation then gives that box width:100% and centres
     its own columns inside it, so the specs stay visually centred no matter
     what the flex parent says. Shrink the grid to its content and align it
     to the end, and it actually sits against the right edge. */
  justify-content: end;
  width: auto;
}

.accent { color: var(--cyan); }

/* ------------------------------ Back button ------------------------------
   Deliberately oversized: it is the ONLY way out of an embedded site, so it
   has to be findable without hunting. It is DISABLED on the menu rather than
   hidden — the header must not reflow at the moment it lights up, or the
   thing you are reaching for moves as you reach for it. */
.back-btn {
  flex: 0 0 auto;
  width: 58px;
  height: 58px;
  display: grid;
  place-items: center;
  padding: 0;
  font: inherit;
  color: var(--cyan);
  background: rgb(var(--accent-rgb) / 0.08);
  border: 1px solid rgb(var(--accent-rgb) / 0.45);
  border-radius: 3px;
  cursor: pointer;
  transition:
    opacity var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease),
    background var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease);
}
.back-glyph {
  /* Sized in px, not em: the button is a fixed square and the glyph has to
     stay centred in it at every breakpoint. line-height 1 drops the font's
     own leading, which would otherwise push it off centre vertically. */
  font-size: 30px;
  line-height: 1;
  display: block;
}
.back-btn:not(:disabled) {
  box-shadow: 0 0 14px rgb(var(--accent-rgb) / 0.28);
}
.back-btn:not(:disabled) .back-glyph {
  text-shadow: 0 0 6px rgb(var(--accent-rgb) / 0.75);
}
.back-btn:not(:disabled):hover {
  background: rgb(var(--accent-rgb) / 0.18);
  box-shadow: 0 0 24px rgb(var(--accent-rgb) / 0.45);
}
.back-btn:not(:disabled):active { transform: translateY(1px); }

/* Dimmed, not gone. Colour alone would not read as inert, so the border and
   the glow drop out too. */
.back-btn:disabled {
  opacity: 0.3;
  cursor: default;
  color: var(--text-dim);
  background: transparent;
  border-color: rgb(var(--accent-rgb) / 0.16);
  box-shadow: none;
}

/* --------------------------------- Stage ---------------------------------
   Menu and viewer are stacked in the same box and cross-faded. They are not
   swapped in and out of the DOM: the viewer holds a live iframe that has to
   survive a trip back to the menu with its socket still open, and pulling it
   out of the flow would tear that down. visibility (not display) is what
   makes the hidden layer inert without unloading it. */
.stage {
  position: relative;
  flex: 1;
  min-height: 0;
}
.view {
  position: absolute;
  inset: 0;
  transition:
    opacity var(--t-med) var(--ease),
    visibility var(--t-med) var(--ease);
}
.view-menu {
  opacity: 1;
  overflow-y: auto;
  overflow-x: hidden;
}
.view-site {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  display: flex;
}
.stage.viewing .view-menu {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
.stage.viewing .view-site {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* -------------------------------- Card grid -------------------------------
   Explicit column count, never auto-fill (DESIGN.md §8) — auto-fill packs
   and leaves a ragged last row. align-content: start is not optional: the
   stage is a fixed-height box, and auto rows in a fixed-height grid absorb
   the free space, which stretches three cards to fill the whole screen. */
.hub-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-content: start;
  gap: 14px;
  padding-bottom: 2px;
}

.site-card {
  /* .hex-frame supplies the surface, the border and the clipped corners. */
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 148px;
  padding: 14px 16px;
  font: inherit;
  color: var(--text-primary);
  text-align: left;
  cursor: pointer;
  container-type: inline-size;
  transition:
    transform var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease);
}
.site-card:hover {
  transform: translateY(-2px);
  border-color: rgb(var(--accent-rgb) / 0.6);
  box-shadow:
    0 0 0 1px rgb(var(--accent-rgb) / 0.08) inset,
    0 0 28px rgb(var(--accent-rgb) / 0.2),
    0 8px 28px rgba(0, 0, 0, 0.5);
}
.site-card:active { transform: translateY(0); }

.card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.card-name {
  font-size: clamp(0.95rem, 4.2cqw, 1.3rem);
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--cyan);
  text-shadow: 0 0 8px rgb(var(--accent-rgb) / 0.55);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.card-desc {
  /* A span, because the card is a <button> and a button may only contain
     phrasing content -- a <p> in there is invalid HTML. */
  display: block;
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--text-dim);
}
.card-foot {
  /* margin-top: auto pins the footer to the bottom, so targets line up across
     cards whose descriptions differ in length. */
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.62rem;
  letter-spacing: 0.15em;
  color: var(--text-dim);
}
.card-target {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.card-go {
  color: var(--cyan);
  flex: 0 0 auto;
}

/* -------------------------------- Status ---------------------------------
   Dot AND label, always (DESIGN.md §10) — a dot on its own is colour-only
   state. The dots do not pulse: the menu shows many at once, and the
   drifting scanlines are already this screen's one ambient animation (§9). */
.status {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  font-size: 0.6rem;
  letter-spacing: 0.15em;
  color: var(--text-dim);
  white-space: nowrap;
}
.status .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-dim);
}
.status.is-online { color: var(--green); }
.status.is-online .dot {
  background: var(--green);
  box-shadow: 0 0 8px var(--green);
}
.status.is-offline { color: var(--red); }
.status.is-offline .dot {
  background: var(--red);
  box-shadow: 0 0 8px var(--red);
}
.status.is-external { color: var(--amber); }
.status.is-external .dot {
  background: var(--amber);
  box-shadow: 0 0 8px var(--amber);
}

/* -------------------------------- Viewer --------------------------------- */
.frame-slot {
  flex: 1;
  min-width: 0;
  position: relative;
  overflow: hidden;
}
.site-frame {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  /* The embedded app paints its own background, but only once it has loaded —
     without this the slot flashes the page gradient first. */
  background: var(--bg-void);
}

/* Shown instead of the iframe when the target is known to be down, or when it
   never reported a load. It sits inside the clipped frame, which is why it is
   a plain absolutely-positioned child and not a portalled popover. */
.embed-error {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 14px;
  padding: 24px;
  text-align: center;
  background: var(--bg-void);
}
.embed-error[hidden] { display: none; }
.embed-title {
  font-size: 1rem;
  letter-spacing: 0.18em;
  color: var(--alert);
  text-shadow: 0 0 8px rgb(var(--alert-rgb) / 0.5);
}
.embed-msg {
  max-width: 52ch;
  font-size: 0.8rem;
  line-height: 1.6;
  color: var(--text-dim);
}
.embed-msg b {
  color: var(--text-primary);
  font-weight: 700;
}

/* Standard button, DESIGN.md §7. */
.btn {
  font-family: inherit;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cyan);
  background: rgb(var(--accent-rgb) / 0.08);
  border: 1px solid rgb(var(--accent-rgb) / 0.45);
  border-radius: 3px;
  padding: 8px 16px;
  cursor: pointer;
  transition:
    background var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease);
}
.btn:hover {
  background: rgb(var(--accent-rgb) / 0.16);
  box-shadow: 0 0 12px rgb(var(--accent-rgb) / 0.35);
}
.btn:active { transform: translateY(1px); }

/* ------------------------------ Focus ring -------------------------------
   Every interactive element needs a visible one (DESIGN.md §10). */
.back-btn:focus-visible,
.site-card:focus-visible,
.btn:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

/* ------------------------------- Responsive -------------------------------
   The header does NOT stack into a column the way phantop's does: the back
   arrow has to stay put and stay reachable at every width, and a header that
   reflows under it would move the exit. The specs drop out instead. */
@media (max-width: 1200px) {
  .hub-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .header-mid { display: none; }
}

@media (max-width: 700px) {
  .hub-grid { grid-template-columns: minmax(0, 1fr); }
  .hud { padding: 10px 12px; gap: 10px; }
  .hud-header { padding: 10px 14px; gap: 12px; }
  .back-btn { width: 48px; height: 48px; }
  .back-glyph { font-size: 25px; }
  .alias { font-size: 1.2rem; }
}


/* ==========================================================================
   AUTHENTICATION — only ever on screen when the auth profile is running.
   With auth off, none of this renders: the gate and the header user block
   both stay `hidden`.
   ========================================================================== */

/* Covers the viewport rather than sitting inside the stage: the site count
   and the menu must not be readable behind a sign-in prompt. Above the CRT
   overlays (z-index 98-100) so the scanlines do not sit on top of the form. */
.gate {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: var(--s-4, 16px);
  background:
    radial-gradient(ellipse at 50% 0%, rgb(var(--accent-rgb) / 0.08), transparent 60%),
    var(--bg-void);
}
.gate[hidden] { display: none; }

.gate-card {
  /* .hex-frame supplies the surface, border and clipped corners. */
  width: min(420px, 100%);
  padding: 28px 26px 22px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.gate-title {
  margin: 0;
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--cyan);
  text-shadow: 0 0 8px rgb(var(--accent-rgb) / 0.7), 0 0 22px rgb(var(--accent-rgb) / 0.35);
}
.gate-sub {
  margin: -12px 0 0;
  font-size: 0.7rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.gate-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.field-label {
  font-size: 0.62rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.input {
  font-family: inherit;
  font-size: 0.85rem;
  color: var(--text-primary);
  background: rgb(0 0 0 / 0.35);
  border: 1px solid rgb(var(--accent-rgb) / 0.25);
  border-radius: 2px;
  padding: 9px 11px;
  width: 100%;
  transition:
    border-color var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease);
}
.input:focus {
  outline: none;
  border-color: rgb(var(--accent-rgb) / 0.6);
  box-shadow: 0 0 10px rgb(var(--accent-rgb) / 0.25);
}
/* Chrome paints autofilled inputs a solid pale yellow, which on a near-black
   panel is unreadable. The inset shadow is the only way to override it. */
.input:-webkit-autofill,
.input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text-primary);
  box-shadow: 0 0 0 1000px #0a1220 inset;
}

/* The one loud button on this view (DESIGN.md §7: at most one per view). */
.btn-solid {
  background: var(--cyan);
  color: var(--bg-void);
  font-weight: 700;
  border-color: var(--cyan);
}
.btn-solid:hover {
  background: var(--cyan);
  box-shadow: 0 0 18px rgb(var(--accent-rgb) / 0.5);
}
.btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  box-shadow: none;
}

.form-error {
  margin: 0;
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--alert);
  text-shadow: 0 0 8px rgb(var(--alert-rgb) / 0.4);
}
.form-error[hidden] { display: none; }

.gate-foot {
  margin: 0;
  font-size: 0.62rem;
  line-height: 1.6;
  color: var(--text-dim);
  border-top: 1px solid rgb(var(--accent-rgb) / 0.14);
  padding-top: 14px;
}

/* ------------------------------ Header user ------------------------------ */
.header-user {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding-left: 16px;
  margin-left: 4px;
  border-left: 1px solid rgb(var(--accent-rgb) / 0.14);
}
.header-user[hidden] { display: none; }

.user-login {
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  color: var(--text-dim);
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.btn-sm {
  font-size: 0.6rem;
  padding: 5px 10px;
}

@media (max-width: 700px) {
  /* The address is the first thing to go: the sign-out button has to stay
     reachable, the label it sits next to does not. */
  .user-login { display: none; }
  .header-user { padding-left: 10px; }
  .gate-card { padding: 22px 18px 18px; }
}


/* ------------------------------ Modal + toast ----------------------------
   Both are direct children of <body>. Anything absolutely positioned inside
   a .hex-frame gets cut by its clip-path (DESIGN.md §7), which is why these
   are portalled rather than nested where they are used. */
.backdrop {
  position: fixed;
  inset: 0;
  z-index: 250;
  display: grid;
  place-items: center;
  padding: var(--s-4, 16px);
  background: rgb(0 0 0 / 0.7);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.backdrop[hidden] { display: none; }

.modal {
  width: min(460px, 100%);
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  padding: 24px 22px 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.modal-title {
  margin: 0;
  font-size: 1.15rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--cyan);
  text-shadow: 0 0 6px rgb(var(--accent-rgb) / 0.5);
}
.modal-text {
  margin: -8px 0 0;
  font-size: 0.72rem;
  line-height: 1.6;
  color: var(--text-dim);
}
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding-top: 4px;
}

.btn-ghost {
  background: transparent;
  border-color: rgb(var(--accent-rgb) / 0.25);
  color: var(--text-dim);
}
.btn-ghost:hover {
  background: rgb(var(--accent-rgb) / 0.08);
  color: var(--text-primary);
  box-shadow: none;
}

.field-hint {
  font-size: 0.6rem;
  line-height: 1.5;
  color: var(--text-dim);
}

.toasts {
  position: fixed;
  z-index: 300;
  bottom: 20px;
  right: 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: min(400px, calc(100vw - 40px));
  pointer-events: none;
}
.toast {
  padding: 10px 14px;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--text-primary);
  background: var(--bg-panel-solid);
  border-left: 3px solid var(--cyan);
  border-radius: 3px;
  box-shadow: 0 4px 24px rgb(0 0 0 / 0.5);
}
.toast-ok { border-left-color: var(--green); }
.toast-alert { border-left-color: var(--red); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
