
:root {
  --sg-ease: cubic-bezier(0.4, 0, 0.2, 1);
  --sg-micro: 120ms;
  --sg-standard: 200ms;
  --sg-emphasis: 300ms;
  color-scheme: dark;
}

* { box-sizing: border-box; }

html, body, #root {
  margin: 0;
  padding: 0;
  min-height: 100%;
}

body {
  background: #010204;
  color: #edf2f4;
  font-family: Chakra Petch, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 14px;
  line-height: 21px;
  -webkit-font-smoothing: antialiased;
}

/* Fixed starfield: it scrolls with nothing, so depth reads as distance.
   Drawn, not shipped (D-972): five tiles of pinpricks at five sizes, so no
   two tiles repeat in step. The legacy wallpaper it replaces was third-party
   art. */
.sg-starfield {
  position: fixed;
  inset: 0;
  z-index: -1;
  background-color: #010204;
  background-image:
    radial-gradient(1px 1px at 12% 18%, #edf2f4 50%, transparent 51%),
    radial-gradient(1px 1px at 67% 42%, #b9c4cb 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 38% 77%, #7fa8e8 50%, transparent 51%),
    radial-gradient(1px 1px at 88% 8%, #8b989f 50%, transparent 51%),
    radial-gradient(1px 1px at 23% 58%, #8b989f 50%, transparent 51%);
  background-size: 240px 240px, 330px 330px, 510px 510px, 170px 170px, 290px 290px;
}

/* The Maw behind the sign-in form (D-972): over the star field, under
   everything else, and never in the way of a click. */
.sg-auth-backdrop {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

a { color: #7fa8e8; text-decoration: none; }
a:hover { text-decoration: underline; }

/* Keyboard focus must always be visible, including on Tamagui surfaces that
   manage their own focus styling for pointer input. */
:focus-visible {
  outline: 2px solid #7fa8e8;
  outline-offset: 2px;
}

/* Motion, applied by selector so components never repeat it — and so the
   reduced-motion guard is one rule rather than one per component. */
@media (prefers-reduced-motion: no-preference) {
  button,
  [role="button"],
  input,
  select,
  .sg-interactive {
    transition:
      background-color var(--sg-micro) var(--sg-ease),
      color var(--sg-micro) var(--sg-ease),
      border-color var(--sg-micro) var(--sg-ease),
      box-shadow var(--sg-micro) var(--sg-ease),
      opacity var(--sg-micro) linear,
      transform var(--sg-micro) var(--sg-ease);
  }
}

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

button { font: inherit; color: inherit; }
/* A placeholder has to read as absent, not as a value already typed — the
   fleet roster shows "0" in every unused ship slot. Tamagui's own
   placeholderTextColor prop only accepts theme tokens. */
input::placeholder { color: #8b989f; opacity: 1; }
/*
 * The selection highlight inside a field, which Tamagui otherwise paints with
 * nothing.
 *
 * Tamagui injects one rule of its own for every input in the app:
 *
 *   .is_Input::selection, .is_TextArea::selection
 *     { background-color: var(--t_selectionColor); }
 *
 * and it only ever defines that variable on the element when a selectionColor
 * prop is passed. Unset, the declaration is invalid at computed-value time, so
 * the background resolves to transparent — but the author rule still wins over
 * the browser's own highlight, which it has now replaced with nothing. The
 * selection is therefore made, and cut, and copied, and completely invisible
 * while it happens, which reads as a field whose text cannot be selected at
 * all. (It is not a user-select problem: no amount of "user-select: text
 * !important" reaches a colour.)
 *
 * One default for the variable gives every field its highlight back.
 */
:root { --t_selectionColor: rgba(127, 168, 232, 0.4); }
button:not(:disabled), [role="button"]:not([aria-disabled="true"]) { cursor: pointer; }
button:disabled { cursor: not-allowed; }
button:focus-visible, [role="button"]:focus-visible { outline: 2px solid #7fa8e8; outline-offset: 3px; }
.sg-card-select { position: absolute; inset: 0; width: 100%; border: 0; background: transparent; }
.sg-card-select:focus-visible { outline-offset: -4px; }

/*
 * The entity grid. Eight columns that divide the space actually available, so
 * the last tile in a row ends flush with the panel and no cell is a different
 * size from its neighbour.
 */
/* A tile is a button. Selecting its caption on a double click, or dragging a
   selection across half the grid while aiming at the scene behind it, is
   never what anyone meant. */
/*
 * The shipyard: a smaller window up top beside the detail, the tiles spanning
 * the full width below. The window cell holds the anchor the scene measures
 * rather than being told a magic offset, so the framing survives any change
 * to these numbers.
 */
.sg-yard {
  display: grid;
  /*
   * minmax(0, ...) on every track, and a fixed height rather than a minimum.
   * A 2fr/1fr split only divides what is left over: the detail spans both rows,
   * so its own content sets a floor under their sum, and a long description
   * grew the grid until the tiles fell off the bottom of the window. Tracks
   * that may shrink below their content, with the panel scrolling inside its
   * own, is what keeps the ratio a ratio.
   */
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  /* The tiles take what they need and the window takes the rest. A fixed
     ratio sized the tiles band by the window's height, so on a tall screen it
     was twice its tiles with a dead band above them. Capped: past 55% the
     tiles scroll rather than squeezing the window shut. */
  grid-template-rows: minmax(0, 1fr) fit-content(55%);
  height: calc(100vh - 190px);
  gap: 12px;
}
.sg-yard-window { grid-column: 1; grid-row: 1; position: relative; min-height: 0; }
/*
 * The right-hand column, standing on the tiles: the detail panel is as tall as
 * it needs to be and grows upward into the sky, never into the tiles. Top-
 * aligned, a tall screen left it hanging under the header with the window's
 * whole height empty beneath it. The tiles span the full width underneath —
 * the queue that used to sit under the detail lives in a modal now.
 */
.sg-yard-detail {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
  max-height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  /*
   * Positioned so the panel orders above anything else in the yard. (Above
   * the scene's DOM labels it is carried by the yard's own stacking context,
   * which sits at z 3 over the labels' z 2 — see EntitiesScreen.)
   */
  position: relative;
  z-index: 5;
}
.sg-yard-tiles {
  grid-column: 1 / -1;
  grid-row: 2;
  min-height: 0;
  overflow-y: auto;
  /*
   * Room for the Warden's mark (D-1126), which hangs 6px off a tile's top-left
   * corner: a scroller clips both axes. Margin out, padding in, so the tiles
   * keep their width and the research rows still land five and three.
   */
  margin: -6px 0 0 -6px;
  padding: 6px 0 0 6px;
  /*
   * A short row — the defense screen's single one — sits at the bottom of the
   * track rather than hanging under the window. Safe alignment keeps a tall
   * grid that overflows reachable at its top instead of clipping it off-track.
   */
  display: grid;
  align-content: safe end;
}
/*
 * The shipyard's two panels: combat and civil side by side, each taking half
 * the columns however many the width fits. One column on a phone, where half
 * of a narrow strip fits nothing.
 */
.sg-yard-groups { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; align-items: start; }
/*
 * The research rows: basic (5 tiles) beside drive (3), advanced beside
 * combat — eight tiles a row at full tile size. The 5:3 split plus the
 * 104px auto-fill is what lands exactly five and three, so nothing is ever
 * squeezed to fit a count.
 */
.sg-yard-groups.sg-groups-5-3 { grid-template-columns: 5fr 3fr; }
/*
 * The tile grid's own 20px gutter was costing a whole column here: at 78px
 * tiles it is most of one, and the row it pushed out was the difference between
 * two rows and three. The cell is already inset by the layout's own gap.
 */
.sg-yard-tiles .sg-grid { padding: 0 0 8px; }
/*
 * On a phone the yard is the cockpit layout: the scene window spans the gap
 * between the HUD and the bottom sheet (so the camera frames the subject into
 * what the sheet leaves visible), and the sheet carries the tiles as one
 * horizontal swipe strip with the selected entity's detail under it.
 */
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-yard { display: block; height: auto !important; }
  .sg-yard-window {
    position: fixed;
    left: 0;
    right: 0;
    top: var(--sg-hud-h, 110px);
    bottom: calc(var(--sg-tabbar-h, 60px) + var(--sg-sheet-h, 45vh));
  }
  /* Room for the Warden's ring and glow (.sg-beacon, .sg-pulse) on the top
     row: the body scrolls, which clips both axes at its padding edge, and
     with no padding on top the ring was cut flat against it. */
  .sg-yard-sheet .sg-sheet-body { display: flex; flex-direction: column; gap: 12px; padding-top: 18px; }
  /* Natural height: the body scrolls, its children never squeeze. */
  .sg-yard-sheet .sg-sheet-body > * { flex-shrink: 0; }
  /* The picked entity leads; the tiles follow it. */
  .sg-yard-detail { display: block; order: -1; max-height: none; align-self: stretch; }
  /* The sheet body is the scroller here, and a side sheet has no left gutter. */
  .sg-yard-tiles { display: block; flex: none; overflow: visible; margin: 0; padding: 0 0 0 6px; }
  /* Four tiles to a row on a phone, a few more on a small tablet. */
  .sg-yard-tiles .sg-grid { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; padding: 0; }
  .sg-yard-groups,
  .sg-yard-groups.sg-groups-5-3 { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .sg-yard-tiles section > :first-child { text-align: left !important; }
  .sg-yard-detail > * { overflow: visible !important; }
}

/*
 * The overview on a phone: the identity block stops floating and becomes the
 * sheet's peek, the queue lanes follow it, and the footer strip (which on a
 * phone covered the planet on the overview and every flat screen) goes.
 */
.sg-overview-queue, .sg-overview-window { display: none; }
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-overview-window {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    top: var(--sg-hud-h, 110px);
    bottom: calc(var(--sg-tabbar-h, 60px) + var(--sg-sheet-h, 124px));
    pointer-events: none;
  }
  #game-footer { display: none; }
  .sg-sheet .sg-overview-id.sg-overview-id {
    position: static !important;
    max-width: none !important;
    padding: 0 4px 12px !important;
    background: none;
    border: 0;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .sg-overview-queue { display: block; }
}

/* The battle overview on a phone: the round title across the top, the two
   sides' hull and morale cards side by side under it. */
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-battle-head { column-gap: 12px !important; row-gap: 8px !important; }
  .sg-battle-head > :nth-child(2) { order: -1; flex: 1 1 100% !important; min-width: 0 !important; }
  .sg-battle-head > :nth-child(1),
  .sg-battle-head > :nth-child(3) { flex: 1 1 0 !important; min-width: 0 !important; }
  .sg-battle-head > * > * { min-width: 0 !important; }
}

/*
 * The bottom sheet (BottomSheet.tsx). Wide screens never see it: the wrapper
 * and body are display: contents, so the children sit in the screen's grid.
 */
/*
 * The star map's focused body (D-1104): the frame cell it centres in, from
 * under the map's bar to the top of the planet card (both measured in
 * StarMap.tsx), and the card's close, floated just above its corner.
 */
.sg-map-window {
  position: fixed;
  left: 0;
  right: 0;
  top: var(--sg-map-top, 0px);
  bottom: var(--sg-map-bottom, 0px);
  pointer-events: none;
}
.sg-map-close { position: absolute; right: 0; bottom: calc(100% + 8px); }
/* The way out of a focused body (D-1135), so it has to read as a control over
   any planet: outlined, and brighter than the HUD's own hairline. */
.sg-map-close .sg-hud-btn.sg-hud-btn { border: 1px solid rgba(127, 168, 232, 0.6) !important; }
.sg-sheet, .sg-sheet-body { display: contents; }
.sg-sheet-handle { display: none; }
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-sheet {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--sg-tabbar-h, 60px);
    z-index: 30;
    pointer-events: auto;
    border-top: 1px solid rgba(127, 168, 232, 0.26);
    border-radius: 18px 18px 0 0;
    background: rgba(6, 9, 16, 0.8);
    backdrop-filter: blur(18px) saturate(140%);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    box-shadow: 0 -12px 32px -18px rgba(127, 168, 232, 0.35);
    transition:
      height 200ms var(--sg-ease),
      transform 240ms var(--sg-ease),
      opacity 200ms var(--sg-ease),
      visibility 240ms;
  }
  .sg-sheet.sg-sheet-dragging { transition: none; }
  /* Under a sheet stacked over it: out of the way, and back where it was
     when that one closes. */
  .sg-sheet.sg-sheet-covered {
    transform: translateY(18px);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
  }
  /* A sheet opened over another (an entity over its tiles) rises into place. */
  .sg-sheet.sg-sheet-stacked { z-index: 31; animation: sg-sheet-in 260ms var(--sg-ease); }
  @keyframes sg-sheet-in { from { transform: translateY(40%); opacity: 0; } }
  /* In a sheet nothing sits above the card, so the close heads it. */
  .sg-map-sheet .sg-map-close { position: static; align-self: flex-end; margin-bottom: -6px; }
  .sg-map-window { bottom: calc(var(--sg-tabbar-h, 60px) + var(--sg-sheet-h, 124px)); }
  /* In its own sheet a panel needs no panel of its own. */
  .sg-map-sheet .sg-map-panel,
  .sg-detail-sheet .sg-yard-detail > * {
    background: none !important;
    border: 0 !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    padding: 2px 4px 8px !important;
  }
  .sg-sheet .sg-sheet-handle {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 26px;
    min-height: 26px;
    padding: 0;
    border: 0;
    background: none;
    touch-action: none;
    cursor: grab;
    -webkit-tap-highlight-color: transparent;
  }
  .sg-sheet-handle span {
    width: 42px;
    height: 4px;
    border-radius: 2px;
    background: rgba(127, 168, 232, 0.6);
    box-shadow: 0 0 8px rgba(127, 168, 232, 0.55);
  }
  .sg-sheet-body {
    display: block;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 0 12px 12px;
  }
}

/*
 * The fleet screen: the muster above, the dispatch form below.
 *
 * The yard's grid with one column instead of two — the subject is a formation
 * spanning the width, not a single hull beside a detail panel, so the window
 * takes the whole top band and the form scrolls under it. Same contract as the
 * yard: the window is an empty cell the scene *measures* (D-868), so the
 * framing follows the layout instead of a magic number chasing it.
 */
.sg-muster {
  display: grid;
  /*
     The form takes what it needs and the scene takes the rest — not a fixed
     ratio. A 1.45fr/1fr split gave the roster a fraction of the window whatever
     its content was, so the tiles scrolled inside their own band while the
     scene above them had room to spare. Auto-sizing the controls and flooring
     the scene is the same bargain the other way round: the picture never
     collapses, and nothing scrolls until the form genuinely outgrows the page.
  */
  grid-template-rows: minmax(240px, 1fr) auto;
  /* A starting guess only: the screen measures where it begins and takes the
     rest of the window (useFitToViewport), because this one has no queue
     strip under it and the yard's 190px left a dead band at the bottom. */
  height: calc(100vh - 150px);
  gap: 12px;
}
.sg-muster-window { position: relative; min-height: 0; }
/* Form only (D-1093): no muster, and the form has the whole height. */
.sg-muster.is-form-only { grid-template-rows: minmax(0, 1fr); }
.sg-muster.is-form-only .sg-muster-window { display: none; }
/* Folded by the player: the window keeps a strip, for the toggle that unfolds it. */
.sg-muster.is-folded { grid-template-rows: 36px minmax(0, 1fr); }
.sg-muster.is-form-only .sg-muster-form,
.sg-muster.is-folded .sg-muster-form { max-height: none; }
/* Hide / show the fleet: in the scene's own window, not the form. */
.sg-muster-toggle { position: absolute; top: 0; right: 0; z-index: 1; pointer-events: auto; }
/* The roster takes the height and the step bar sits on the bottom of it; the
   panel scrolls only when a step genuinely overflows, which the target and
   mission forms can and the tiles should not. */
.sg-muster-form {
  min-height: 0;
  /* The one cap: past this the form scrolls rather than pushing the fleet off
     the top of the screen. */
  max-height: 62vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* On a phone: the muster fills the scene window above the sheet, the form
   is the sheet (the yard's cockpit layout). */
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-muster { display: block; height: auto !important; }
  .sg-muster-window {
    position: fixed;
    left: 0;
    right: 0;
    top: var(--sg-hud-h, 110px);
    bottom: calc(var(--sg-tabbar-h, 60px) + var(--sg-sheet-h, 45vh));
  }
  .sg-muster-form { max-height: none; overflow: visible; }
  .sg-muster-toggle { display: none; }
  /* The route keeps its origin's disc; the target's name needs the width. */
  .sg-route-from { display: none !important; }
  .sg-squad-label.is-idle { display: none; }
  /* The step's way on stays under the thumb instead of below the roster. */
  .sg-sheet .sg-muster-actions {
    position: sticky;
    bottom: -12px;
    z-index: 1;
    margin: 0 -12px -12px !important;
    padding: 8px 12px 10px;
    row-gap: 6px !important;
    background: #101417 !important;
    border-top: 1px solid rgba(127, 168, 232, 0.18);
  }
  /* The totals ride along the top of the bar as one line; the buttons under. */
  .sg-sheet .sg-muster-actions > :nth-child(2) {
    order: -1;
    flex-basis: 100%;
    flex-wrap: nowrap !important;
    gap: 18px !important;
  }
  .sg-sheet .sg-muster-actions > :nth-child(2) > * {
    flex-direction: row !important;
    align-items: baseline;
    gap: 6px;
  }
  .sg-sheet .sg-muster-actions button { white-space: nowrap; }
  /* Steps 2 and 3: the way on stays under the thumb as well. */
  /* The briefing's figures take their own line; Back and Next share the next. */
  .sg-sheet .sg-brief-row { flex-wrap: wrap !important; justify-content: space-between; }
  .sg-sheet .sg-brief-figures { order: -1; flex-basis: 100% !important; }
  .sg-sheet .sg-sheet-next {
    position: sticky;
    bottom: -12px;
    z-index: 1;
    margin: 0 -16px -16px !important;
    padding: 10px 16px !important;
    border-radius: 0 !important;
    background: #101417 !important;
    border-top: 1px solid rgba(127, 168, 232, 0.18);
  }
}

.sg-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(124px, 1fr)); padding: 20px; user-select: none; -webkit-user-select: none; }
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) { .sg-grid { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); padding: 12px; } }

/*
 * The tile caption. A scrim that fades to nothing over the top third, not an
 * opaque bar: the bar cut the artwork in two, and the name is legible either
 * way because the dark end of the gradient sits directly behind it.
 *
 * Buildable tiles wear it always (the card sets opacity inline); locked ones
 * keep it for hover/focus only, so an unbuildable goal does not shout over
 * what can actually be ordered.
 */
.sg-card-caption {
  opacity: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0.92) 0%, rgba(0,0,0,0.72) 45%, rgba(0,0,0,0) 100%);
  transform: translateY(-4px);
  transition: opacity var(--sg-standard) var(--sg-ease), transform var(--sg-standard) var(--sg-ease);
}
.sg-entity-card:hover .sg-card-caption, .sg-entity-card:focus-within .sg-card-caption { opacity: 1; transform: none; }

/*
 * Locked artwork. Dark at rest so the buildable tiles lead, full colour under
 * the pointer so the goal can be looked at. Both knobs: opacity alone over a
 * near-black tile greys the art out rather than darkening it. Doubled class:
 * Tamagui emits its own opacity class on the image.
 */
.sg-locked-art.sg-locked-art {
  opacity: 0.38;
  filter: brightness(0.7) saturate(0.8);
  transition: opacity var(--sg-standard) var(--sg-ease), filter var(--sg-standard) var(--sg-ease);
}
.sg-entity-card:hover .sg-locked-art.sg-locked-art,
.sg-entity-card:focus-within .sg-locked-art.sg-locked-art { opacity: 1; filter: none; }

/*
 * The frosted sheet. Doubled class for specificity: Tamagui emits its own
 * background class on any component this is put on, and that would outrank a
 * single-class rule.
 *
 * Three depths. The dialog floats over the whole page, so it stays translucent
 * and heavily blurred; sg-glass-deep is for chrome that has to win against
 * anything — the star map's control cluster over a bright star.
 *
 * sg-glass-veil is the entity detail, and it is the inverse trade: barely any
 * black, as much blur as the compositor will give. Owner call — the panel used
 * to be 86% black, which turned the bay it floats in into a rumour. Legibility
 * comes from destroying the detail behind the text rather than from covering
 * it, the way a frosted pane does, so what reads through is tone and light and
 * never a hull edge running under a number.
 */
.sg-glass.sg-glass {
  background: rgba(8, 11, 16, 0.74);
  backdrop-filter: blur(20px) saturate(150%);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
}
.sg-glass-deep.sg-glass-deep {
  background: rgba(2, 4, 7, 0.86);
  backdrop-filter: blur(8px) saturate(130%);
  -webkit-backdrop-filter: blur(8px) saturate(130%);
}
.sg-glass-veil.sg-glass-veil {
  background: rgba(2, 4, 7, 0.2);
  backdrop-filter: blur(28px) saturate(140%);
  -webkit-backdrop-filter: blur(28px) saturate(140%);
  border: 1px solid rgba(255, 255, 255, 0.06);
}
/*
  A place on the planet you can click (REQ-FEW-009).

  A hotspot is a real button that the 3D scene moves, so everything about where
  it *is* belongs to the scene and everything about what it *looks like* belongs
  here. Small, quiet and glass, because there are four of them sitting on top of
  the thing they are pointing at: a marker that competes with the planet is a
  marker in the way.
*/
.sg-hotspot {
  display: inline-flex;
  align-items: center;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid rgba(255, 126, 28, 0.28);
  cursor: pointer;
  white-space: nowrap;
  transition: box-shadow 140ms ease-out, border-color 140ms ease-out;
}
/* Scene markers keep their size on touch: the 44px floor stacked two
   neighbouring sites' pills on top of each other. */
@media (pointer: coarse) { .sg-hotspot { min-height: 0; padding: 7px 12px; } }
.sg-hotspot:hover,
.sg-hotspot:focus-visible {
  border-color: rgba(92, 184, 92, 0.6);
  box-shadow: 0 0 4px 0px rgba(92, 184, 92, 0.35);
}

/*
  The name of a thing standing on the ground (D-928).

  It exists only while the pointer is on the structure it names — the scene
  decides that and fades the host; all this owes is a chip small enough that a
  colony's worth of them, appearing one at a time, never reads as a layer of
  labels over the site.
*/
.sg-site-label {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid rgba(237, 242, 244, 0.16);
  color: #edf2f4;
  font-size: 12px;
  letter-spacing: 0.4px;
  white-space: nowrap;
  cursor: pointer;
}
.sg-site-label b { color: #ff7e1c; font-weight: 600; font-variant-numeric: tabular-nums; }
.sg-site-label:hover,
.sg-site-label:focus-visible { border-color: rgba(255, 126, 28, 0.65); }

/*
  A system's name on the galaxy plane (D-966). No backdrop blur: up to forty
  of these are on screen at once, and forty blurred layers over a moving
  canvas is the frame budget. A dark wash does the same job for legibility.
  The second line opens for the hovered and the selected system, flagged by
  the scene on the host (data-hot), so hover costs no render.
*/
.sg-star-label {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 2px 8px;
  /* One radius collapsed and expanded: a pill that turns into a card on hover
     reads as two different things, and a two-row pill is a lozenge (D-971). */
  border-radius: 8px;
  border: 1px solid rgba(237, 242, 244, 0.12);
  background: rgba(1, 2, 4, 0.55);
  color: #b9c4cb;
  font-size: 11px;
  letter-spacing: 0.4px;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 140ms ease-out, color 140ms ease-out, padding 140ms ease-out;
}
.sg-star-name { display: inline-flex; gap: 5px; align-items: baseline; }
.sg-star-name b { font-weight: 400; color: #8b989f; font-variant-numeric: tabular-nums; }
.sg-star-more { display: none; font-size: 11px; color: #b9c4cb; }
/* A chip sizes to its text: its host is a zero-width point, so shrink-to-fit
   would otherwise wrap every word onto its own line. A long line (a lair's
   threat) wraps at a readable measure instead. */
.sg-star-label { width: max-content; }
.sg-star-more { max-width: 320px; white-space: normal; text-align: center; }
.sg-star-owner { font-size: 10px; color: #b9c4cb; }

/*
  A squadron's chip in orbit and in battle (D-994): the star map's chip, with
  the count in the name and, in a fight, a hull bar under it. Not clickable —
  the hulls are what the pointer is for.
*/
/* A battle's end over the scene (D-1004): one word across the middle, in the
   outcome's colour, glowing, and a line under it. It never takes the pointer. */
.sg-battle-end {
  position: fixed; inset: 0; z-index: 4; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  text-align: center; padding: 0 16px;
}
.sg-battle-end-word {
  font-family: Gugi, Chakra Petch, sans-serif;
  font-size: clamp(44px, 11vw, 132px); line-height: 1; letter-spacing: 0.08em;
  text-transform: uppercase;
}
.sg-battle-end-line {
  font-family: Chakra Petch, sans-serif; font-size: clamp(14px, 2.2vw, 20px);
  color: #edf2f4; letter-spacing: 0.04em;
  text-shadow: 0 0 12px rgba(0, 0, 0, 0.9), 0 0 2px rgba(0, 0, 0, 0.9);
}
.sg-battle-end.is-victory .sg-battle-end-word {
  color: #5cb85c;
  text-shadow: 0 0 6px rgba(92, 184, 92, 0.9), 0 0 28px rgba(92, 184, 92, 0.75), 0 0 80px rgba(92, 184, 92, 0.5), 0 2px 2px rgba(0, 0, 0, 0.8);
}
.sg-battle-end.is-defeat .sg-battle-end-word {
  color: #ff7865;
  text-shadow: 0 0 6px rgba(255, 120, 101, 0.9), 0 0 28px rgba(255, 120, 101, 0.75), 0 0 80px rgba(255, 120, 101, 0.5), 0 2px 2px rgba(0, 0, 0, 0.8);
}
.sg-battle-end.is-draw .sg-battle-end-word {
  color: #edf2f4;
  text-shadow: 0 0 6px rgba(237, 242, 244, 0.7), 0 0 28px rgba(237, 242, 244, 0.4), 0 2px 2px rgba(0, 0, 0, 0.8);
}
@media (prefers-reduced-motion: no-preference) {
  .sg-battle-end-word { animation: sg-battle-end-in 1400ms cubic-bezier(0.16, 1, 0.3, 1) both; }
  .sg-battle-end-line { animation: sg-battle-end-line 900ms ease-out 900ms both; }
}
@keyframes sg-battle-end-in {
  0% { opacity: 0; transform: scale(1.6); letter-spacing: 0.6em; filter: blur(14px); }
  55% { opacity: 1; filter: blur(0); }
  100% { opacity: 1; transform: scale(1); letter-spacing: 0.08em; filter: blur(0); }
}
@keyframes sg-battle-end-line { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.sg-squad-label { cursor: default; pointer-events: none; gap: 3px; }
.sg-squad-label .sg-star-name b { color: inherit; font-weight: 600; }
.sg-squad-hp { display: block; width: 100%; min-width: 56px; height: 3px; border-radius: 2px;
  background: rgba(237, 242, 244, 0.14); overflow: hidden; }
.sg-squad-hp i { display: block; height: 100%; background: currentColor; transition: width 400ms ease-out; }
.sg-squad-hp.is-shield i { background: #b49cff; box-shadow: 0 0 4px rgba(180, 156, 255, 0.7); transition-duration: 700ms; }
/* THE BATTLE FEED: the fight told as it plays. Newest at the bottom, older
   lines fading out under the top edge; it scrolls back through the fight. */
.sg-feed { display: flex; flex-direction: column; gap: 3px; overflow-y: auto; overscroll-behavior: contain;
  padding: 10px 12px; scrollbar-width: thin; pointer-events: auto;
  mask-image: linear-gradient(to bottom, transparent 0, black 26px); }
.sg-feed-line { display: block; }
/* Few lines sit at the bottom, against the newest; many scroll. */
.sg-feed > :first-child { margin-top: auto; }
.sg-feed-phase { display: block; margin-top: 6px; padding-top: 6px; letter-spacing: 0.08em; text-transform: uppercase;
  border-top: 1px solid rgba(237, 242, 244, 0.1); }
.sg-feed-phase:first-child { padding-top: 0; border-top: 0; }
@media (prefers-reduced-motion: no-preference) {
  .sg-feed > * { animation: sg-feed-in 420ms ease-out both; }
}
@keyframes sg-feed-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
/* Where it sits over a fight: bottom right on a desk, beside the orders, under
   the attackers and clear of the planet's batteries; a phone swaps the scene
   for it instead (the Log toggle). Its ground is a light scrim, not the glass:
   the fight shows through, and it is only dark where the newest lines are. */
.sg-battle-foot { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 620px) minmax(260px, 1fr);
  gap: 12px; align-items: end; }
.sg-battle-foot > .sg-battle-orders { grid-column: 2; grid-row: 1; }
.sg-battle-foot > .sg-feed-dock { grid-column: 3; grid-row: 1; justify-self: end; width: 100%; }
.sg-feed-dock { display: flex; flex-direction: column; height: min(20vh, 200px); min-height: 120px; max-width: 360px;
  border-radius: 10px; pointer-events: auto; border: 1px solid rgba(237, 242, 244, 0.08);
  background: linear-gradient(to top, rgba(1, 2, 4, 0.82), rgba(1, 2, 4, 0.3));
  backdrop-filter: blur(3px); }
.sg-feed-dock .sg-feed { flex: 1; min-height: 0; }
.sg-feed-full { display: flex; flex-direction: column; flex: 1; min-height: 46vh; border-radius: 10px;
  pointer-events: auto; }
.sg-feed-full .sg-feed { flex: 1; min-height: 0; }
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-battle-foot { grid-template-columns: minmax(0, 1fr); }
  .sg-battle-foot > .sg-battle-orders { grid-column: 1; }
  .sg-battle-foot .sg-feed-dock { display: none; }
}
.sg-star-label.is-enemy { color: #ff7865; border-color: rgba(255, 120, 101, 0.45); }
/* Haulers and probes: named, but quieter than anything that fights. */
.sg-star-label.is-civil { font-size: 10px; opacity: 0.75; }
.sg-star-label.is-civil .sg-squad-hp { display: none; }
[data-hot="1"] .sg-star-label.is-civil { opacity: 1; }
.sg-star-label.is-mine .sg-star-owner { color: rgba(92, 184, 92, 0.8); }
/* A debris field beside its ring: the resource amounts, in a warm edge. */
.sg-debris-label { flex-direction: row; gap: 8px; border-color: rgba(255, 126, 28, 0.35); }
[data-hot="1"] .sg-debris-label { border-color: rgba(255, 126, 28, 0.8); padding: 2px 8px; }
/* A body's chip: the head is the select target, the actions open on hover. */
.sg-body-label { cursor: default; }
.sg-star-head {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  cursor: pointer;
}
.sg-star-actions { display: none; gap: 4px; margin-top: 4px; }
[data-hot="1"] .sg-star-actions { display: inline-flex; }
.sg-star-act {
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  cursor: pointer;
}
.sg-star-act:disabled { cursor: not-allowed; }

/* A mission tile (D-1091, MissionIcon): the glyph in a light tint of the
   mission's hue on a ground of it, the hue as its rim. --m is set inline. */
.sg-mission {
  display: inline-grid;
  place-items: center;
  flex: none;
  box-sizing: border-box;
  border-radius: 24%;
  color: color-mix(in srgb, var(--m) 62%, #edf2f4);
  background: radial-gradient(circle at 50% 30%, color-mix(in srgb, var(--m) 48%, #010204), color-mix(in srgb, var(--m) 16%, #010204) 78%);
  border: 1px solid color-mix(in srgb, var(--m) 75%, transparent);
  box-shadow: inset 0 0 6px color-mix(in srgb, var(--m) 40%, transparent);
  transition: box-shadow 120ms ease-out, filter 120ms ease-out, opacity 120ms ease-out;
}
button:hover:not(:disabled) .sg-mission,
button:focus-visible .sg-mission,
[aria-pressed="true"] .sg-mission {
  box-shadow: inset 0 0 6px color-mix(in srgb, var(--m) 40%, transparent), 0 0 0 1px var(--m), 0 0 12px color-mix(in srgb, var(--m) 70%, transparent);
}
:disabled .sg-mission,
[aria-disabled="true"] .sg-mission { filter: grayscale(1); opacity: 0.35; }

/* The dispatch's mission picker: a tile over its name, the picked one lit. */
.sg-mission-pick {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 76px;
  padding: 8px 6px 6px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  color: #b9c4cb;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.sg-mission-pick:hover:not(:disabled, .is-blocked) { color: #edf2f4; }
/* Blocked, not disabled (D-1105): the tile keeps its colour, does nothing,
   and its tooltip says why. */
.sg-mission-pick.is-blocked { cursor: not-allowed; }
.sg-mission-pick[aria-pressed="true"] { color: #edf2f4; border-color: rgba(211,227,235,0.13); background: rgba(237,242,244,0.10); }
.sg-mission-pick:disabled { cursor: not-allowed; color: #8b989f; }
/* The seven share the row's width, four to a row on a phone. */
/* The dispatch's route, origin to target (D-1098): a dashed course with the
   distance over it and an arrowhead at the target end. */
.sg-route { flex: 1 1 60px; min-width: 48px; position: relative; padding: 0 8px 10px; text-align: center; }
.sg-route::before { content: ""; position: absolute; left: 0; right: 6px; bottom: 4px; border-top: 1px dashed rgba(255, 126, 28, 0.6); }
.sg-route::after { content: ""; position: absolute; right: 0; bottom: 0; border: 4.5px solid transparent; border-right: 0; border-left: 7px solid #ff7e1c; }
.sg-mission-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(76px, 1fr)); gap: 4px; }

/* The dispatch's slot list: every slot of the target system, one click each. */
.sg-slot-list { display: flex; flex-direction: column; gap: 2px; }
.sg-slot-row {
  display: grid;
  grid-template-columns: 16px 18px minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  padding: 3px 8px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.sg-slot-row:hover { background: rgba(237,242,244,0.10); }
/* Planet, moon, debris (D-1142): which body at the address a fleet flies to. */
.sg-body-pick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid rgba(127, 168, 232, 0.28);
  border-radius: 6px;
  background: none;
  cursor: pointer;
}
.sg-body-pick:hover:not(:disabled) { border-color: #5cb85c; }
.sg-body-pick[aria-pressed="true"] { border-color: #ff7e1c; box-shadow: 0 0 4px 0px rgba(255, 126, 28, 0.35); }
.sg-body-pick:disabled { opacity: 0.3; cursor: default; }
/* A star map chip's head and its moon, side by side; the moon is the same
   button as the picker's, text-sized. */
.sg-star-row { display: inline-flex; align-items: center; gap: 6px; }
.sg-star-row .sg-body-pick { width: 20px; height: 20px; }
/* The aimed-at slot opens out of its row into the target card. */
@media (prefers-reduced-motion: no-preference) {
  .sg-slot-open { overflow: hidden; animation: sg-slot-open var(--sg-standard) var(--sg-ease); }
  @keyframes sg-slot-open { from { max-height: 28px; opacity: 0.5; } to { max-height: 160px; opacity: 1; } }
}
[data-hot="1"] .sg-star-label { color: #edf2f4; border-color: rgba(255, 126, 28, 0.65); padding: 4px 10px; }
[data-hot="1"] .sg-star-more { display: block; }
/* A tap is not a hover: on a touch screen the selected body's panel carries
   the facts and the missions, so the chip stays a name. */
@media (hover: none) {
  [data-hot="1"] .sg-star-more, [data-hot="1"] .sg-star-actions { display: none; }
}
.sg-star-label:hover,
.sg-star-label:focus-visible { color: #edf2f4; border-color: rgba(255, 126, 28, 0.65); }
.sg-star-label.is-home,
.sg-star-label.is-mine { color: #5cb85c; border-color: rgba(92, 184, 92, 0.45); }
.sg-star-label.is-ally { color: #7fa8e8; }
.sg-star-label.is-lair { color: #ff7865; border-color: rgba(255, 120, 101, 0.45); }
.sg-star-label.is-core { color: #ff7e1c; border-color: rgba(255, 126, 28, 0.45); }

/*
  A fleet in flight, as something you can actually point at (D-927).

  The route itself is a one-pixel line that moves; this is the target. Ringed
  rather than filled so it reads as a marker on the ships rather than as another
  body in the sky, and it grows on hover because a 10 px dot that does not
  answer the pointer feels broken.
*/
.sg-fleet-pip {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 999px;
  border: 1px solid currentColor;
  background: rgba(1, 2, 4, 0.55);
  cursor: pointer;
  transition: transform 140ms ease-out, background 140ms ease-out;
}
.sg-fleet-pip:hover,
.sg-fleet-pip:focus-visible { transform: scale(1.25); background: rgba(1, 2, 4, 0.8); }
.sg-fleet-dot { display: block; width: 7px; height: 7px; border-radius: 999px; }

/* The Feedback button (D-1113): bottom right, and over the sheet on a phone. */
.sg-feedback-fab { position: fixed; right: 16px; bottom: 16px; z-index: 25; }
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-feedback-fab { right: 10px; bottom: calc(var(--sg-tabbar-h, 60px) + var(--sg-sheet-h, 124px) + 10px); }
}
/* An announcement (D-1112): a closable card under the HUD, over whatever screen is open. */
.sg-announcement {
  position: fixed; z-index: 26; top: calc(var(--sg-hud-h, 64px) + 8px);
  left: 50%; transform: translateX(-50%); width: min(640px, calc(100vw - 20px));
}

.sg-skip { position: fixed; left: 12px; top: -80px; z-index: 2000; padding: 12px; background: #101417; }
.sg-skip:focus { top: 12px; }
.sg-data-table { width: 100%; border-collapse: collapse; text-align: left; }
.sg-data-table th { font-weight: 400; color: #b9c4cb; font-size: 12px; }
.sg-data-table th, .sg-data-table td { padding: 12px 10px; border-bottom: 1px solid rgba(211,227,235,0.13); }
.sg-data-table tbody tr:hover { background: rgba(237,242,244,0.10); }
.sg-data-table td:not(:nth-child(2)) { font-variant-numeric: tabular-nums; }
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) { .sg-data-table th, .sg-data-table td { padding: 10px 6px; } }
/* The flat system table (D-965): your rows and your alliance's rows carry an
   edge, the selected slot a wash. */
/* Sized before the system arrives (D-1104): fixed columns, and every row as
   tall as a planet's — disc and mission buttons — so nothing moves when the
   rows fill in. A long name ends in an ellipsis rather than widening its column. */
/* Fixed only where the columns fit: a narrower sheet (a small laptop, between
   the two rails) squeezes the table as before rather than scrolling it. */
.sg-system-scroll { container-type: inline-size; }
@container (min-width: 760px) {
  .sg-system-table { table-layout: fixed; }
  .sg-system-table col:nth-child(1) { width: 40px; }
  .sg-system-table col:nth-child(2) { width: 52px; }
  .sg-system-table col:nth-child(4) { width: 64px; }
  .sg-system-table col:nth-child(5) { width: 56px; }
  .sg-system-table col:nth-child(6) { width: 156px; }
  .sg-system-table col:nth-child(7) { width: 80px; }
  .sg-system-table col:nth-child(8) { width: 200px; }
}
.sg-system-table td { vertical-align: middle; padding: 6px 10px; height: 46px; box-sizing: border-box; }
.sg-system-table td > * { max-width: 100%; }
/* Not the moon's (4th): it is an icon now, and its hover card has to get out. */
.sg-system-table td:nth-child(3), .sg-system-table td:nth-child(6) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sg-system-table tbody tr.sg-row-own > td:first-child { box-shadow: inset 3px 0 0 0 #5cb85c; }
.sg-system-table tbody tr.sg-row-ally > td:first-child { box-shadow: inset 3px 0 0 0 #7fa8e8; }
.sg-system-table tbody tr.sg-row-target { background: rgba(255, 126, 28, 0.1); }
.sg-system-table tbody tr.sg-row-compose:hover { background: none; }
.sg-system-table tfoot td { border-bottom: 0; }
/*
 * On a phone each slot is a card, not an eight-column row that scrolls its
 * actions off the right edge: number and planet down the left, name over
 * owner in the middle, moon/debris/alliance at the right, actions under.
 */
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-system-table thead { display: none; }
  .sg-system-table { table-layout: auto; }
  .sg-system-table col:nth-child(n) { width: auto; }
  .sg-system-table tbody tr:not(.sg-row-compose) {
    display: grid;
    grid-template-columns: 22px 38px minmax(0, 1fr) auto auto;
    grid-template-areas:
      "n img name moon debris"
      "n img owner owner tag"
      "act act act act act";
    align-items: center;
    column-gap: 8px;
    padding: 8px 6px;
    border-bottom: 1px solid rgba(211,227,235,0.13);
  }
  .sg-system-table tbody tr:not(.sg-row-compose) > td { padding: 0; border: 0; min-width: 0; height: auto; }
  /* A planet's card has the same three lines whether its rows have arrived
     or not (D-1104): name, owner, missions. */
  .sg-system-table tbody tr.sg-row-planet { grid-template-rows: 22px 22px 34px; }
  .sg-system-table tbody tr > td:nth-child(1) { grid-area: n; }
  .sg-system-table tbody tr > td:nth-child(2) { grid-area: img; }
  .sg-system-table tbody tr > td:nth-child(3) { grid-area: name; }
  .sg-system-table tbody tr > td:nth-child(4) { grid-area: moon; }
  .sg-system-table tbody tr > td:nth-child(5) { grid-area: debris; }
  .sg-system-table tbody tr > td:nth-child(6) { grid-area: owner; }
  .sg-system-table tbody tr > td:nth-child(7) { grid-area: tag; }
  .sg-system-table tbody tr > td:nth-child(8) { grid-area: act; padding-top: 6px; }
  .sg-system-table tbody tr:not(.sg-row-planet) > td:nth-child(8) { display: none; }
  .sg-system-table tbody tr.sg-row-own > td:first-child,
  .sg-system-table tbody tr.sg-row-ally > td:first-child { box-shadow: none; }
  .sg-system-table tbody tr.sg-row-own { box-shadow: inset 3px 0 0 0 #5cb85c; }
  .sg-system-table tbody tr.sg-row-ally { box-shadow: inset 3px 0 0 0 #7fa8e8; }
}
.sg-prose { overflow-wrap: anywhere; }
/* Label for screen readers only. */
.sg-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* Alliance roster: rows sit on the rank column, actions hug the right edge. */
.sg-roster td { vertical-align: middle; }
/* A count riding inside a button label (alliance section tabs). */
.sg-count { display: inline-block; min-width: 18px; margin-left: 6px; padding: 0 6px; border-radius: 9px; font-size: 11px; line-height: 18px; text-align: center; color: #edf2f4; background: rgba(237,242,244,0.10); }
.sg-count-alert { color: #ff7e1c; background: rgba(255, 126, 28, 0.18); }
/*
 * Tabs, two levels (D-1089, components/Tabs.tsx). The strip hangs off the
 * foot of a screen's banner: icon, label, count, and the lit tab takes the
 * orange bar along its bottom edge, the phone tab bar's signal turned upside
 * down. Segments pick within a section from one sunken well. The Messages
 * hub's private conversations are a list lit from the left, the way the rail
 * lights a screen.
 */
.sg-tabstrip {
  display: flex;
  align-items: center;
  border-top: 1px solid rgba(127, 168, 232, 0.22);
  border-bottom: 1px solid rgba(211,227,235,0.13);
  background: linear-gradient(180deg, rgba(6, 9, 16, 0.94), rgba(6, 9, 16, 0.72));
}
/* Wrapping, not a hidden-scrollbar scroller: between the two rails a laptop
   window is too narrow for five tabs, and the last ones were simply cut off. */
.sg-tabstrip-tabs {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  min-width: 0;
  align-self: stretch;
  padding: 0 12px;
}
.sg-tabstrip-end { flex: none; padding: 0 12px; }
.sg-tab {
  position: relative;
  display: flex;
  flex: none;
  align-items: center;
  gap: 8px;
  min-height: 48px;
  padding: 0 18px;
  border: 0;
  background: none;
  cursor: pointer;
  color: #b9c4cb;
  font: 600 13px/1 Chakra Petch, sans-serif;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  white-space: nowrap;
  transition: color var(--sg-standard) var(--sg-ease), background var(--sg-standard) var(--sg-ease);
}
.sg-tab:hover { color: #edf2f4; background: rgba(127, 168, 232, 0.06); }
.sg-tab[aria-selected="true"] {
  color: #ff7e1c;
  /* Lit to the top and as wide as its bar: a tint fading out at 85% floated
     under the strip's edge, narrower than the tab and wider than the bar. */
  background: linear-gradient(0deg, rgba(255, 126, 28, 0.18), rgba(255, 126, 28, 0.05));
}
.sg-tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  border-radius: 2px;
  background: #ff7e1c;
  box-shadow: 0 0 10px 1px rgba(255, 126, 28, 0.8);
}
.sg-tab .sg-count, .sg-segment .sg-count, .sg-convo .sg-count { margin-left: 0; }
.sg-segments {
  display: flex;
  align-self: flex-start;
  width: fit-content;
  max-width: 100%;
  gap: 2px;
  padding: 3px;
  overflow-x: auto;
  scrollbar-width: none;
  border: 1px solid rgba(211,227,235,0.13);
  border-radius: 6px;
  background: rgba(2, 4, 10, 0.55);
}
.sg-segment {
  display: flex;
  flex: none;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: none;
  cursor: pointer;
  color: #b9c4cb;
  font: 500 13px/1 Chakra Petch, sans-serif;
  white-space: nowrap;
  transition: color var(--sg-standard) var(--sg-ease), background var(--sg-standard) var(--sg-ease);
}
.sg-segment:has(> svg) { padding-left: 10px; }
.sg-segment:hover { color: #edf2f4; background: rgba(127, 168, 232, 0.08); }
.sg-segment:disabled { cursor: not-allowed; opacity: 0.55; }
.sg-segment[aria-pressed="true"] {
  color: #edf2f4;
  border-color: rgba(255, 126, 28, 0.55);
  background: rgba(255, 126, 28, 0.12);
  box-shadow: 0 0 12px rgba(255, 126, 28, 0.2);
}
.sg-convo {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  min-height: 40px;
  padding: 0 12px;
  border: 0;
  border-radius: 4px;
  background: none;
  cursor: pointer;
  text-align: left;
  color: #b9c4cb;
  font: 500 14px/1.2 Chakra Petch, sans-serif;
}
.sg-convo > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sg-convo:hover { color: #edf2f4; background: rgba(127, 168, 232, 0.06); }
.sg-convo[aria-pressed="true"] {
  color: #edf2f4;
  background: linear-gradient(90deg, rgba(255, 126, 28, 0.16), rgba(255, 126, 28, 0));
}
.sg-convo[aria-pressed="true"]::before {
  content: "";
  position: absolute;
  left: 0;
  top: 22%;
  bottom: 22%;
  width: 2px;
  border-radius: 2px;
  background: #ff7e1c;
  box-shadow: 0 0 8px 1px rgba(255, 126, 28, 0.7);
}
.sg-tab:focus-visible, .sg-segment:focus-visible, .sg-convo:focus-visible {
  outline: 2px solid #7fa8e8;
  outline-offset: -2px;
}
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  /* Every tab gets an equal share, icon over label, the count on the icon;
     a control at the end drops to its own row under the tabs. */
  .sg-tabstrip { flex-wrap: wrap; }
  .sg-tabstrip-tabs { flex-basis: 100%; padding: 0; }
  .sg-tabstrip-end { margin-left: auto; padding: 4px 8px; }
  .sg-tab { flex: 1 1 0; flex-direction: column; gap: 4px; min-width: 0; min-height: 56px; padding: 8px 2px 6px; font-size: 11px; letter-spacing: 0.4px; }
  /* A label too long for its share takes a second line rather than an ellipsis. */
  .sg-tab > span:first-of-type { max-width: 100%; white-space: normal; overflow-wrap: anywhere; text-align: center; line-height: 1.15; }
  .sg-tab .sg-count { position: absolute; top: 4px; left: calc(50% + 6px); }
  .sg-segments { align-self: stretch; width: auto; }
  /* Four channels fit a phone's width without the glyphs; the well scrolls past that. */
  .sg-segment, .sg-segment:has(> svg) { gap: 4px; padding: 0 10px; }
  .sg-segment > svg { display: none; }
}

/* An on/off switch over a native checkbox (keyboard + screen reader for free). */
.sg-switch { position: relative; display: inline-block; flex: none; width: 42px; height: 24px; }
.sg-switch input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.sg-switch span { position: absolute; inset: 0; border-radius: 12px; background: rgba(237,242,244,0.10); border: 1px solid rgba(211,227,235,0.30); pointer-events: none; transition: background var(--sg-standard) var(--sg-ease), border-color var(--sg-standard) var(--sg-ease); }
.sg-switch span::after { content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #b9c4cb; transition: transform var(--sg-standard) var(--sg-ease); }
.sg-switch input:checked + span { background: rgba(92, 184, 92, 0.3); border-color: #5cb85c; }
.sg-switch input:checked + span::after { transform: translateX(18px); background: #5cb85c; }
.sg-switch input:focus-visible + span { box-shadow: 0 0 8px 1px rgba(127, 168, 232, 0.55); }
.sg-switch input:disabled { cursor: not-allowed; }
.sg-switch input:disabled + span { opacity: 0.55; }
/* The rank permission matrix (D-1029): ranks across, permissions down. */
.sg-matrix { width: 100%; border-collapse: collapse; }
.sg-matrix th, .sg-matrix td { padding: 10px 12px; border-bottom: 1px solid rgba(211,227,235,0.13); text-align: center; vertical-align: middle; }
.sg-matrix thead th { vertical-align: bottom; font-weight: 400; }
.sg-matrix thead th:first-child { text-align: left; }
.sg-matrix tbody th { text-align: left; font-weight: 400; min-width: 220px; }
.sg-matrix tbody th > * { display: block; }
.sg-matrix tbody tr:hover { background: rgba(237,242,244,0.10); }
.sg-matrix tbody th, .sg-matrix thead th:first-child { position: sticky; left: 0; z-index: 1; background: #101417; }
@media (max-width: 640px) {
  .sg-matrix tbody th { min-width: 132px; }
  .sg-matrix tbody th > :last-child { display: none; }
  .sg-matrix th, .sg-matrix td { padding: 8px; }
}
.sg-matrix input[type="checkbox"] { width: 18px; height: 18px; accent-color: #5cb85c; cursor: pointer; }
.sg-matrix input[type="checkbox"]:disabled { cursor: not-allowed; opacity: 0.55; }
/* An alliance tag or name that opens its public page (D-1034). */
.sg-alliance-link { color: #7fa8e8; text-decoration: none; }
.sg-alliance-link:hover { text-decoration: underline; }
.sg-alliance-link:focus-visible { outline: 2px solid #7fa8e8; outline-offset: 2px; border-radius: 2px; }
/* Player card (D-1067): a name that opens the card keeps its own text styling. */
.sg-player-link { all: unset; cursor: pointer; display: inline; }
.sg-player-link:hover { text-decoration: underline; text-decoration-color: #7fa8e8; }
.sg-player-link:focus-visible { outline: 2px solid #7fa8e8; outline-offset: 2px; border-radius: 2px; }
.sg-player-place { color: #b9c4cb; text-decoration: none; font-variant-numeric: tabular-nums; }
.sg-player-place:hover { color: #7fa8e8; text-decoration: underline; }
.sg-player-place:focus-visible { outline: 2px solid #7fa8e8; outline-offset: 2px; border-radius: 2px; }
.sg-leaderboard-focus td { background: rgba(255, 126, 28, 0.12); }
.sg-player-stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 12px; margin: 0; }
.sg-player-stats dd { margin: 2px 0 0; }
.sg-look-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); gap: 8px; }
.sg-look-grid--wide { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
.sg-look-pick { all: unset; cursor: pointer; display: grid; place-items: center; padding: 4px; border-radius: 6px; border: 1px solid transparent; overflow: hidden; }
.sg-look-pick:hover { border-color: #5cb85c; }
.sg-look-pick[aria-checked="true"] { border-color: #ff7e1c; box-shadow: 0 0 4px 0px rgba(255, 126, 28, 0.35); }
.sg-look-pick:focus-visible { outline: 2px solid #7fa8e8; outline-offset: 2px; }
.sg-look-grid--wide .sg-look-pick { display: block; }
/* Alliance circulars (D-1033): markdown rendered with HTML off. */
.sg-markdown { overflow-wrap: anywhere; font-size: 14px; line-height: 1.55; color: #edf2f4; }
.sg-markdown > :first-child { margin-top: 0; }
.sg-markdown > :last-child { margin-bottom: 0; }
.sg-markdown p, .sg-markdown ul, .sg-markdown ol, .sg-markdown blockquote, .sg-markdown pre { margin: 0 0 8px; }
.sg-markdown h1, .sg-markdown h2, .sg-markdown h3 { font-size: 15px; margin: 12px 0 6px; }
.sg-markdown ul, .sg-markdown ol { padding-left: 20px; }
.sg-markdown a { color: #7fa8e8; }
.sg-markdown code { background: rgba(237,242,244,0.10); padding: 1px 4px; border-radius: 3px; }
.sg-markdown pre code { display: block; padding: 8px; overflow-x: auto; }
.sg-markdown table { border-collapse: collapse; margin: 0 0 12px; font-variant-numeric: tabular-nums; }
.sg-markdown th, .sg-markdown td { padding: 6px 12px; border: 1px solid rgba(211,227,235,0.13); text-align: left; }
.sg-markdown th { background: rgba(237,242,244,0.10); font-weight: 500; }
/* A whole alliance page reads as a document: headings with some presence. */
.sg-page { font-size: 15px; line-height: 1.65; }
.sg-page h1 { font-size: 22px; margin: 20px 0 8px; }
.sg-page h2 { font-size: 18px; margin: 20px 0 8px; padding-bottom: 6px; border-bottom: 1px solid rgba(211,227,235,0.13); }
.sg-page h3 { font-size: 16px; margin: 16px 0 6px; }
.sg-page hr { border: 0; border-top: 1px solid rgba(211,227,235,0.13); margin: 16px 0; }
.sg-markdown blockquote { border-left: 3px solid rgba(211,227,235,0.13); padding-left: 10px; color: #b9c4cb; }
@media (pointer: coarse) { button, [role="button"], input { min-height: 44px; } .sg-card-caption { opacity: 1; } }
/*
 * Touch: iOS Safari zooms the whole page into any field under 16px when it
 * takes focus (and leaves it zoomed), and a double tap on a control is a
 * page zoom too, which holds every tap back while it waits for the second.
 */
@media (pointer: coarse) {
  input, textarea, select { font-size: 16px !important; }
  button, [role="button"], a { touch-action: manipulation; }
}
@media (prefers-reduced-motion: no-preference) {
  button:not(:disabled):active { transform: translateY(1px); }
  .sg-reveal { animation: sg-reveal 180ms ease-out; }
  /* A notice that arrived while you were elsewhere (D-925). */
  .sg-toast { animation: sg-toast-in 220ms ease-out; }
  @keyframes sg-toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
  @keyframes sg-reveal { from { opacity: 0; } to { opacity: 1; } }
  /* The build screens swap hero <-> entity detail in a fixed-height slot; the
     slide is what tells you the panel replaced the art rather than the page
     having jumped. */
  .sg-swap { animation: sg-swap 200ms var(--sg-ease); }
  @keyframes sg-swap { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
  /* The Warden's "this one" (D-1013): a glow that swells three times. Under
     reduced motion only the static outline below is left. */
  .sg-pulse { animation: sg-pulse 800ms ease-in-out 3; }
  @keyframes sg-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(127, 168, 232, 0); }
    50% { box-shadow: 0 0 18px 4px rgba(127, 168, 232, 0.55); }
  }
  /* A quick-build press landed: the button pops and a green ring leaves it.
     The button stays put, so the next press can follow at once. */
  .sg-press { animation: sg-press 320ms ease-out; }
  @keyframes sg-press {
    0% { transform: scale(0.86); box-shadow: 0 0 0 0 rgba(92, 184, 92, 0.9); }
    40% { transform: scale(1.08); }
    100% { transform: none; box-shadow: 0 0 14px 8px rgba(92, 184, 92, 0); }
  }
  /* The Warden's next step (D-1126): an orange glow that keeps breathing
     until the quest is done. A card wears this or .sg-pulse, never both. */
  .sg-beacon { animation: sg-beacon 1600ms ease-in-out infinite; }
  @keyframes sg-beacon {
    0%, 100% { box-shadow: 0 0 0 0 rgba(255, 126, 28, 0); }
    50% { box-shadow: 0 0 16px 3px rgba(255, 126, 28, 0.6); }
  }
  /* The Warden breathing in the rail; amber while a reward waits. */
  .sg-warden-glyph { animation: sg-warden-breathe 4s ease-in-out infinite; }
  @keyframes sg-warden-breathe { 0%, 100% { opacity: 0.7; } 50% { opacity: 1; } }
  /* A reward waiting (D-1127): the glyph beats and the block glows, hard
     enough to catch the eye from the scene. Static ring under reduced motion. */
  .sg-warden-glyph.is-alert { animation: sg-warden-beat 1200ms ease-in-out infinite; transform-origin: center; }
  @keyframes sg-warden-beat { 0%, 100% { opacity: 0.8; transform: scale(1); } 50% { opacity: 1; transform: scale(1.2); } }
  .sg-warden-call { animation: sg-warden-call 1200ms ease-in-out infinite; }
  @keyframes sg-warden-call {
    0%, 100% { box-shadow: 0 0 4px 0px rgba(255, 126, 28, 0.35); }
    50% { box-shadow: 0 0 22px 4px rgba(255, 126, 28, 0.55); }
  }
}
.sg-pulse { outline: 2px solid #7fa8e8; outline-offset: 3px; }
.sg-beacon { outline: 2px solid rgba(255, 126, 28, 0.8); outline-offset: 2px; }
/* "Come and collect" over the Warden's rail block for the first few claims
   (D-1127), its caret pointing down at the glyph. */
.sg-warden-callout {
  position: relative;
  padding: 6px 10px;
  border: 1px solid #ff7e1c;
  border-radius: 6px;
  background: rgba(12,16,18,0.90);
  box-shadow: 0 0 4px 0px rgba(255, 126, 28, 0.35);
}
.sg-warden-callout::after {
  content: "";
  position: absolute;
  bottom: -5px;
  left: 20px;
  width: 8px;
  height: 8px;
  background: rgba(12,16,18,0.90);
  border-right: 1px solid #ff7e1c;
  border-bottom: 1px solid #ff7e1c;
  transform: rotate(45deg);
}
/*
  A panel floating over the scene that must sit above the queue strip rather
  than behind it — the overview's identity block. The --sg-footer-h variable is
  published by GameLayout from the strip's own height, so the panel moves when it
  does; the fallback only covers the frame before the first measurement. The
  panel must not set a bottom of its own — Tamagui writes that as an inline
  style, which wins over any stylesheet.
*/
.sg-above-footer.sg-above-footer { bottom: calc(var(--sg-footer-h, 180px) + 24px); }
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-above-footer.sg-above-footer { bottom: calc(var(--sg-footer-h, 180px) + 100px); }
}
/*
 * Every hover panel (the Tip component): queue tiles, resource readouts,
 * the rail's queue icons, the star map's chips. Rendered in a portal at body
 * level and fixed, so no scroller clips it, no blurred neighbour paints over
 * it, and a closed one takes no room. Above Tamagui's dialog portals (stacked
 * from 100000): the queue modal's thumbnails open one too. The component
 * picks the side and the offsets; pointer-transparent unless it holds
 * controls.
 */
.sg-tip {
  position: fixed;
  z-index: 200000;
  width: max-content;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  border: 1px solid rgba(211,227,235,0.30);
  border-radius: 6px;
  /* The dialog's own sheet (.sg-glass): a hover panel over the scene is the
     same kind of surface as a modal over the scene. */
  background: rgba(8, 11, 16, 0.78);
  backdrop-filter: blur(20px) saturate(150%);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  box-shadow: 0 0 4px 0px rgba(127, 168, 232, 0.35);
  animation: sg-tip-in var(--sg-micro) var(--sg-ease);
}
@keyframes sg-tip-in { from { opacity: 0; } to { opacity: 1; } }
/* The caret: a rotated square straddling the edge that faces the anchor,
   offset along it by --sg-tip-caret so it points at the anchor's centre. */
.sg-tip::before {
  content: "";
  position: absolute;
  width: 8px;
  height: 8px;
  background: rgba(8, 11, 16, 0.78);
  border: 0 solid rgba(211,227,235,0.30);
  transform: rotate(45deg);
}
.sg-tip[data-side="bottom"]::before { top: -5px; left: calc(var(--sg-tip-caret) - 4px); border-left-width: 1px; border-top-width: 1px; }
.sg-tip[data-side="top"]::before { bottom: -5px; left: calc(var(--sg-tip-caret) - 4px); border-right-width: 1px; border-bottom-width: 1px; }
.sg-tip[data-side="right"]::before { left: -5px; top: calc(var(--sg-tip-caret) - 4px); border-left-width: 1px; border-bottom-width: 1px; }
.sg-tip[data-side="left"]::before { right: -5px; top: calc(var(--sg-tip-caret) - 4px); border-right-width: 1px; border-top-width: 1px; }

/*
 * The rail's queue strip: icons that open the queue modal. Chromeless by
 * design — the planet card already carries the border and the hover ring, so
 * a second bordered button inside it read as a double border. No hover
 * highlight of its own either: the tooltips on the icons are the feedback.
 */
.sg-queue-strip {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
  background: transparent;
  border: 0;
  padding: 2px;
  border-radius: 4px;
  cursor: pointer;
}

/*
 * The progressive blur behind the topnav: 100% at the top edge, 0% a breath
 * below the bar, so the chrome reads over a bright scene without putting a
 * lid on it.
 *
 * Viewport-fixed and edge to edge: absolute positioning would stop at the
 * centered content column. A backdrop blur is uniform across its element, so
 * the gradient comes from a mask fading the layer itself out downward. Blur
 * only, no tint — negative z-index paints it behind the header's content but
 * above the scene, and it never takes the pointer.
 */
.sg-topnav-blur {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100px;
  z-index: -1;
  pointer-events: none;
  /*
   * Quadratic-feel falloff (1-x squared sampled per quarter): the blur holds
   * strong through the upper band and lets go toward the bottom, instead of
   * thinning evenly. Mask and tint share the curve so they never decouple.
   */
  background: linear-gradient(
    to bottom,
    rgba(2, 4, 7, 0.32) 0%,
    rgba(2, 4, 7, 0.3) 25%,
    rgba(2, 4, 7, 0.24) 50%,
    rgba(2, 4, 7, 0.14) 75%,
    rgba(2, 4, 7, 0) 100%
  );
  backdrop-filter: blur(10px) saturate(130%);
  -webkit-backdrop-filter: blur(10px) saturate(130%);
  mask-image: linear-gradient(
    to bottom,
    black 0%,
    rgba(0, 0, 0, 0.94) 25%,
    rgba(0, 0, 0, 0.75) 50%,
    rgba(0, 0, 0, 0.44) 75%,
    transparent 100%
  );
  -webkit-mask-image: linear-gradient(
    to bottom,
    black 0%,
    rgba(0, 0, 0, 0.94) 25%,
    rgba(0, 0, 0, 0.75) 50%,
    rgba(0, 0, 0, 0.44) 75%,
    transparent 100%
  );
}
/* A flat screen scrolls, and its banner starts right under the header: the
   blur keeps to the header's own box and scrolls with it, so it never lies
   over the content (D-1132). The phone HUD is sticky and keeps its own. */
@media (not ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse)))) {
  .sg-topnav-blur-flat { position: absolute; inset: 0; height: auto; }
}

/* Rename affordance: the pencil is the hint, the name is the control. */
.sg-pencil { opacity: 0; }
.sg-editable:hover .sg-pencil, .sg-editable:focus-within .sg-pencil { opacity: 1; }
@media (pointer: coarse) { .sg-pencil { opacity: 1; } }

/* Numerals never reflow as they tick. */
.sg-num { font-variant-numeric: tabular-nums; }

/*
 * Queue progress inside the entity artwork. The unfinished remainder is a
 * dark sheet over the top that drains upward as the order completes; the 2px
 * line rides its lower edge so the 0→100 travel reads even on bright art.
 * Heights are set inline from the live progress (1 Hz tick) with a linear
 * transition smoothing the steps — motion-safe: the global reduced-motion
 * guard collapses it.
 */
.sg-progress-shade {
  background: rgba(2, 4, 7, 0.72);
  transition: height 1s linear;
}
.sg-progress-line {
  height: 2px;
  margin-top: -2px;
  background: #ff7e1c;
  box-shadow: 0 0 8px 1px rgba(255, 126, 28, 0.55);
  transition: top 1s linear;
}

/* The resource rail sits directly on the starfield with no panel under it, so
   the figures carry their own ground: a tight dark shadow, then a wider one in
   the value's own hue. */
.sg-resource {
  font-weight: 700;
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.95),
    0 0 14px rgba(0, 0, 0, 0.8);
}

/*
 * The hover underline, drawn from the middle outwards.
 *
 * A rule that grows from the centre reads as the element responding, where one
 * that wipes left-to-right reads as loading. Drawn as a pseudo-element rather
 * than a text-decoration because only a transform can be animated — and a
 * transform is also the one thing here that cannot cause a reflow.
 */
.sg-underline { position: relative; }
.sg-underline::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--sg-standard) var(--sg-ease);
}
.sg-underline:hover::after,
.sg-underline:focus-visible::after { transform: scaleX(1); }

/* Every coordinate is a link to the star map. */
.sg-coords.sg-coords:hover { color: #5cb85c; }

/*
 * Top navigation: the same treatment as the wordmark it sits beside — wide
 * tracking, uppercase, no button chrome. Hover lights it rather than boxing it.
 *
 * Only what Tamagui does not itself emit belongs here. A styled component
 * declares its own colour and font size, and those classes outrank this rule,
 * so TopLink sets both as props; this owns the treatment plus the hover state,
 * for which Tamagui emits nothing unless given a hoverStyle.
 */
.sg-topnav {
  text-transform: uppercase;
  letter-spacing: 2px;
  background: none;
  border: 0;
  /* The bottom padding is the underline's room: drawn outside the box it would
     paint over dead space, and the cursor would flicker crossing it. */
  padding: 4px 2px 8px;
  transition:
    color var(--sg-micro) var(--sg-ease),
    text-shadow var(--sg-micro) var(--sg-ease);
}
.sg-topnav:hover {
  color: #5cb85c;
  text-shadow: 0 0 12px rgba(92, 184, 92, 0.8);
}
.sg-topnav[aria-current="page"] { text-shadow: 0 0 12px rgba(255, 126, 28, 0.7); }
.sg-topnav[aria-current="page"]::after { transform: scaleX(1); }

/*
 * The commander's name in the HUD: the topnav's type, uppercase and tracked.
 * It opens the player card, so the link fills the label's height (the whole
 * box takes the pointer) and the label centres on the HUD buttons beside it.
 *
 * This cannot be Tamagui props: the body font's letterSpacing scale only
 * carries 0 (see config.ts), so letterSpacing 2 silently resolves to nothing
 * and the name renders tight next to tracked links.
 */
.sg-navlabel.sg-navlabel {
  text-transform: uppercase;
  letter-spacing: 2px;
  padding: 0 2px;
}
.sg-navlabel .sg-player-link { display: inline-block; padding: 6px 0; }

/*
 * The loading spinner. One rule set, sized by --sg-spinner-size, so every
 * call site shares this animation instead of declaring its own.
 *
 * Orange orbits around a green nucleus that breathes: the same
 * hover-green/active-orange pair the nav and every button use.
 */
.sg-spinner {
  position: relative;
  flex: none;
  width: var(--sg-spinner-size, 40px);
  height: var(--sg-spinner-size, 40px);
}
.sg-spinner-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: max(1px, calc(var(--sg-spinner-size, 40px) / 25)) solid transparent;
  /* Ring colour travels through the inherited color property, so the whole
     spinner recolours with one declaration instead of three restated
     animations. NOTE: this file is a template literal, no backticks in it. */
  color: #ff7e1c;
  border-left-color: currentColor;
  border-top-color: currentColor;
}
/* Two animations per ring: the orbit, and a pulse offset per ring so the three
   breathe out of step rather than blinking as one. */
.sg-spinner-ring:nth-child(1) { transform: rotateZ(120deg) rotateX(66deg); animation: sg-atom-1 1s linear infinite, sg-atom-pulse 2.4s ease-in-out infinite; }
.sg-spinner-ring:nth-child(2) { transform: rotateZ(240deg) rotateX(66deg); animation: sg-atom-2 1s linear infinite, sg-atom-pulse 2.4s ease-in-out -0.8s infinite; }
.sg-spinner-ring:nth-child(3) { transform: rotateZ(360deg) rotateX(66deg); animation: sg-atom-3 1s linear infinite, sg-atom-pulse 2.4s ease-in-out -1.6s infinite; }
@keyframes sg-atom-pulse {
  0%, 100% { opacity: 0.45; }
  50% { opacity: 1; }
}
@keyframes sg-atom-1 { to { transform: rotateZ(120deg) rotateX(66deg) rotateZ(360deg); } }
@keyframes sg-atom-2 { to { transform: rotateZ(240deg) rotateX(66deg) rotateZ(360deg); } }
@keyframes sg-atom-3 { to { transform: rotateZ(360deg) rotateX(66deg) rotateZ(360deg); } }
/* The nucleus. Offset by margin, not translate, so the pulse owns transform. */
.sg-spinner-core {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(var(--sg-spinner-size, 40px) * 0.12);
  height: calc(var(--sg-spinner-size, 40px) * 0.12);
  margin: calc(var(--sg-spinner-size, 40px) * -0.06) 0 0 calc(var(--sg-spinner-size, 40px) * -0.06);
  border-radius: 50%;
  background: #5cb85c;
  animation: sg-core-breathe 1.6s ease-in-out infinite;
}
@keyframes sg-core-breathe {
  0%, 100% { box-shadow: 0 0 5px 1px rgba(92, 184, 92, 0.6); transform: scale(0.85); }
  50%      { box-shadow: 0 0 14px 3px rgba(92, 184, 92, 0.95); transform: scale(1.35); }
}

/* A real select (D-918). The element is a browser widget, so it is styled
   here rather than through Tamagui, and the chevron is a background image
   because appearance:none removes the native one. */
.sg-select {
  appearance: none;
  font-family: Chakra Petch, sans-serif;
  font-size: 13px;
  line-height: 20px;
  color: #edf2f4;
  background-color: rgba(237,242,244,0.10);
  background-image: linear-gradient(45deg, transparent 50%, #b9c4cb 50%), linear-gradient(135deg, #b9c4cb 50%, transparent 50%);
  background-position: right 14px center, right 9px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  border: 1px solid rgba(211,227,235,0.13);
  border-radius: 4px;
  padding: 7px 28px 7px 10px;
  cursor: pointer;
  max-width: 100%;
}
.sg-select:hover { border-color: #5cb85c; }
.sg-select:focus-visible { border-color: #7fa8e8; box-shadow: 0 0 8px 1px rgba(127, 168, 232, 0.55); }
.sg-select:disabled { opacity: 0.55; cursor: not-allowed; }
.sg-select-sm { font-size: 12px; padding: 4px 24px 4px 7px; background-position: right 11px center, right 6px center; }
/* The list itself is drawn by the OS: without this it inherits the page's
   dark text on the platform's light popup on some browsers. */
.sg-select option { background: #101417; color: #edf2f4; }

/* ---------------------------------------------------------------------------
   Admin console (D-915)

   The console left the game shell: it owns the viewport, so it is a plate, not
   a stack of translucent panels floating on a 3D planet. Structure comes from
   hairline rules and two independently scrolling columns - the rail (sections
   above, roster below) and the workspace. Nothing here is a card except the
   entity tiles, which are artwork.

   These rules live in the generated stylesheet, not in the app's retained
   apps/web/src/styles.css: nothing imports that file, so the D-910 rules in it
   never reached the page. That is why the two-pane console rendered as a stack
   at every width.
   --------------------------------------------------------------------------- */
.sg-console {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  height: 100vh;
  background: #0f0f0f;
}
.sg-console-bar {
  display: flex;
  align-items: center;
  gap: 20px;
  height: 52px;
  padding: 0 20px;
  background: #010204;
  border-bottom: 1px solid rgba(211,227,235,0.13);
}
/* Reserved height: the success and error line appears and disappears
   constantly, and letting it push the console down reads as the page jumping. */
.sg-console-note {
  display: flex;
  align-items: center;
  min-height: 26px;
  padding: 0 20px;
  border-bottom: 1px solid rgba(211,227,235,0.13);
}
.sg-console-body {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  min-height: 0;
}
.sg-console-rail {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
  padding: 10px 0 24px;
  background: #010204;
  border-right: 1px solid rgba(211,227,235,0.13);
}
/* A section link: the whole row is the target, and the active one carries the
   accent on its leading edge rather than a filled pill. */
.sg-console-link {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 16px;
  border: 0;
  border-left: 2px solid transparent;
  background: none;
  color: #b9c4cb;
  font-family: Chakra Petch, sans-serif;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  transition: color var(--sg-micro) var(--sg-ease);
}
.sg-console-link:hover { color: #5cb85c; }
.sg-console-link[aria-current="page"] {
  border-left-color: #ff7e1c;
  color: #edf2f4;
  background: rgba(255, 126, 28, 0.08);
}
.sg-console-railhead {
  padding: 16px 16px 6px;
  border-top: 1px solid rgba(211,227,235,0.13);
  margin-top: 10px;
}
/* One roster row. Same accent language as the section links above it. */
.sg-console-row {
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 16px;
  border: 0;
  border-left: 2px solid transparent;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.sg-console-row:hover { background: rgba(237,242,244,0.10); }
.sg-console-row[aria-current="true"] {
  border-left-color: #ff7e1c;
  background: rgba(255, 126, 28, 0.1);
}
.sg-console-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(211,227,235,0.30);
}
.sg-console-dot.is-online {
  background: #5cb85c;
  box-shadow: 0 0 4px 0px rgba(92, 184, 92, 0.35);
}
.sg-console-work {
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  padding: 18px 28px 64px;
}
.sg-console-work > * { max-width: 1100px; }
/* A section heading and the hairline under it. The rule is the container. */
.sg-console-rule {
  height: 1px;
  background: rgba(211,227,235,0.13);
  border: 0;
  margin: 0;
}
.sg-console-scroll { overflow-x: auto; max-width: 100%; }
.sg-console-table th, .sg-console-table td { padding: 6px 10px; white-space: nowrap; }
.sg-console-table td.wrap { white-space: normal; min-width: 220px; }
.sg-console-table .num { text-align: right; font-variant-numeric: tabular-nums; }
/* The base .sg-data-table highlights EVERY row on hover, which made a clickable
   row indistinguishable from an inert one. Hover belongs to the rows that do
   something. */
.sg-console-table tbody tr:hover { background: none; }
.sg-console-table tbody tr.is-clickable { cursor: pointer; }
.sg-console-table tbody tr.is-clickable:hover { background: rgba(237,242,244,0.10); }
.sg-console-table tbody tr.is-clickable:hover .sg-console-go { color: #5cb85c; }
/* The selected row: an accent edge on the first cell, because a 14% wash is
   invisible on a table with one row. */
.sg-console-table tbody tr.is-selected > td:first-child { box-shadow: inset 3px 0 0 0 #ff7e1c; }
.sg-console-table tbody tr.is-selected td { color: #edf2f4; }
.sg-console-table tbody tr.is-selected .sg-console-go { color: #ff7e1c; }
.sg-console-go { color: #8b989f; }
/* A planet's own texture, round, as the row's identity. */
.sg-console-thumb {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  object-fit: cover;
  vertical-align: middle;
  margin-right: 8px;
  border: 1px solid rgba(211,227,235,0.13);
}
.sg-console-thumb.lg { width: 40px; height: 40px; }
/* The audit diff: two columns of monospace, before on the left. */
.sg-console-diff { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; align-items: start; }
.sg-console-diff pre {
  margin: 0;
  padding: 6px 8px;
  overflow-x: auto;
  max-height: 220px;
  font-family: Roboto Mono, monospace;
  font-size: 11px;
  line-height: 1.45;
  color: #b9c4cb;
  background: rgba(237,242,244,0.10);
  border-radius: 4px;
}
@media (max-width: 1000px) {
  .sg-console { height: auto; min-height: 100vh; }
  .sg-console-body { grid-template-columns: minmax(0, 1fr); }
  .sg-console-rail { overflow-y: visible; border-right: 0; border-bottom: 1px solid rgba(211,227,235,0.13); }
  .sg-console-work { overflow-y: visible; padding: 16px; }
  .sg-console-diff { grid-template-columns: 1fr; }
}

/*
 * The hostile-inbound light in the topnav fleet pill: a red dot blinking at
 * ~1 Hz while an attack or espionage fleet is on its way to one of the
 * commander's planets. Under prefers-reduced-motion the global guard stills
 * it to a steady red dot, which carries the same warning without the flash.
 */
.sg-hostile-dot {
  width: 10px;
  height: 10px;
  flex: none;
  border-radius: 50%;
  background: #ff7865;
  box-shadow: 0 0 8px 2px rgba(255, 120, 101, 0.75);
  animation: sg-hostile-blink 1.1s ease-in-out infinite;
}
/* The fleet pill's combat half: the same ~1 Hz blink as the hostile light. */
.sg-combat-live {
  border-color: #ff7865 !important;
  box-shadow: 0 0 10px 1px rgba(255, 120, 101, 0.6);
  animation: sg-hostile-blink 1.1s ease-in-out infinite;
}
@keyframes sg-hostile-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.15; }
}

/*
 * On a phone the HUD is a bar in its own right, the mirror of the tab bar: the
 * scene window starts under it, so it can be opaque enough to keep the
 * readouts legible over whatever the scene draws behind it.
 */
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  /* Dark under the readouts, fading out past the HUD's own bottom edge into
     the scene: the same progressive falloff as the desktop bar, deeper. */
  .sg-topnav-blur.sg-topnav-blur {
    height: calc(var(--sg-hud-h, 110px) + 44px);
    background: linear-gradient(
      to bottom,
      rgba(2, 4, 8, 0.92) 0%,
      rgba(2, 4, 8, 0.86) 45%,
      rgba(2, 4, 8, 0.55) 72%,
      rgba(2, 4, 8, 0.18) 90%,
      rgba(2, 4, 8, 0) 100%
    );
    mask-image: linear-gradient(to bottom, black 0%, black 70%, transparent 100%);
    -webkit-mask-image: linear-gradient(to bottom, black 0%, black 70%, transparent 100%);
  }
  /* Pinned: on a screen that scrolls, the HUD stays with its backdrop
     (.sg-topnav-blur is viewport-fixed) instead of scrolling out of it. */
  #game-header { position: sticky !important; top: 0; padding-top: env(safe-area-inset-top, 0px); }
}

/* A button that leads to another screen: a chevron on its far edge. */
.sg-door::after { content: "\203A"; margin-left: auto; font-size: 18px; line-height: 1; color: #b9c4cb; }

/* A label that only one width wants (the star map's short phone labels). */
.sg-phone-only { display: none; }
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) { .sg-phone-only { display: inline; } .sg-desk-only { display: none; } }

/*
 * The phone tab bar: four destinations and More, above the home indicator.
 * The active tab gets the rail's orange as a lit bar along its top edge,
 * the same signal the desktop rail gives with its ring.
 */
.sg-tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 100;
  /* The immersive layout is pointer-transparent; the bar takes it back. */
  pointer-events: auto;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  padding: 0 4px env(safe-area-inset-bottom, 0px);
  border-top: 1px solid rgba(127, 168, 232, 0.22);
  background: rgba(6, 9, 16, 0.86);
}
@media (not ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse)))) { .sg-tabbar { display: none; } }
@media (min-width: 560px) and ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-yard-tiles .sg-grid { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)) !important; }
}
/*
 * The phone HUD's controls are one family: planet chip, fleet pill, chat,
 * reports and settings share a height, a frame and a corner. Orange is the
 * screen you are on, green is something unread.
 */
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-hud-btn.sg-hud-btn {
    box-sizing: border-box !important;
    height: 42px !important;
    min-height: 42px !important;
    min-width: 42px !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    padding-left: 8px !important;
    padding-right: 8px !important;
    border: 1px solid rgba(127, 168, 232, 0.28) !important;
    border-radius: 4px !important;
    background: rgba(6, 9, 16, 0.55) !important;
  }
  .sg-hud-btn.sg-hud-btn.sg-combat-live { border-color: #ff7865 !important; }
  .sg-hud-btn.sg-hud-btn.is-unread { border-color: rgba(92, 184, 92, 0.7) !important; }
  .sg-hud-btn.sg-hud-btn.is-active,
  .sg-hud-btn.sg-hud-btn[aria-current="page"] { border-color: #ff7e1c !important; }
}
@media ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse))) {
  .sg-hud-readouts { order: 3; }
}
.sg-tabbar-item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: 58px;
  padding: 6px 0 4px;
  border: 0;
  background: none;
  color: #b9c4cb;
  font-size: 11px;
  letter-spacing: 0.4px;
  -webkit-tap-highlight-color: transparent;
}
.sg-tabbar-item[aria-current="page"] { color: #ff7e1c; }
.sg-tabbar-item[aria-current="page"]::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 28%;
  right: 28%;
  height: 2px;
  border-radius: 2px;
  background: #ff7e1c;
  box-shadow: 0 0 10px 1px rgba(255, 126, 28, 0.8);
}
/*
 * A phone on its side (D-1023): every vertical pixel is scene. The HUD folds
 * into one row, the tab bar is a rail down the left edge, and a screen's sheet
 * is a panel down the right (BottomSheet's side mode), so a screen's scene
 * window is the band between the two. The notch side takes its inset.
 */
@media ((max-height: 520px) and (orientation: landscape) and ((max-width: 900px) or ((max-height: 520px) and (pointer: coarse)))) {
  :root {
    --sg-rail-w: calc(76px + env(safe-area-inset-left, 0px));
    --sg-side-w: calc(min(44vw, 380px) + env(safe-area-inset-right, 0px));
  }
  .sg-game.sg-game {
    padding-left: var(--sg-rail-w) !important;
    padding-right: env(safe-area-inset-right, 0px) !important;
    padding-bottom: 24px !important;
  }
  .sg-hud-readouts.sg-hud-readouts { order: 0; flex-basis: auto !important; }
  #game-header > * { flex-wrap: nowrap !important; padding-top: 6px !important; padding-bottom: 6px !important; }

  .sg-tabbar {
    top: 0;
    right: auto;
    width: var(--sg-rail-w);
    grid-auto-flow: row;
    grid-auto-rows: minmax(0, 76px);
    align-content: center;
    padding: env(safe-area-inset-top, 0px) 0 env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
    border-top: 0;
    border-right: 1px solid rgba(127, 168, 232, 0.22);
  }
  .sg-tabbar-item { gap: 4px; min-height: 0; padding: 0 2px; font-size: 10px; letter-spacing: 0.2px; }
  .sg-tabbar-item[aria-current="page"]::before { top: 22%; bottom: 22%; left: auto; right: -1px; width: 2px; height: auto; }

  .sg-yard-window, .sg-muster-window, .sg-overview-window, .sg-map-window {
    left: var(--sg-rail-w);
    right: var(--sg-sheet-w, 0px);
    bottom: 0;
  }

  .sg-sheet {
    top: var(--sg-hud-h, 56px);
    bottom: 0;
    left: auto;
    right: 0;
    width: var(--sg-side-w);
    flex-direction: row;
    padding-right: env(safe-area-inset-right, 0px);
    border-top: 0;
    border-left: 1px solid rgba(127, 168, 232, 0.26);
    border-radius: 18px 0 0 0;
    box-shadow: -12px 0 32px -18px rgba(127, 168, 232, 0.35);
  }
  /* Folded away to its handle strip (24px, SIDE_HANDLE in BottomSheet.tsx). */
  .sg-sheet.sg-sheet-collapsed { transform: translateX(calc(100% - 24px)); }
  .sg-sheet.sg-sheet-covered { transform: translateX(18px); }
  .sg-sheet.sg-sheet-stacked { animation-name: sg-side-in; }
  @keyframes sg-side-in { from { transform: translateX(40%); opacity: 0; } }
  .sg-sheet .sg-sheet-handle { width: 24px; height: auto; min-height: 0; }
  .sg-sheet-handle span { width: 4px; height: 42px; }
  .sg-sheet-body { padding: 12px 12px 12px 0; }
  /* The tiles' 6px inset plus this is the same room on the glow's left. */
  .sg-yard-sheet .sg-sheet-body { padding-left: 12px; }

  /* Flat screens: a banner is its copy, not 120px of art, and the chat log
     leaves the composer on screen under it. */
  .sg-artwork.sg-artwork { min-height: 0 !important; height: auto !important; }
  .sg-artwork > :last-child { padding-top: 12px !important; padding-bottom: 12px !important; }
  /* Three roster tiles to a row in the side panel, not five squeezed ones. */
  .sg-roster-tile.sg-roster-tile { width: 30% !important; flex-basis: 30% !important; max-width: 33% !important; }
  .sg-chat-log.sg-chat-log { height: max(120px, calc(100dvh - var(--sg-hud-h, 56px) - 210px)) !important; }
}
.sg-more-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.sg-more-item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 84px;
  border: 1px solid rgba(127, 168, 232, 0.22);
  border-radius: 6px;
  color: #edf2f4;
  font-size: 13px;
  -webkit-tap-highlight-color: transparent;
}
.sg-more-item[aria-current="page"] { border-color: #ff7e1c; color: #ff7e1c; box-shadow: 0 0 4px 0px rgba(255, 126, 28, 0.35); }

/* Inside the app (apps/native stamps data-native on the root): an app, not a
   page. No rubber band, no text selection or long-press callout outside the
   fields, no tap flash. */
html[data-native], html[data-native] body { overscroll-behavior: none; }
html[data-native] body {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
html[data-native] input, html[data-native] textarea { -webkit-user-select: text; user-select: text; }

/* The glow system (prd/25 §1.3). Rest rings are always on; hover/press rings
   only exist on pointer devices, so a touch user never sees a stuck state. */

.sg-glow-brand { box-shadow: 0 0 4px 0px rgba(123, 98, 196, 0.35); }
.sg-glow-hover-brand:hover { box-shadow: 0 0 8px 1px rgba(123, 98, 196, 0.55); }
.sg-glow-hover-brand:active { box-shadow: 0 0 14px 2px rgba(123, 98, 196, 0.75); }
.sg-glow-hover-brand:disabled { box-shadow: none; }
.sg-glow-warning { box-shadow: 0 0 4px 0px rgba(255, 126, 28, 0.35); }
.sg-glow-hover-warning:hover { box-shadow: 0 0 8px 1px rgba(255, 126, 28, 0.55); }
.sg-glow-hover-warning:active { box-shadow: 0 0 14px 2px rgba(255, 126, 28, 0.75); }
.sg-glow-hover-warning:disabled { box-shadow: none; }
.sg-glow-success { box-shadow: 0 0 4px 0px rgba(92, 184, 92, 0.35); }
.sg-glow-hover-success:hover { box-shadow: 0 0 8px 1px rgba(92, 184, 92, 0.55); }
.sg-glow-hover-success:active { box-shadow: 0 0 14px 2px rgba(92, 184, 92, 0.75); }
.sg-glow-hover-success:disabled { box-shadow: none; }
.sg-glow-danger { box-shadow: 0 0 4px 0px rgba(255, 120, 101, 0.35); }
.sg-glow-hover-danger:hover { box-shadow: 0 0 8px 1px rgba(255, 120, 101, 0.55); }
.sg-glow-hover-danger:active { box-shadow: 0 0 14px 2px rgba(255, 120, 101, 0.75); }
.sg-glow-hover-danger:disabled { box-shadow: none; }
.sg-glow-info { box-shadow: 0 0 4px 0px rgba(127, 168, 232, 0.35); }
.sg-glow-hover-info:hover { box-shadow: 0 0 8px 1px rgba(127, 168, 232, 0.55); }
.sg-glow-hover-info:active { box-shadow: 0 0 14px 2px rgba(127, 168, 232, 0.75); }
.sg-glow-hover-info:disabled { box-shadow: none; }
