/* Galtean Dispatch management portal. No inline styles anywhere (the CSP forbids them), so it all lives here.

   The look is the "Ember" design language: near-black violet ink surfaces lifted one step at a time, hairline
   white borders, a four-stop flame scale for accents with violet beside it, Bricolage Grotesque for the
   interface, JetBrains Mono for labels and numbers' captions, Instrument Serif italic for the odd flame-lit
   word, big soft radii, deep shadows with an inset highlight, and glows rather than flat fills. The fonts are
   served from /static/vendor/fonts (OFL, licences beside them), since the CSP allows nothing but 'self'. */

@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/static/vendor/fonts/bricolage-grotesque-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
    U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/static/vendor/fonts/bricolage-grotesque-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Instrument Serif";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/static/vendor/fonts/instrument-serif-italic-latin.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/static/vendor/fonts/jetbrains-mono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
    U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/static/vendor/fonts/jetbrains-mono-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* The ring that turns around a lit primary button */
@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

:root {
  /* Ground to lifted */
  --ink-0: #070410;
  --ink-1: #0e0b18;
  --ink-2: #151126;
  --ink-3: #1d1630;
  --line: rgba(255, 255, 255, 0.08);
  --line-2: rgba(255, 255, 255, 0.14);
  --text: #f3eefc;
  --text-soft: #c9c1de;
  --mute: #8f87aa;
  /* Flame stops, gold to deep red, and the second accent */
  --flame-1: #ffd36b;
  --flame-2: #ff9a2e;
  --flame-3: #ff5a1f;
  --flame-4: #c2260f;
  --violet: #9b8cff;
  --mint: #7fe0b0;
  --sky: #8cc8ff;
  --ember-red: #ff7a66;

  --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  --font-ui: "Bricolage Grotesque", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-serif: "Instrument Serif", Georgia, serif;
  --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  --radius-lg: 28px;
  --radius: 22px;
  --radius-sm: 12px;
  --shadow-deep: 0 40px 80px -30px rgba(0, 0, 0, 0.9), inset 0 1px 0 rgba(255, 255, 255, 0.05);
  --shadow: 0 20px 40px -20px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.04);
  --glow: 0 0 10px var(--flame-3);

  /* The names the rest of the portal (the Hub Planner's stylesheet included) was written against, now on the
     tokens above. */
  --bg: var(--ink-0);
  --surface: var(--ink-1);
  --surface-2: var(--ink-2);
  --muted: var(--mute);
  --border: var(--line);
  --accent: var(--flame-2);
  --accent-text: var(--ink-0);
  --danger-bg: rgba(255, 90, 31, 0.1);
  --danger-text: var(--ember-red);

  --badge-neutral-bg: rgba(255, 255, 255, 0.05);
  --badge-neutral-text: var(--text-soft);
  --badge-requested-bg: rgba(255, 211, 107, 0.1);
  --badge-requested-text: var(--flame-1);
  --badge-loading-bg: rgba(155, 140, 255, 0.12);
  --badge-loading-text: var(--violet);
  --badge-loaded-bg: rgba(127, 224, 176, 0.1);
  --badge-loaded-text: var(--mint);
  --badge-deployed-bg: rgba(255, 154, 46, 0.12);
  --badge-deployed-text: var(--flame-2);
  --badge-cancelled-bg: rgba(255, 90, 31, 0.1);
  --badge-cancelled-text: var(--ember-red);
}

/* ============================================================ Base */

* { box-sizing: border-box; }

html { color-scheme: dark; background: var(--ink-0); }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--ink-0);
  color: var(--text);
  font: 15px/1.55 var(--font-ui);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: var(--flame-1); text-decoration: none; transition: color 0.2s ease; }
a:hover { color: var(--flame-2); text-decoration: underline; text-underline-offset: 3px; }
:focus-visible { outline: 2px solid var(--flame-2); outline-offset: 2px; }

h1, h2 { font-weight: 700; letter-spacing: -0.035em; line-height: 1.02; }
h1 { font-size: clamp(34px, 4.6vw, 52px); margin: 0.25rem 0 1.5rem; }
h2 { font-size: 21px; letter-spacing: -0.02em; margin: 0 0 1rem; }
h3 {
  margin: 1.25rem 0 0.6rem;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mute);
}
/* The flame-lit word: Instrument Serif italic in the flame gradient */
h1 em, h2 em, .brand-sub {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.01em;
  background: linear-gradient(100deg, var(--flame-1), var(--flame-2) 45%, var(--flame-3));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  padding-right: 0.06em;
}

code {
  padding: 1px 6px;
  border-radius: 6px;
  background: var(--ink-3);
  font-family: var(--font-mono);
  font-size: 0.86em;
  color: var(--flame-1);
}

.muted { color: var(--mute); }
.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ============================================================ Top bar */

.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.75rem;
  min-height: 64px;
  padding: 0.6rem max(24px, calc((100vw - 1680px) / 2));
  background: rgba(7, 4, 16, 0.72);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
  white-space: nowrap;
}
.brand:hover { color: var(--text); text-decoration: none; }
.brand-sub { font-size: 19px; }
/* A glowing ember in a dark socket */
.brand-mark {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 70%, var(--flame-1), var(--flame-2) 35%, var(--flame-3) 60%, var(--ink-3) 62%);
  box-shadow: inset 0 -2px 4px rgba(0, 0, 0, 0.5), 0 0 16px rgba(255, 110, 30, 0.45);
  animation: breathe 2.6s ease-in-out infinite;
}
.tag {
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--line-2);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 400;
  color: var(--text-soft);
}

.nav { position: relative; min-width: 0; }
/* On a narrow screen the tabs scroll sideways inside their box. portal.js marks the nav `overflowing` when they
   do, and `at-start` / `at-end` at either end, and a fade over the edge with more beyond it says so. */
.nav.overflowing::before, .nav.overflowing::after {
  content: "";
  position: absolute;
  top: 1px;
  bottom: 1px;
  z-index: 1;
  width: 36px;
  border-radius: 13px;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.nav.overflowing::before { left: 1px; background: linear-gradient(90deg, var(--ink-0) 30%, transparent); }
.nav.overflowing::after { right: 1px; background: linear-gradient(270deg, var(--ink-0) 30%, transparent); }
.nav.overflowing.at-start::before, .nav.overflowing.at-end::after { opacity: 0; }
.nav ul {
  display: flex;
  gap: 4px;
  list-style: none;
  margin: 0;
  padding: 4px;
  border-radius: 14px;
  background: var(--ink-0);
  border: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.nav a {
  position: relative;
  display: block;
  padding: 7px 14px;
  border-radius: 10px;
  color: var(--mute);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  transition: background 0.2s ease, color 0.2s ease;
}
.nav a:hover { color: var(--text-soft); text-decoration: none; }
.nav a.active {
  background: var(--ink-3);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
/* The live tab's ember under its label */
.nav a.active::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 3px;
  width: 5px;
  height: 5px;
  margin-left: -2.5px;
  border-radius: 50%;
  background: var(--flame-2);
  box-shadow: var(--glow);
}

.account { margin-left: auto; display: flex; align-items: center; gap: 0.7rem; font-size: 14px; font-weight: 500; color: var(--text-soft); }
.account form { margin: 0; }
.avatar { display: block; border-radius: 50%; box-shadow: 0 0 0 2px var(--ink-3), 0 0 0 3px rgba(255, 154, 46, 0.45); }
.link-button {
  padding: 7px 13px;
  border: 1px solid var(--line-2);
  border-radius: 10px;
  background: var(--ink-2);
  color: var(--text);
  font: 600 12px var(--font-ui);
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.link-button:hover { border-color: rgba(255, 154, 46, 0.5); color: var(--flame-1); }

/* ============================================================ Page */

/* A faint grid fading down from the top, and the warmth of a fire just off the top edge */
.content {
  position: relative;
  flex: 1;
  width: min(1680px, 100% - 48px);
  margin: 0 auto;
  padding: 2.25rem 0 1rem;
}
.content::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 420px;
  z-index: -1;
  pointer-events: none;
  background-image: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(70% 60% at 50% 0%, #000, transparent 75%);
  mask-image: radial-gradient(70% 60% at 50% 0%, #000, transparent 75%);
  opacity: 0.55;
}
body::before {
  content: "";
  position: fixed;
  inset: -30vh 0 auto;
  height: 60vh;
  z-index: -2;
  pointer-events: none;
  background: radial-gradient(45% 55% at 50% 0%, rgba(255, 90, 20, 0.1), transparent 70%);
}

.footer {
  width: min(1680px, 100% - 48px);
  margin: 0 auto;
  padding: 2rem 0 2.5rem;
  border-top: 1px solid var(--line);
  color: var(--mute);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-align: center;
}

.card {
  position: relative;
  margin-bottom: 1.25rem;
  padding: 1.4rem 1.6rem;
  border-radius: var(--radius-lg);
  background: var(--ink-1);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-deep);
}
.card.narrow {
  max-width: 30rem;
  margin: 4rem auto;
  padding: 2.25rem;
  text-align: center;
  background: radial-gradient(60% 90% at 50% 100%, rgba(255, 90, 20, 0.18), transparent 70%), var(--ink-1);
}
.card.narrow h1 { font-size: clamp(32px, 6vw, 42px); }
.card.narrow .notice { text-align: left; }
.card > h2:first-child, .card-head h2 { display: flex; align-items: center; gap: 10px; }
/* Each card's heading carries an ember, like the source's eyebrows */
.card > h2:first-child::before, .card-head h2::before {
  content: "";
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--flame-2);
  box-shadow: var(--glow);
}

/* ============================================================ Buttons */

/* The primary action: the lit fire button's body (warm dark gradient, inset highlight, ember glow beneath) with
   its turning ring on hover and focus */
.button, .content button.primary {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.85rem 1.6rem;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(180deg, #2b1c33 0%, #170f22 100%);
  box-shadow: inset 0 1px 0 rgba(255, 210, 160, 0.22), inset 0 -2px 6px rgba(40, 0, 0, 0.6),
    inset 0 0 26px rgba(255, 120, 40, 0.16), 0 18px 44px -14px rgba(255, 80, 20, 0.45),
    0 0 0 1px rgba(255, 150, 80, 0.18);
  color: var(--flame-1);
  font: 650 15px/1 var(--font-ui);
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: scale 0.45s var(--spring), box-shadow 0.4s ease;
}
.button:hover, .content button.primary:hover:not(:disabled) { color: var(--flame-1); text-decoration: none; filter: none; }
.button:active, .content button.primary:active:not(:disabled) { scale: 0.95; transition-duration: 0.12s; }
.button::before, .content button.primary::before {
  content: "";
  position: absolute;
  inset: -2px;
  padding: 2px;
  border-radius: inherit;
  background: conic-gradient(from var(--angle), transparent 0 35%, var(--flame-3) 50%, var(--flame-1) 58%, transparent 72%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
  animation: spin 2.8s linear infinite;
}
.button:hover::before, .button:focus-visible::before,
.content button.primary:hover:not(:disabled)::before, .content button.primary:focus-visible::before { opacity: 1; }
.button:focus-visible, .content button.primary:focus-visible { outline-offset: 5px; }

/* ============================================================ Notices */

.notice {
  padding: 0.75rem 1rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.03);
}
.notice-error { background: var(--danger-bg); border-color: rgba(255, 122, 102, 0.3); color: var(--danger-text); }

/* ============================================================ Summary tiles */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 14px;
  margin-bottom: 1.25rem;
}
.tile {
  position: relative;
  overflow: hidden;
  padding: 1.25rem 1.35rem 1.2rem;
  border-radius: var(--radius);
  background: radial-gradient(90% 80% at 50% 0%, var(--ink-2), var(--ink-1));
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
}
.tile-label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
}
.tile-value {
  margin: 0.35rem 0 0.2rem;
  font-size: 40px;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
}
.tile-none { color: var(--mute); }
.tile-note { color: var(--text-soft); font-size: 13px; }
a.tile-link { display: block; color: inherit; transition: border-color 0.3s ease, translate 0.45s var(--spring), box-shadow 0.4s ease; }
a.tile-link:hover, a.tile-link:focus-visible {
  color: inherit;
  text-decoration: none;
  translate: 0 -3px;
  border-color: rgba(255, 154, 46, 0.35);
  box-shadow: var(--shadow), 0 18px 44px -18px rgba(255, 80, 20, 0.45);
}
/* A link tile's heat, rising from below when it's pointed at */
a.tile-link::after {
  content: "";
  position: absolute;
  inset: auto -20% -60%;
  height: 90%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(255, 120, 30, 0.22), transparent);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s ease;
}
a.tile-link:hover::after, a.tile-link:focus-visible::after { opacity: 1; }

/* ============================================================ Tables */

/* The wrapper scrolls sideways, so a wide table never widens the page on a phone. It is positioned so that what is
   taken out of the flow inside it (a button's visually hidden subject) is clipped with the table, not left far to
   the right of it, widening the page. */
.table-wrap { position: relative; overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0 -0.4rem; padding: 0 0.4rem; }
/* A scroll cue on a table with more to the right: portal.js marks the wrapper `overflowing`, and `at-end` once it is
   scrolled all the way. The wrapper is a one-cell grid so the cue can sit over the table's own cell and stay stuck to
   the visible right edge (a plain pseudo-element would scroll away with the content). It never takes a click. */
.table-wrap.overflowing { display: grid; }
.table-wrap.overflowing > table { grid-area: 1 / 1; }
.table-wrap.overflowing::after {
  content: "";
  grid-area: 1 / 1;
  justify-self: end;
  position: sticky;
  right: -0.4rem;
  width: 36px;
  background: linear-gradient(270deg, var(--ink-1), transparent);
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.table-wrap.overflowing.at-end::after { opacity: 0; }
table { border-collapse: collapse; width: 100%; font-size: 14px; }
th, td { padding: 0.6rem 0.65rem; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap; }
th {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
  border-bottom-color: var(--line-2);
}
th.num, td.num { text-align: right; }
td { color: var(--text-soft); }
td:first-child { color: var(--text); }
tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background 0.2s ease; }
tbody tr:hover { background: rgba(255, 255, 255, 0.025); }
/* The row that matters now (the next system to claim, the hub picked): warmed, with a flame edge */
tbody tr.row-current { background: linear-gradient(90deg, rgba(255, 120, 30, 0.1), rgba(255, 120, 30, 0.02) 60%); }
tbody tr.row-current td:first-child { box-shadow: inset 3px 0 0 var(--flame-2); }
.empty { color: var(--mute); margin: 0.25rem 0; }

/* ============================================================ Status badges */

.badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 8px;
  border: 1px solid var(--line-2);
  background: var(--badge-neutral-bg);
  color: var(--badge-neutral-text);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1.5;
  white-space: nowrap;
}
.badge-requested, .badge-route-claimed { background: var(--badge-requested-bg); color: var(--badge-requested-text); border-color: rgba(255, 211, 107, 0.3); }
.badge-loading { background: var(--badge-loading-bg); color: var(--badge-loading-text); border-color: rgba(155, 140, 255, 0.32); }
.badge-loaded, .badge-complete, .badge-finished, .badge-route-colonised { background: var(--badge-loaded-bg); color: var(--badge-loaded-text); border-color: rgba(127, 224, 176, 0.28); }
.badge-deployed, .badge-route-deployed { background: var(--badge-deployed-bg); color: var(--badge-deployed-text); border-color: rgba(255, 154, 46, 0.35); }
.badge-cancelled { background: var(--badge-cancelled-bg); color: var(--badge-cancelled-text); border-color: rgba(255, 122, 102, 0.3); }
.badge-hub, .badge-sourced { background: rgba(140, 200, 255, 0.1); color: var(--sky); border-color: rgba(140, 200, 255, 0.28); }
.badge-point { background: rgba(155, 140, 255, 0.1); color: var(--violet); border-color: rgba(155, 140, 255, 0.28); }
/* The one thing to do next: lit */
.badge-next {
  border-color: transparent;
  background: linear-gradient(90deg, var(--flame-1), var(--flame-2) 50%, var(--flame-3));
  color: var(--ink-0);
  box-shadow: 0 0 14px rgba(255, 110, 30, 0.45);
}
td .badge + .badge, td .badge-next { margin-left: 4px; }

/* ============================================================ Facts, counts, stats */

.facts { display: grid; grid-template-columns: max-content 1fr; gap: 0.55rem 1.5rem; margin: 0; }
.facts dt {
  padding-top: 0.2em;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
}
.facts dd { margin: 0; color: var(--text-soft); }
.facts dd strong { color: var(--text); font-weight: 650; }

.stage-counts { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; margin: 0 0 1.1rem; padding: 0; }
.stage-counts li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px 6px 12px;
  border-radius: 999px;
  background: var(--ink-2);
  border: 1px solid var(--line);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-soft);
}
.stage-counts li:has(.badge) { padding-left: 6px; }
.stage-counts strong { font-variant-numeric: tabular-nums; color: var(--text); padding-right: 4px; }

/* A card's heading beside its period links */
.card-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem 1rem; margin-bottom: 1rem; }
.card-head h2 { margin: 0; }
/* A card-head's controls on the right together, e.g. a selector and its table's CSV, kept right when they wrap */
.card-tools { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 0.5rem 1rem; margin-left: auto; }
/* The Fleet & CMDRs search, between the tab bar and the section it narrows */
.fleet-search { margin-bottom: 1rem; }
.fleet-search p { margin: 0.25rem 0 0; font-size: 13px; }
/* The period links as a segmented control */
.period-links { display: flex; gap: 4px; list-style: none; margin: 0; padding: 4px; border-radius: var(--radius-sm); background: var(--ink-0); border: 1px solid var(--line); }
.period-links a {
  display: block;
  padding: 6px 12px;
  border-radius: 8px;
  color: var(--mute);
  font: 600 13px var(--font-ui);
  transition: background 0.2s ease, color 0.2s ease;
}
.period-links a:hover { color: var(--text-soft); text-decoration: none; }
.period-links a.active { background: var(--ink-3); color: var(--text); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); }

/* The Hubs tab's section tabs: the same segmented control, with each tab's count; on a narrow screen it scrolls
   sideways inside its own box rather than widen the page */
.tab-bar {
  display: flex;
  gap: 4px;
  width: fit-content;
  max-width: 100%;
  margin: 1.75rem 0 1.25rem;
  padding: 4px;
  border-radius: 14px;
  background: var(--ink-0);
  border: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.tab-bar a {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 7px 14px;
  border-radius: 10px;
  color: var(--mute);
  font: 600 14px var(--font-ui);
  white-space: nowrap;
  transition: background 0.2s ease, color 0.2s ease;
}
.tab-bar a:hover { color: var(--text-soft); text-decoration: none; }
.tab-bar a.active { background: var(--ink-3); color: var(--text); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); }
.tab-bar a:focus-visible { outline: 2px solid var(--flame-2); outline-offset: 2px; }
.tab-count { font: 500 12px var(--font-mono); font-variant-numeric: tabular-nums; color: var(--mute); }
.tab-bar a.active .tab-count { color: var(--flame-1); }

.columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0.5rem 1.25rem; margin-top: 0.75rem; }
.columns > div { padding: 1rem 1.1rem; border-radius: 20px; background: var(--ink-2); border: 1px solid var(--line); }
.columns h3 { margin-top: 0; }
.ranked { list-style: none; margin: 0; padding: 0; counter-reset: rank; }
.ranked li {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.45rem 0;
  border-top: 1px solid var(--line);
  font-size: 14px;
  color: var(--text-soft);
  counter-increment: rank;
}
.ranked li:first-child { border-top: 0; }
.ranked li::before {
  content: counter(rank, decimal-leading-zero);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  color: var(--flame-1);
}
.ranked li > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A copy button sets its own ellipsis (on its name, so the icon stays), and its focus ring must not be clipped */
.ranked li > span:has(button.copy) { overflow: visible; }
.ranked li strong { color: var(--text); font-weight: 650; }
/* The logged-in person's own line on the Stats tab */
.ranked li.ranked-you { color: var(--text); }
.ranked li.ranked-you strong { color: var(--flame-1); }
.badge-you { background: var(--badge-deployed-bg); color: var(--badge-deployed-text); border-color: rgba(255, 154, 46, 0.35); margin-left: 4px; }

/* The Stats tab's selectors, side by side in a card's head */
.selectors { display: flex; flex-wrap: wrap; gap: 8px; }
/* /fleetstatus' states, and the panel dimming while a choice is fetched */
.badge-free { background: var(--badge-loaded-bg); color: var(--badge-loaded-text); border-color: rgba(127, 224, 176, 0.28); }
.badge-busy { background: var(--badge-deployed-bg); color: var(--badge-deployed-text); border-color: rgba(255, 154, 46, 0.35); }
.doing { list-style: none; margin: 0; padding: 0; }
.doing li + li { margin-top: 0.25rem; }
#stats-panel.htmx-request { opacity: 0.6; transition: opacity 0.2s ease; }

/* The route's progress: the heat meter's track and flame fill */
.route-progress {
  display: block;
  width: 100%;
  height: 10px;
  margin: 0.25rem 0 0.9rem;
  border: 0;
  border-radius: 99px;
  background: var(--ink-2);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.6);
  overflow: hidden;
  -webkit-appearance: none;
  appearance: none;
}
.route-progress::-webkit-progress-bar { background: var(--ink-2); border-radius: 99px; box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.6); }
.route-progress::-webkit-progress-value {
  border-radius: 99px;
  background: linear-gradient(90deg, var(--flame-4), var(--flame-3), var(--flame-2), var(--flame-1));
  box-shadow: 0 0 12px rgba(255, 110, 30, 0.6);
}
.route-progress::-moz-progress-bar {
  border-radius: 99px;
  background: linear-gradient(90deg, var(--flame-4), var(--flame-3), var(--flame-2), var(--flame-1));
  box-shadow: 0 0 12px rgba(255, 110, 30, 0.6);
}

td.wrap { white-space: normal; min-width: 12rem; overflow-wrap: anywhere; }
/* A system name copy button in a cell that may wrap wraps with it, its icon beside the last line */
td.wrap .copy-text { overflow: visible; white-space: normal; text-align: left; }
/* Two cards in a row (the finished reload tables), stacked when the page is too narrow for both; min-width: 0
   lets each card's table scroll inside it rather than widen the row. */
.side-by-side { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 30rem), 1fr)); gap: 0 1.25rem; align-items: start; }
.side-by-side > .card { min-width: 0; }

/* "updated …", with an ember that breathes while the panel keeps itself fresh */
.panel-footer {
  margin: 0.75rem 0 0;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--mute);
}
.panel-footer::before {
  content: "";
  display: inline-block;
  margin: 0 0.6rem 0.1em 0;
  vertical-align: middle;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--flame-2);
  box-shadow: var(--glow);
  animation: breathe 2.6s ease-in-out infinite;
}

/* ============================================================ Motion */

@keyframes breathe { 50% { scale: 1.18; opacity: 0.75; } }
@keyframes spin { to { --angle: 360deg; } }
/* Magic UI's Blur Fade: up, in and into focus */
@keyframes intro { from { opacity: 0; translate: 0 24px; filter: blur(6px); } }

/* The page comes in once, its parts one after another. portal.js marks the page settled once that's done, so a
   panel's 30-second refresh, which replaces its cards, doesn't play it again. */
html:not(.settled) .content > h1,
html:not(.settled) .content > [id$="-panel"] > *,
html:not(.settled) .tiles > * {
  animation: intro 0.9s var(--ease-out) both;
}
html:not(.settled) .content > h1 { animation-delay: 0.05s; }
html:not(.settled) .content > [id$="-panel"] > :nth-child(1),
html:not(.settled) .tiles > :nth-child(1) { animation-delay: 0.15s; }
html:not(.settled) .content > [id$="-panel"] > :nth-child(2),
html:not(.settled) .tiles > :nth-child(2) { animation-delay: 0.25s; }
html:not(.settled) .content > [id$="-panel"] > :nth-child(3),
html:not(.settled) .tiles > :nth-child(3) { animation-delay: 0.35s; }
html:not(.settled) .content > [id$="-panel"] > :nth-child(4),
html:not(.settled) .tiles > :nth-child(4) { animation-delay: 0.45s; }
html:not(.settled) .content > [id$="-panel"] > :nth-child(n + 5),
html:not(.settled) .tiles > :nth-child(n + 5) { animation-delay: 0.55s; }
html:not(.settled) .content > #hub-section { animation: intro 0.9s var(--ease-out) 0.45s both; }
html:not(.settled) .card.narrow { animation: intro 0.9s var(--ease-out) 0.1s both; }

/* ============================================================ Magic UI effects

   Ported from Magic UI (magicui.design, MIT) to plain CSS; portal.js sets the custom properties they read. */

/* Magic Card: a soft light under the pointer (--mx, --my from portal.js), and the border it catches. Cards and
   tiles are isolated so the light sits above their background and below their content. */
.card, .tile { isolation: isolate; }
.card::before, .tile::before, .card::after {
  content: "";
  position: absolute;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.45s ease;
}
.card::before, .tile::before {
  inset: 0;
  z-index: -1;
  background: radial-gradient(380px circle at var(--mx, -999px) var(--my, -999px), rgba(255, 120, 30, 0.085), transparent 70%);
}
.card::after {
  inset: -1px;
  padding: 1px;
  background: radial-gradient(280px circle at var(--mx, -999px) var(--my, -999px), rgba(255, 170, 80, 0.55), transparent 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}
@media (hover: hover) {
  .card:hover::before, .tile:hover::before, .card:hover::after { opacity: 1; }
}

/* Border Beam: a spark running round the edge of something waiting on someone (the login card, a queue with
   requests nobody has claimed). The span is masked to a ring; the spark follows the ring's rounded rect. */
.border-beam {
  position: absolute;
  inset: -1px;
  padding: 1.5px;
  border-radius: inherit;
  pointer-events: none;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}
.border-beam::after {
  content: "";
  position: absolute;
  width: var(--beam-size, 90px);
  aspect-ratio: 1;
  background: linear-gradient(to left, var(--flame-1), var(--flame-3), transparent);
  offset-path: rect(0 auto auto 0 round var(--beam-radius, var(--radius-lg)));
  offset-anchor: 90% 50%;
  animation: beam var(--beam-duration, 9s) linear infinite;
}
.tile .border-beam { --beam-radius: var(--radius); --beam-size: 70px; --beam-duration: 6s; }
@supports not (offset-path: rect(0 auto auto 0)) {
  .border-beam { display: none; }
}

/* Meteors: streaks falling across the login card, made by portal.js inside its .meteors box */
.meteors {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
}
.meteor {
  position: absolute;
  top: -4px;
  left: var(--left, 50%);
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--flame-1);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06), 0 0 8px var(--flame-2);
  opacity: 0;
  rotate: 215deg;
  animation: meteor var(--duration, 6s) linear var(--delay, 0s) infinite;
}
.meteor::before {
  content: "";
  position: absolute;
  top: 50%;
  width: 60px;
  height: 1px;
  translate: 0 -50%;
  background: linear-gradient(90deg, var(--flame-2), transparent);
}

/* Animated Shiny Text: a glint crossing the flame-lit word now and then */
.brand-sub, .card.narrow h1 em {
  background-image: linear-gradient(110deg, transparent 42%, rgba(255, 246, 225, 0.95) 50%, transparent 58%),
    linear-gradient(100deg, var(--flame-1), var(--flame-2) 45%, var(--flame-3));
  background-size: 250% 100%, 100% 100%;
  background-repeat: no-repeat;
  background-position: 100% 0, 0 0;
  animation: shine 8s ease-in-out 2s infinite;
}

/* Number Ticker: a number counting warms while it counts, and a tile or count whose number changed on a
   refresh flares once */
.tile-value, .stage-counts strong { transition: color 0.6s ease; }
.tile-value.ticking, .stage-counts strong.ticking { color: var(--flame-1); }
.tile.changed, .card.changed { animation: flare 1.8s var(--ease-out); }
.stage-counts li.changed { animation: flare-pill 1.8s var(--ease-out); }

@keyframes beam { to { offset-distance: 100%; } }
@keyframes meteor {
  0% { opacity: 1; transform: translateX(0); }
  70% { opacity: 1; }
  100% { opacity: 0; transform: translateX(-520px); }
}
@keyframes shine {
  0% { background-position: 100% 0, 0 0; }
  35%, 100% { background-position: 0% 0, 0 0; }
}
@keyframes flare {
  0% { border-color: rgba(255, 154, 46, 0.6); box-shadow: var(--shadow), 0 0 0 1px rgba(255, 154, 46, 0.45), 0 0 34px -4px rgba(255, 110, 30, 0.5); }
  100% { border-color: var(--line); box-shadow: var(--shadow); }
}
@keyframes flare-pill {
  0% { border-color: rgba(255, 154, 46, 0.6); box-shadow: 0 0 18px -2px rgba(255, 110, 30, 0.55); }
  100% { border-color: var(--line); box-shadow: none; }
}

/* ============================================================ Actions */

/* The message an action leaves on the page it came back to, once */
.notice-ok { background: rgba(127, 224, 176, 0.08); border-color: rgba(127, 224, 176, 0.3); color: var(--mint); }
.flash { margin: 0 0 1.25rem; white-space: pre-line; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* A button that asks first: a <details> whose summary is the button, and whose form, once opened, says what will
   happen and has the confirm button. */
/* The buttons' column. It has no background of its own, so the card's pointer light shows through it like any
   other cell. On a table too wide for its card (portal.js marks its wrapper `overflowing`), at 900px and wider, a
   cell that holds a button is pinned to the right edge, so the buttons stay in view while the rest scrolls under
   them: there it's frosted glass, translucent so the light still comes through, blurred so the columns sliding
   beneath stay out of the way. `right` reaches over the wrapper's padding so nothing shows beyond the pinned cell.
   Narrower, a phone's table has no room to spare for a column that stays: the buttons scroll with their row, and the
   wrapper's own scroll cue says there is more. A cell with no button (a row with nothing to do) is never pinned, and
   the header cell only where some row has one. */
th.actions-cell, td.actions-cell { text-align: right; }
@media (min-width: 900px) {
  .table-wrap.overflowing td.actions-cell:has(details.action),
  .table-wrap.overflowing:has(td.actions-cell details.action) th.actions-cell {
    position: sticky;
    right: -0.4rem;
    z-index: 1;
    background: rgba(14, 11, 24, 0.6);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    box-shadow: -18px 0 16px -16px rgba(0, 0, 0, 0.9);
  }
  /* The pinned column is its own cue */
  .table-wrap.overflowing:has(td.actions-cell details.action)::after { display: none; }
}
details.action { display: inline-block; vertical-align: top; text-align: left; margin-left: 6px; }
details.action > summary {
  display: inline-block;
  padding: 7px 15px;
  border: 1px solid var(--line-2);
  border-radius: 10px;
  background: var(--ink-2);
  color: var(--text);
  font: 600 13.5px var(--font-ui);
  white-space: nowrap;
  cursor: pointer;
  list-style: none;
  transition: border-color 0.2s ease, color 0.2s ease;
}
details.action > summary::-webkit-details-marker { display: none; }
details.action > summary:hover, details.action[open] > summary { border-color: rgba(255, 154, 46, 0.5); color: var(--flame-1); }
details.action > summary:focus-visible { outline: 2px solid var(--flame-2); outline-offset: 2px; }
.action-form {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  width: min(22rem, 80vw);
  margin-top: 6px;
  padding: 0.85rem 1rem;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(255, 122, 102, 0.3);
  background: var(--ink-1);
  box-shadow: var(--shadow);
  white-space: normal;
}
/* Opened, portal.js shows the form as a popover in the top layer, over every table, card and pointer light, at
   its button (--pop-left, --pop-top), rather than in the cell where it would push the columns apart. */
.action-form[popover] {
  position: fixed;
  inset: auto;
  left: var(--pop-left, 8px);
  top: var(--pop-top, 8px);
  max-height: var(--pop-max-height, calc(100vh - 16px));
  overflow-y: auto;
  margin: 0;
  color: var(--text);
  box-shadow: var(--shadow), 0 18px 48px -12px rgba(0, 0, 0, 0.85);
}
.action-form[popover]:not(:popover-open) { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .action-form[popover]:popover-open { animation: pop-in 0.16s ease-out; }
}
@keyframes pop-in { from { opacity: 0; transform: translateY(-4px); } }
.action-form p { margin: 0; color: var(--text-soft); font-size: 13px; }
.action-reason { display: flex; flex-direction: column; gap: 0.2rem; font-size: 12px; color: var(--mute); }
.action-reason input {
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: var(--ink-0);
  color: var(--text);
  font: 500 13px var(--font-ui);
}
.action-form button.danger {
  align-self: flex-start;
  padding: 0.6rem 1.15rem;
  border: 1px solid rgba(255, 122, 102, 0.5);
  border-radius: 10px;
  background: var(--danger-bg);
  color: var(--danger-text);
  font: 600 14px var(--font-ui);
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.action-form button.danger:hover { background: rgba(255, 90, 31, 0.2); color: var(--flame-1); }
/* An edit rather than an undoing: the same button, in the ember tones instead of the danger ones. */
.action-form button.safe {
  align-self: flex-start;
  padding: 0.55rem 1.05rem;
  border: 1px solid rgba(255, 154, 46, 0.5);
  border-radius: 10px;
  background: rgba(255, 154, 46, 0.1);
  color: var(--flame-1);
  font: 600 13.5px var(--font-ui);
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.action-form button.safe:hover { background: rgba(255, 154, 46, 0.2); }
.action-form button:focus-visible, .action-reason input:focus-visible, .action-check input:focus-visible { outline: 2px solid var(--flame-2); outline-offset: 2px; }
.action-check { display: flex; align-items: center; gap: 0.5rem; min-height: 32px; font-size: 13px; color: var(--text-soft); }
.action-check input { width: 18px; height: 18px; margin: 0; }

/* An action menu (_macros.html's `action_menu`): several small forms in one popover, one under another, each with its
   own heading. It can be taller than a short window, so it scrolls inside itself rather than running off the screen. */
.action-menu {
  width: min(24rem, 90vw);
  gap: 0;
  padding: 0;
  overflow-wrap: anywhere;
}
.action-menu .menu-form { display: flex; flex-direction: column; gap: 0.5rem; padding: 0.8rem 1rem; }
.action-menu .menu-form + .menu-form { border-top: 1px solid var(--line); }
.action-menu h3 { margin: 0; font: 600 11.5px var(--font-ui); letter-spacing: 0.06em; text-transform: uppercase; color: var(--mute); }

/* ============================================================ Copyable names */

/* A star system's name is a button that copies it (portal.js). At rest it reads as the text it replaced, with a
   faint icon after it; hovered, the icon lights and a dotted flame underline appears. Copied (`.copied`, for
   1.4 s), the icon's clipboard shrinks away as a tick draws in, sparks burst from the icon, a ring spreads out and a
   "Copied" pill springs over the button itself, the name fading out under it (never outside the button's own box, so
   no table wrapper or list clips it). A button that is only an icon shows the tick alone; a screen reader is told by
   the page's live region. The end states are classes rather than the end of an animation, so with reduced motion it
   still shows, just without the movement. */
button.copy {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  min-width: 0;
  max-width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-align: inherit;
  cursor: copy;
  -webkit-user-select: text;
  user-select: text;
  vertical-align: baseline;
}
button.copy:focus-visible { outline: 2px solid var(--flame-2); outline-offset: 3px; border-radius: 4px; }
.copy-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-decoration: underline dotted transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color 0.35s ease, text-shadow 0.35s ease, text-decoration-color 0.2s ease, opacity 0.2s ease;
}
button.copy:hover .copy-text { text-decoration-color: rgba(255, 154, 46, 0.55); }

.copy-icon {
  flex: none;
  width: 0.95em;
  height: 0.95em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.35;
  overflow: visible;
  transition: opacity 0.2s ease, color 0.2s ease, transform 0.25s var(--spring);
}
button.copy:hover .copy-icon, button.copy:focus-visible .copy-icon { opacity: 1; color: var(--flame-2); transform: scale(1.12); }
.copy-glyph { transform-origin: 8px 8px; transition: transform 0.25s var(--spring), opacity 0.2s ease; }
.copy-check {
  stroke: var(--mint);
  stroke-width: 2;
  stroke-dasharray: 16;
  stroke-dashoffset: 16;
  transition: stroke-dashoffset 0.35s var(--ease-out) 0.08s;
}
.copy-icon-only { padding: 2px; margin-left: 0.15em; }
.copy-icon-only .copy-icon { opacity: 0.5; }
.copy-icon-only .copy-done { display: none; }
/* portal.js writes the pill's word as it shows; it's never part of a selection copied from the page */
.copy-done { -webkit-user-select: none; user-select: none; }

/* The pill, over the button's own box */
.copy-done {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 0 4px;
  border: 1px solid rgba(127, 224, 176, 0.45);
  border-radius: 999px;
  background: rgba(14, 11, 24, 0.96);
  box-shadow: 0 0 16px -2px rgba(127, 224, 176, 0.45);
  color: var(--mint);
  font: 600 10px var(--font-mono);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

/* The ring and the sparks, both around the icon's end of the button */
button.copy::before, button.copy::after {
  content: "";
  position: absolute;
  right: calc(0.475em - 3px);
  top: calc(50% - 3px);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
}
button.copy.copy-icon-only::before, button.copy.copy-icon-only::after { right: calc(0.475em - 1px); }

button.copy.copied .copy-icon { opacity: 1; color: var(--mint); }
button.copy.copied .copy-text, button.copy.copy-failed .copy-text { opacity: 0; }
button.copy.copied .copy-glyph { transform: scale(0.2) rotate(-25deg); opacity: 0; }
button.copy.copied .copy-check { stroke-dashoffset: 0; }
button.copy.copied .copy-done, button.copy.copy-failed .copy-done { opacity: 1; transform: none; }
button.copy.copy-failed .copy-done { border-color: rgba(255, 122, 102, 0.5); color: var(--ember-red); box-shadow: none; }

@media (prefers-reduced-motion: no-preference) {
  button.copy.copied .copy-icon { animation: copy-thump 0.45s var(--spring); }
  button.copy.copied .copy-done { animation: copy-pill 0.5s var(--spring); }
  button.copy.copied::before { animation: copy-ring 0.7s var(--ease-out); }
  button.copy.copied::after { animation: copy-sparks 0.65s var(--ease-out); }
  button.copy.copy-failed .copy-icon { animation: copy-shake 0.4s ease; }
}
@keyframes copy-thump { 0% { transform: scale(1); } 35% { transform: scale(0.7); } 100% { transform: scale(1); } }
@keyframes copy-pill {
  0% { opacity: 0; transform: scale(0.6); }
  100% { opacity: 1; transform: none; }
}
@keyframes copy-ring {
  0% { opacity: 0.9; box-shadow: 0 0 0 0 rgba(255, 154, 46, 0.7); }
  100% { opacity: 0; box-shadow: 0 0 0 18px rgba(255, 154, 46, 0); }
}
@keyframes copy-sparks {
  0% {
    opacity: 1;
    box-shadow: 0 0 0 -1px var(--flame-1), 0 0 0 -1px var(--flame-2), 0 0 0 -1px var(--mint),
      0 0 0 -1px var(--flame-1), 0 0 0 -1px var(--flame-3), 0 0 0 -1px var(--violet);
  }
  100% {
    opacity: 0;
    box-shadow: 0 -16px 0 -2px var(--flame-1), 14px -8px 0 -2px var(--flame-2), 14px 8px 0 -2px var(--mint),
      0 16px 0 -2px var(--flame-1), -14px 8px 0 -2px var(--flame-3), -14px -8px 0 -2px var(--violet);
  }
}
@keyframes copy-shake { 25% { transform: translateX(-2px); } 75% { transform: translateX(2px); } }

/* The fallback copy's textarea, out of sight */
.copy-scratch { position: fixed; top: 0; left: -9999px; opacity: 0; }

/* ============================================================ Carriers and Logs */

/* The panels dim while a choice is fetched, as the Stats tab's does */
#carriers-panel.htmx-request, #logs-panel.htmx-request { opacity: 0.6; transition: opacity 0.2s ease; }

/* A log line's level; the roster's New and Returning commanders */
.badge-debug { color: var(--mute); }
.badge-info { background: rgba(140, 200, 255, 0.1); color: var(--sky); border-color: rgba(140, 200, 255, 0.28); }
.badge-warning { background: var(--badge-requested-bg); color: var(--badge-requested-text); border-color: rgba(255, 211, 107, 0.3); }
.badge-error { background: var(--badge-cancelled-bg); color: var(--badge-cancelled-text); border-color: rgba(255, 122, 102, 0.3); }
.badge-critical { background: var(--flame-4); color: var(--text); border-color: var(--ember-red); }
.badge-new { background: var(--badge-loaded-bg); color: var(--badge-loaded-text); border-color: rgba(127, 224, 176, 0.28); margin-left: 4px; }
.badge-returning { background: var(--badge-loading-bg); color: var(--badge-loading-text); border-color: rgba(155, 140, 255, 0.32); margin-left: 4px; }

/* A GET form that narrows a table: labelled fields in a row, wrapping on a narrow screen */
.filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.75rem 1rem; margin: 0 0 1rem; }
.filter { display: flex; flex-direction: column; gap: 0.2rem; font-size: 12px; color: var(--mute); min-width: 0; }
.filter-wide { flex: 1 1 16rem; }
.filters input, .filters select {
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: var(--ink-0);
  color: var(--text);
  font: 500 13px var(--font-ui);
}
.filters input:focus-visible, .filters select:focus-visible, .filters button:focus-visible { outline: 2px solid var(--flame-2); outline-offset: 2px; }
.filters button {
  padding: 0.5rem 1.1rem;
  border: 1px solid rgba(255, 154, 46, 0.5);
  border-radius: 8px;
  background: var(--badge-deployed-bg);
  color: var(--flame-1);
  font: 600 13px var(--font-ui);
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.filters button:hover { background: rgba(255, 154, 46, 0.22); }
.filters > a { align-self: center; font-size: 13px; }

/* A plain list of carriers or downloads, and the <details> that folds a long one (or a long log message) away */
.plain-list { list-style: none; margin: 0.35rem 0 0; padding: 0; }
.plain-list li + li { margin-top: 0.2rem; }
details.folded > summary { cursor: pointer; color: var(--flame-1); font-size: 13px; }
details.folded > summary:hover { color: var(--flame-2); }
details.folded > summary:focus-visible { outline: 2px solid var(--flame-2); outline-offset: 2px; border-radius: 4px; }
/* The EDDN feed's Markets: a block per carrier, and its two commodity lists side by side where there is room */
.market + .market { margin-top: 1.4rem; padding-top: 1.2rem; border-top: 1px solid var(--line); }
.market h3 { margin: 0 0 0.6rem; }
.market h4 { margin: 0 0 0.4rem; color: var(--text-soft); font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.market-lists { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); gap: 1rem 2rem; margin-top: 1rem; }
/* A table's CSV download (_macros.html's download_button): a secondary button, like an action's */
a.download {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 7px 14px;
  border: 1px solid var(--line-2);
  border-radius: 10px;
  background: var(--ink-2);
  color: var(--text);
  font: 600 13.5px var(--font-ui);
  white-space: nowrap;
  transition: border-color 0.2s ease, color 0.2s ease;
}
a.download:hover { border-color: rgba(255, 154, 46, 0.5); color: var(--flame-1); text-decoration: none; }
a.download:focus-visible { outline: 2px solid var(--flame-2); outline-offset: 2px; }
.download-icon { width: 15px; height: 15px; flex-shrink: 0; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.download-name { font: 500 12px var(--font-mono); color: var(--mute); }
a.download:hover .download-name { color: inherit; }

/* A /ravenscore user's /addscore, shown as the command's code block shows it, with its copy button beside it */
.addscore { display: flex; align-items: flex-start; gap: 0.5rem; min-width: 16rem; }
.addscore code {
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--ink-0);
  font: 12.5px/1.5 var(--font-mono);
  white-space: normal;
  overflow-wrap: anywhere;
  user-select: all;
}
.addscore button.copy { margin-top: 6px; }

/* The log table: the message in monospace, kept as written (a traceback's lines) and wrapped at any length */
.log-table td { vertical-align: top; }
.log-source { min-width: 7rem; max-width: 13rem; font-family: var(--font-mono); font-size: 12px; white-space: normal; overflow-wrap: break-word; }
td.log-message {
  min-width: 14rem;
  max-width: 60rem;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
td.log-message details { margin-top: 0.35rem; }

/* ============================================================ Small screens */

@media (max-width: 760px) {
  .topbar { gap: 0.6rem 1rem; padding: 0.6rem 16px; }
  .tag { display: none; }
  .nav { order: 3; flex: 1 1 100%; }
  .content, .footer { width: calc(100% - 32px); }
  .content { padding-top: 1.5rem; }
  .card { padding: 1.1rem; border-radius: var(--radius); }
  .card.narrow { margin: 2rem auto; padding: 1.5rem; }
  .tile-value { font-size: 34px; }
  .facts { grid-template-columns: 1fr; gap: 0.2rem; }
  .facts dd { margin-bottom: 0.6rem; }
}

/* A phone's top bar is three rows tall: it scrolls away rather than cover a third of the screen */
@media (max-width: 639px), (max-height: 599px) {
  .topbar { position: static; }
}

/* A table that opts in with `table-stack` becomes a stack of cards on a phone: a row a card, a cell a block with its
   column's name before it (the cell's `data-label`). The header row stays for screen readers, out of sight. */
@media (max-width: 759px) {
  .table-stack { display: block; }
  .table-stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .table-stack tbody, .table-stack tr, .table-stack td { display: block; }
  .table-stack tbody tr, .table-stack tbody tr:hover {
    margin-bottom: 0.7rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--ink-2);
  }
  .table-stack tbody tr:last-child { margin-bottom: 0; }
  .table-stack td, .table-stack tbody tr:last-child td {
    min-width: 0;
    max-width: none;
    padding: 0.35rem 0;
    border-bottom: 0;
    white-space: normal;
    overflow-wrap: anywhere;
    text-align: left;
  }
  .table-stack td::before {
    content: attr(data-label);
    display: block;
    font: 600 10.5px var(--font-mono);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--mute);
  }
  .table-stack td:not([data-label])::before { display: none; }
}

/* A touch screen: a hit area of 32px at least on the small things, without moving any of them. A copy button
   grows by padding it takes back with a negative margin; a link in a table does the same. */
@media (pointer: coarse) {
  button.copy { padding-block: 6px; margin-block: -6px; }
  button.copy .copy-done { inset-block: 6px; }
  button.copy.copy-icon-only { padding: 10px; margin: -10px; margin-left: calc(0.15em - 10px); }
  button.copy.copy-icon-only::before, button.copy.copy-icon-only::after { right: calc(0.475em + 7px); }
  .link-button, .filters button, .nav a, .period-links a, .tab-bar a { min-height: 32px; }
  .brand { min-height: 44px; }
  .period-links a, .tab-bar a { display: flex; align-items: center; }
  td a, .facts a, .plain-list a, .filters > a, .card-head a, .card.narrow p a { display: inline-block; padding-block: 7px; margin-block: -7px; }
  .card-head a.download { display: inline-flex; min-height: 32px; margin-block: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .border-beam, .meteors { display: none; }
  .brand-sub, .card.narrow h1 em { animation: none; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
