/* Phosphor Instrument base — reset, type, shared components. Flat surfaces only. */

/* ═══ THE BREAKPOINT LADDER — one set, used by every dashboard stylesheet ═══════
   base.css · dashboard.css · shell-extras.css · server-features.css · account-security.css
   all key off these six widths and nothing else. Three responsive lanes landed independently
   and produced sixteen ad-hoc values (400/520/560/620/640/700/720/820/900/940/1200/1400/
   1560/1600/2000/2200) — several of which meant the same thing at slightly different places,
   so the same component changed shape twice on the way down. Consolidated to:

     <= 400px   xs   narrowest phone — shed the last optional topbar chip
     <= 640px   sm   PHONE. The rail becomes an off-canvas drawer, the topbar sheds chrome,
                     shared controls take their 44px/16px touch sizing, toasts dock bottom,
                     data tables stack into labelled cards. 641px is therefore the
                     phone/desktop-shell boundary: above it there is always a persistent rail.
     <= 900px   md   NARROW (tablet portrait, split-screen, small laptop window). Multi-column
                     PAGE layouts fold to one column and the deck tab bar becomes a scroll
                     strip. Tables are real tables again from 641 up — `break-word` lets them
                     shrink to fit, and `.u-scroll-x` catches anything that still cannot.
     <= 1279px  md+  COMPACT SHELL. The band between "narrow page" and "real desktop": page
                     layouts are already multi-column here, but the SHELL is not full size —
                     the labeled rail stays collapsed to its 58px icon form and .main keeps the
                     16px gutter. Both resume at 1280, on the same pixel on purpose: the rail
                     costs 132px and the gutter 16px, so splitting them made a 1px viewport
                     GROWTH cost 144px of content twice over. One mode switch, one step, and
                     content is otherwise monotonic across 320 -> 2560.
     >= 1200px  lg   WIDE. Prose-shaped pages (settings, billing, credits, support) gain a
                     second column instead of staying a 660-720px ribbon. Deliberately not the
                     same width as the shell step above: the page can afford two columns
                     (1094px of content at 1200) before the shell can afford 190px of rail.
     >= 1600px  xl   LARGE DESKTOP. Page measure opens to 1680px.
     >= 2000px  2xl  ULTRAWIDE. Page measure opens to 1880px.

   Two modifiers cut across the ladder and are NOT width steps:
     (hover: none)                a coarse pointer at any width — a tablet at 768 or a touch
                                  laptop at 1366 gets phone-grade hit targets without phone
                                  layout. Always paired as `(max-width: 640px), (hover: none)`.
     (min-height:) / (max-height:) viewport HEIGHT, for panes that size themselves to the
                                  screen (the deck console) and for landscape phones.

   Adding another width breakpoint is a design decision, not a local fix — the value belongs
   here, in this list, or it belongs in an existing step. ═══════════════════════════════════ */
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--fs-base);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  font-variant-numeric: tabular-nums;
}
a { color: var(--green-text); text-decoration: none; }
a:hover { color: var(--green); }
h1, h2, h3 { color: var(--head); letter-spacing: -0.01em; }
::selection { background: var(--green-soft); color: var(--head); }
/* 1.5px rounded to a single device pixel at DPR 1 — under the 2px minimum of SC 2.4.13. */
:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; border-radius: var(--r-xs); }
input { caret-color: var(--green); }

* { scrollbar-color: var(--line) transparent; scrollbar-width: thin; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-thumb { background: var(--line); border-radius: var(--r-xs); }
*::-webkit-scrollbar-thumb:hover { background: var(--line-2); }

/* static graph-paper dot grid (canvas painted by ui/topo.js mountDots) */
.bgdots { position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: 0.85; }
/* Live fleet backdrop (servers deck) — sits just above the dot grid, well below content. */
.fleet-topo { position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: 0.55; }

/* ── TRANSLATIONS ARE LONGER THAN ENGLISH ─────────────────────────────────────
   German and Russian commonly run 30–50% past the English they replace, and this design is
   monospace-forward and tight — every advance is the same width, so there is no proportional
   slack to absorb it. The rules in this block, and the two matching ones in dashboard.css and
   landing.css, are what let a longer word land without clipping.

   Two kinds of rule, and the difference matters because English rendering must not move:
     · a rule that CANNOT fire for text that already fits (a wrap allowance, an overflow-wrap)
       is unscoped — English never reaches it;
     · a rule that changes a measurement (a gap, a track width) is scoped to
       `:root:not([lang="en"])`, so it is off by construction in the source language.
   <html lang> is set pre-paint by js/theme-boot.js, so both are true from the first frame.

   `.lbl` was `white-space: nowrap` "so a two-word section label never breaks". That reasoning
   inverts once the label can be "// ZWEI-FAKTOR-AUTHENTIFIZIERUNG": an unbreakable label does
   not stay on one line, it leaves the card. It may break now — which for English text that
   fits its header is not a change, because a line that never had to wrap still does not. */
.lbl { font-size: var(--fs-xs); letter-spacing: 0.2em; text-transform: uppercase; color: var(--green-dim); font-weight: 600; overflow-wrap: anywhere; }
.lbl .live { color: var(--green); animation: pulse 1.8s infinite; text-shadow: var(--glow); }
.cap { font-size: var(--fs-xs); color: var(--faint); letter-spacing: 0.1em; text-transform: uppercase; }
/* `.cap` styles a 10px caption; as a link ("« billing") it is a navigation control, and at
   63x15.5px it missed even the 24x24 floor of SC 2.5.8 on a mouse. */
a.cap, a.linklike, .linklike { display: inline-flex; align-items: center; min-height: 24px; }
.muted { color: var(--muted); }
.subtle { font-size: var(--fs-sm); color: var(--muted); }
.coords { text-align: center; font-size: var(--fs-xs); letter-spacing: 0.22em; color: var(--faint); text-transform: uppercase; padding: var(--s2) 0 0; }

/* ── buttons ──────────────────────────────────────────────── */
.btn {
  font-family: var(--mono); font-size: var(--fs-md); cursor: pointer;
  padding: 9px 14px; border-radius: var(--r-sm); border: 1px solid var(--line-2);
  background: var(--surf-3); color: var(--text);
  transition: border-color .15s, color .15s, filter .15s;
  display: inline-flex; align-items: center; gap: 9px; letter-spacing: 0.02em;
}
.btn:hover { border-color: var(--green); color: var(--head); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; border-color: var(--line); }
.btn kbd {
  margin-left: auto; font-family: var(--mono); font-size: var(--fs-xs); color: var(--faint);
  border: 1px solid var(--line); border-radius: var(--r-xs); padding: 1px 5px; background: var(--surf-1);
}
.btn:hover kbd { color: var(--muted); }
.btn-main { background: var(--green); border-color: var(--green); color: var(--on-accent); font-weight: 700; justify-content: center; }
/* The shortcut hint inks the accent fill just like the label does, so it has to come from
   --on-accent — hardcoding the phosphor ink left it at 3.2:1 on the darker accents (nebula,
   synthwave, zomboid-red) while the label beside it stayed legible. 85% keeps it visibly
   quieter than the label and still clears AA at --fs-xs on every theme's fill. */
.btn-main kbd { border-color: color-mix(in srgb, var(--on-accent) 30%, transparent); color: color-mix(in srgb, var(--on-accent) 85%, transparent); background: transparent; }
.btn-main:hover { filter: brightness(1.12); color: var(--on-accent); }
/* A disabled primary must clearly recede — a dimmed green fill still reads as the CTA
   (e.g. Start while the server is already running, next to a usable outline Stop). Drop
   the fill entirely so the actionable buttons carry the emphasis. */
.btn-main:disabled { opacity: 1; background: var(--surf-2); border-color: var(--line); color: var(--faint); filter: none; }
.btn-main:disabled kbd { color: var(--faint); border-color: var(--line); }
.btn-danger { color: var(--red); background: transparent; border-color: var(--line); }
.btn-danger:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 6%, transparent); color: var(--red); }
.btn-ghost { background: transparent; border-color: var(--line); }
.btn-block { display: flex; width: 100%; justify-content: center; }
.btn-mini { padding: 5px 10px; font-size: var(--fs-sm); border-radius: var(--r-sm); }

/* ── vertical rhythm ──────────────────────────────────────────
   General fix for a recurring flaw: JS-injected card/panel bodies stack heterogeneous children
   (a heading, a `.cap` note, a `.sw` toggle, a button, a key-reveal) with NO spacing between them,
   because the leaf components carry no margins and the container isn't a flex `.stack`. Result:
   squished blocks, e.g. the Hytale account card. `.flow` is the "lobotomized owl" — it adds a
   consistent gap ONLY between adjacent siblings, so it composes onto existing markup without
   changing any element's display, adding edge margins, or double-spacing. Put it on any injected
   body that lays content out vertically. `--flow-gap` lets a tighter block opt down. */
.flow { --flow-gap: var(--s2); }
.flow > * + * { margin-top: var(--flow-gap); }
.flow-sm { --flow-gap: var(--s1); }

/* layout utilities — replace the most common repeating inline styles (identical declarations) */
.u-row { display: flex; gap: var(--s1); }
/* Horizontal scroll wells (the admin table cards). The scrollbar IS the affordance here, so it
   has to be visible against --surf-2 rather than the near-invisible global --line thumb, and the
   gesture must not chain into the page/back-navigation. */
.u-scroll-x { padding: 6px 12px; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-color: var(--line-2) transparent; }
.u-scroll-x::-webkit-scrollbar-thumb { background: var(--line-2); }
.u-mt-2 { margin-top: var(--s2); }
.u-mt-3 { margin-top: var(--s3); }

/* ── fields ───────────────────────────────────────────────── */
.field { margin-bottom: 16px; }
.field label { display: block; font-size: var(--fs-xs); color: var(--muted); margin-bottom: 6px; letter-spacing: 0.2em; text-transform: uppercase; }
.input-wrap {
  background: var(--inset); border: 1px solid var(--line); border-radius: var(--r-sm);
  transition: border-color .15s; display: flex; align-items: center;
}
.input-wrap:focus-within { border-color: var(--green); }
.input-wrap input, .input-wrap select {
  flex: 1; background: transparent; color: var(--head); border: none; outline: none;
  font-family: var(--mono); font-size: var(--fs-lg); padding: 11px 13px; width: 100%;
}
.input-wrap input::placeholder { color: var(--faint); }
.input-wrap select option { background: var(--surf-2); color: var(--text); }
/* Help text under a field is instruction, not decoration: --faint measures 4.09:1 on --surf-2
   at 11px (AA wants 4.5:1). --muted is 8.77:1 and still clearly quieter than --text. */
.hint { font-size: var(--fs-sm); color: var(--muted); margin-top: 6px; }
.hint.err { color: var(--red); }
.hint.ok { color: var(--green-text); }
input[type="range"] { accent-color: var(--green); }
input[type="checkbox"] { accent-color: var(--green); }

/* ── cards + target-lock ──────────────────────────────────── */
.card { position: relative; background: var(--surf-2); border: 1px solid var(--line); border-radius: var(--r); padding: var(--s3); }
.lock::before, .lock::after {
  content: ""; position: absolute; width: 13px; height: 13px; opacity: 0; pointer-events: none;
  transition: opacity .16s, transform .16s; z-index: 2;
}
.lock::before { top: -2px; left: -2px; border-top: 1.5px solid var(--green); border-left: 1.5px solid var(--green); border-top-left-radius: 3px; transform: translate(5px, 5px); }
.lock::after { bottom: -2px; right: -2px; border-bottom: 1.5px solid var(--green); border-right: 1.5px solid var(--green); border-bottom-right-radius: 3px; transform: translate(-5px, -5px); }
.lock:hover::before, .lock:hover::after { opacity: 1; transform: none; }
.lock:hover { border-color: var(--line-2); }

/* ── status badges — exact §4.2 status words as textContent ── */
.badge {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 11px; border-radius: var(--r-pill); font-size: var(--fs-xs);
  letter-spacing: 0.14em; text-transform: uppercase; border: 1px solid var(--line-2);
  background: var(--surf-1); color: var(--muted);
  /* LONGER TEXT (see the block at the top of this file). A status word sits beside a server's
     title in a fixed-width card: "stopped" is 7 characters, "gestoppt" is 8, "wird
     bereitgestellt" is 19. As a flex item the pill was the thing that gave way — measured, a
     German fleet card showed "GESTOPP / T" broken across two lines. It does not give way now;
     the TITLE beside it wraps instead, which it was already set up to do (`.server-title` has
     word-break). `break-word`, not `anywhere`, so the backstop only fires for a word that
     cannot fit a line on its own. */
  flex: none; overflow-wrap: break-word; text-align: center;
}
.badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.badge.running { color: var(--green); background: var(--green-soft); }
.badge.running::before { box-shadow: var(--glow); animation: pulse 1.8s infinite; }
.badge.online { color: var(--green); background: var(--green-soft); }
.badge.online::before { box-shadow: var(--glow); }
.badge.starting, .badge.provisioning, .badge.restarting, .badge.deleting, .badge.migrating { color: var(--amber); animation: pulse 1.6s infinite; }
.badge.stopping { color: var(--amber); }
.badge.stopped, .badge.offline { color: var(--muted); }
.badge.crashed, .badge.error, .badge.suspended { color: var(--red); }

/* ── kv rows / reveals / chips ────────────────────────────── */
.kv { display: flex; justify-content: space-between; align-items: center; gap: 10px; font-size: var(--fs-md); margin: 4px 0; }
.kv .k { color: var(--muted); }
.kv .v { color: var(--green-text); }
.cred, .key-reveal {
  color: var(--green-text); background: var(--inset); border: 1px solid var(--line-hair);
  border-radius: var(--r-sm); padding: 8px 10px; font-size: var(--fs-md); word-break: break-all; margin-top: 6px;
}
/* SFTP card: one row per field, each with its own copy button. Values exist to be read and copied,
   so they stay freely selectable — nothing here swallows a selection drag. The revealed password
   auto-hides on the countdown, or via the Hide button. */
.sftp-row { gap: 8px; }
.sftp-row .v { flex: 1; min-width: 0; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; user-select: text; cursor: text; }
.sftp-row .v.pass { font-family: var(--mono); word-break: break-all; }
.sftp-row .v.pass.masked { color: var(--muted); letter-spacing: .12em; user-select: none; }
.sftp-copy { font-family: var(--mono); font-size: var(--fs-2xs); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); background: var(--surf-3); border: 1px solid var(--line); border-radius: var(--r-xs); padding: 3px 8px; cursor: pointer; flex: none; transition: color .13s, border-color .13s; }
.sftp-copy:hover { color: var(--green); border-color: var(--green); }
.sftp-reveal-hint { margin-top: 8px; font-size: var(--fs-xs); color: var(--faint); letter-spacing: .02em; user-select: none; }
.mchip { display: inline-flex; gap: 6px; font-size: var(--fs-sm); color: var(--muted); border: 1px solid var(--line-hair); border-radius: var(--r-sm); padding: 3.5px 9px; background: var(--surf-1); white-space: nowrap; }
.mchip b { color: var(--text); font-weight: 500; }

.empty-state { text-align: center; padding: 48px 20px; color: var(--muted); }
.empty-state pre { color: var(--green-dim); font-size: var(--fs-md); line-height: 1.3; letter-spacing: 0.1em; }

/* ── toasts (flat, instant text) ──────────────────────────── */
/* pointer-events:none on the stack so the gaps between toasts never swallow a click on whatever
   is underneath (the topbar on desktop); the toasts themselves stay clickable-to-dismiss. */
/* top:16px put the stack ON the 52px topbar, so while any toast was up the ⌘K chip,
   notifications, credits and logout were all unclickable (and a focused one fully obscured —
   SC 2.4.11). 64px clears the topbar row and keeps the familiar top-right position. */
#toastContainer { position: fixed; top: 64px; right: 16px; z-index: 10000; display: flex; flex-direction: column; gap: 8px; max-width: min(360px, calc(100vw - 32px)); pointer-events: none; }
.rh-toast {
  background: var(--surf-2); border: 1px solid var(--line-2); border-radius: var(--r); padding: 11px 13px;
  cursor: pointer; animation: rise .3s cubic-bezier(.2,.7,.2,1) both; pointer-events: auto;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
}
.rh-toast--out { opacity: 0; transform: translateX(16px); transition: all .25s ease; }
.rh-toast--error { border-color: var(--red); }
.rh-toast--warn { border-color: var(--amber); }
.rh-toast-title { color: var(--green); font-weight: 600; font-size: var(--fs-sm); letter-spacing: 0.14em; text-transform: uppercase; }
.rh-toast--error .rh-toast-title { color: var(--red); }
.rh-toast--warn .rh-toast-title { color: var(--amber); }
.rh-toast-msg { color: var(--text); font-size: var(--fs-md); margin-top: 4px; white-space: pre-wrap; word-break: break-word; }

/* ── scanner wipe (route transition) ──────────────────────── */
.rh-wipe-line {
  position: fixed; left: 0; right: 0; top: -4px; height: 2px; z-index: 9500; pointer-events: none;
  background: var(--green); opacity: 0.85; box-shadow: var(--glow);
  animation: sweep .4s cubic-bezier(.3,.6,.3,1) forwards;
}
@keyframes sweep { to { transform: translateY(104vh); } }

/* ── modal ────────────────────────────────────────────────── */
.rh-modal-overlay { position: fixed; inset: 0; z-index: 10001; background: rgba(1, 4, 2, 0.72); display: flex; align-items: center; justify-content: center; padding: 20px; }
/* max-height:100% resolves against the overlay's content box (fixed inset:0 minus its padding),
   so a card taller than the screen scrolls inside itself instead of centring its Cancel/Confirm
   row off the bottom of the viewport — the failure mode at 400% zoom and in landscape. */
.rh-modal { background: var(--surf-2); border: 1px solid var(--line-2); border-radius: var(--r-md); padding: var(--s4); max-width: 460px; width: 100%; box-shadow: 0 24px 80px rgba(0, 0, 0, 0.7); max-height: 100%; overflow-y: auto; overscroll-behavior: contain; }
.rh-modal h3 { margin-top: 0; }
.rh-modal-body { color: var(--text); font-size: var(--fs-base); line-height: 1.55; }
.rh-modal-body:empty { display: none; }
.rh-modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; }

/* ── connection banner ────────────────────────────────────── */
#connBanner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 10002; background: var(--surf-1);
  color: var(--amber); text-align: center; padding: 8px; font-size: var(--fs-md);
  border-bottom: 1px solid var(--amber); display: none; letter-spacing: 0.08em;
}
#connBanner.show { display: block; }

/* ── ⌘K command palette ───────────────────────────────────── */
.pal-ov { position: fixed; inset: 0; z-index: 9800; background: rgba(1, 4, 2, 0.72); display: none; align-items: flex-start; justify-content: center; padding-top: 13vh; }
.pal-ov.open { display: flex; }
.pal { width: min(560px, 92vw); background: var(--surf-2); border: 1px solid var(--line-2); border-radius: var(--r-md); overflow: hidden; box-shadow: 0 24px 80px rgba(0, 0, 0, 0.7); }
.pal .inrow { display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--line); }
.pal .inrow .pr { color: var(--green); font-weight: 700; }
.pal input { flex: 1; background: transparent; border: 0; outline: 0; color: var(--head); font: inherit; font-size: var(--fs-lg); }
.pal input::placeholder { color: var(--faint); }
.pal .list { max-height: 296px; overflow: auto; padding: 6px; }
.pitem { display: flex; align-items: center; gap: 11px; padding: 9px 12px; border-radius: var(--r-sm); font-size: var(--fs-base); color: var(--text); cursor: pointer; }
.pitem .gl { color: var(--green); width: 14px; text-align: center; flex: none; }
.pitem .plabel { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pgroup { font-size: var(--fs-2xs); letter-spacing: 0.16em; text-transform: uppercase; color: var(--faint); padding: 10px 12px 4px; }
.pgroup:first-child { padding-top: 4px; }
.pitem kbd { margin-left: auto; font-size: var(--fs-xs); color: var(--faint); border: 1px solid var(--line); border-radius: var(--r-xs); padding: 1px 6px; }
.pitem.act, .pitem:hover { background: var(--green-soft); color: var(--head); }
.pal .pfoot { display: flex; gap: var(--s3); padding: 9px 16px; border-top: 1px solid var(--line-hair); font-size: var(--fs-xs); color: var(--faint); letter-spacing: 0.1em; }

/* ── shared components: coarse pointers & phones ───────────────────────────────
   One place for the two rules every shared primitive was missing: a 44x44 hit box
   (SC 2.5.5, and comfortably over the 24px SC 2.5.8 floor) and a >=16px control font
   so iOS Safari never force-zooms the page on focus. Geometry only — every colour still
   comes from the tokens, so all eight themes are untouched.
   `hover: none` catches tablets and touch laptops at any width; the width step catches
   phones. `sm` — the same width at which the rail becomes a drawer, so a viewport either has
   the phone shell AND phone-sized controls or neither, never one without the other. */
@media (max-width: 640px), (hover: none) {
  .btn { min-height: 44px; padding: 10px 14px; }
  .btn-mini { min-height: 44px; padding: 8px 12px; }
  .btn-block, .btn-main { justify-content: center; }
  .sftp-copy { min-height: 44px; min-width: 44px; justify-content: center; display: inline-flex; align-items: center; }
  /* inline text links used as controls ("« billing", "Open a ticket →") */
  a.cap, a.linklike, .linklike { min-height: 44px; }
  /* 16px is the iOS/iPadOS Safari auto-zoom threshold: below it, focusing a field zooms the
     whole page and leaves it pannable. 16px x 1.55 line-height + 10px padding = a 44px control.
     Keyed on `hover: none` too, so a tablet gets it at 768px width as well. */
  .input-wrap input, .input-wrap select { font-size: 16px; padding: 10px 13px; min-height: 44px; }
  .field { margin-bottom: var(--s3); }
}

/* `sm` — phone shell only (a coarse-pointer tablet keeps the desktop toast/modal placement). */
@media (max-width: 640px) {
  /* toasts must never sit on the topbar: on a phone the stack docks to the bottom instead. */
  #toastContainer {
    top: auto; bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    left: 12px; right: 12px; max-width: none;
  }
  .rh-toast--out { transform: translateY(16px); }
  /* a confirm dialog on a phone: full-bleed card, stacked full-width actions, primary on top */
  .rh-modal-overlay { padding: 12px; }
  .rh-modal { padding: var(--s3); }
  .rh-modal-actions { flex-direction: column-reverse; gap: var(--s1); margin-top: var(--s3); }
  .rh-modal-actions .btn { width: 100%; justify-content: center; }
  .empty-state { padding: 32px 12px; }
  /* `.lbl` is nowrap so a two-word section label never breaks — but pages append live counts
     to it ("// revenue by game (3 games · 5:39 AM)"), and at 320px that single unbreakable
     line pushed the page 58px sideways. Let it wrap; it still never breaks a word. */
  .lbl { white-space: normal; }
  .page-head > h2, .page-head > h3 { min-width: 0; }
}

/* ── motion ───────────────────────────────────────────────── */
@keyframes rise { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }
.rise, .fade-up { animation: rise .4s cubic-bezier(.2,.7,.2,1) both; }
@keyframes pulse { 50% { opacity: 0.35; } }
@keyframes blink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
