/* BraveUber — dark UI matching the in-game palette.
   Plain CSS, no build step and no CDN: the CSP is 'self' only. */

:root {
  --bg: #0e1116;
  --bg-raised: #161b22;
  --bg-inset: #1c232c;
  --border: #2b3440;
  --text: #d7dee7;
  --text-muted: #8b96a5;
  --accent: #f0a020;
  --accent-hover: #ffb845;
  --danger: #d9414d;

  /* Security-band colours, mirroring the in-game map. */
  --sec-high: #4fb477;
  --sec-low: #e0a13a;
  --sec-null: #d9414d;

  --radius: 6px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* The full-bleed map is 100vw wide, which counts the vertical scrollbar and
     would otherwise push the page itself into horizontal scroll. Nothing here
     needs the page to scroll sideways — wide tables and the map each scroll
     inside their own container. */
  overflow-x: hidden;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); text-decoration: underline; }

h1, h2, h3 { line-height: 1.25; font-weight: 600; }
h1 { font-size: 1.6rem; margin: 0 0 1rem; }
h2 { font-size: 1.2rem; margin: 2rem 0 .75rem; }

.muted { color: var(--text-muted); }
.empty { color: var(--text-muted); font-style: italic; }

/* --- Layout --- */
.container { max-width: 1100px; margin: 0 auto; padding: 1.5rem 1rem 3rem; }

.site-header {
  display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap;
  padding: .75rem 1.5rem;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
}
.brand { font-size: 1.25rem; font-weight: 700; color: var(--text); }
.brand span { color: var(--accent); }
.brand:hover { text-decoration: none; }

.site-header nav { display: flex; gap: 1.1rem; flex: 1; flex-wrap: wrap; }
.site-header nav a { color: var(--text-muted); font-size: .92rem; }
.site-header nav a:hover { color: var(--text); text-decoration: none; }

.session { display: flex; align-items: center; gap: .6rem; }
.session .who { font-size: .9rem; color: var(--text-muted); }

/* How many pilots are available right now. Recomputed per render, so it
   follows logins and logouts on its own. */
.pilot-count { font-size: .85rem; color: var(--text-muted); white-space: nowrap; }
.pilot-count:hover { color: var(--text); text-decoration: none; }
.pilot-count-value { font-weight: 700; color: var(--sec-high); }

/* Availability toggle, for pilots only. Reads as a status first and a control
   second, which is the right way round: most of the time you are glancing at
   it to check, not clicking it. The word says which state you are in, so the
   meaning does not rest on red-vs-green alone. */
.availability-form { display: flex; margin: 0; }
.availability-toggle {
  border: 1px solid transparent; border-radius: 999px;
  padding: .3rem .8rem; cursor: pointer;
  font: inherit; font-size: .72rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; white-space: nowrap;
}
.availability-toggle.is-available {
  background: color-mix(in srgb, var(--sec-high) 22%, var(--bg-raised));
  border-color: var(--sec-high); color: var(--sec-high);
}
.availability-toggle.is-available:hover {
  background: var(--sec-high); color: #10241a;
}
.availability-toggle.is-unavailable {
  background: color-mix(in srgb, var(--danger) 20%, var(--bg-raised));
  border-color: var(--danger); color: #f08c94;
}
.availability-toggle.is-unavailable:hover {
  background: var(--danger); color: #fff;
}
.availability-toggle:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* With more than one character the toggle becomes the head of a menu: the
   button still takes the whole account on or off the board, and the panel
   under it sets each character individually.

   The panel is a form of tick boxes, not a row of links, so several
   characters can be changed before anything is submitted. It opens on :hover
   and on :focus-within, with no JavaScript — the CSP allows no inline script,
   and focus-within earns its keep twice over: it makes the menu reachable by
   tab, and because ticking a box focuses it, the menu stays open even if the
   pointer leaves it mid-edit. The panel sits flush against the button
   (top: 100%) so moving onto it never crosses a dead gap. */
.availability-menu { position: relative; display: flex; }
.availability-list {
  display: none; position: absolute; z-index: 30;
  top: 100%; right: 0; min-width: 16rem;
  margin: 0; padding: .4rem;
  background: var(--bg-raised);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .45);
}
.availability-menu:hover .availability-list,
.availability-menu:focus-within .availability-list { display: block; }

.availability-rows { list-style: none; margin: 0; padding: 0; }
.availability-rows li {
  display: flex; align-items: center; gap: .5rem;
  padding: 0 .25rem; border-radius: 4px;
}
.availability-rows li:hover { background: var(--bg-inset); }
.availability-check { flex: 0 0 auto; margin: 0; accent-color: var(--sec-high); }
.availability-rows label {
  display: flex; flex: 1 1 auto; align-items: center;
  justify-content: space-between; gap: .75rem;
  padding: .35rem .25rem; font-size: .85rem; cursor: pointer;
}
.availability-row-name { white-space: nowrap; }
/* The state repeats the colour in words, so the row still reads correctly to
   anyone who cannot separate the two colours. Both words are in the markup and
   only the one matching the box is shown, so a row that has been ticked but
   not yet applied already reads as available. */
.availability-row-state {
  font-size: .68rem; font-weight: 700; letter-spacing: .06em; white-space: nowrap;
}
.state-on { color: var(--sec-high); }
.state-off { color: #f08c94; }
.availability-check:not(:checked) + label .state-on,
.availability-check:checked + label .state-off { display: none; }

.availability-list-actions {
  display: flex; gap: .35rem;
  margin-top: .4rem; padding-top: .4rem;
  border-top: 1px solid var(--border);
}
.availability-list-actions .btn { flex: 1 1 auto; text-align: center; }
/* Apply leads the markup so Enter submits it rather than All; it reads last. */
.availability-list-actions .btn-apply { order: 3; }
.availability-list-foot {
  margin: .4rem 0 0; padding-top: .4rem;
  border-top: 1px solid var(--border); font-size: .8rem;
}

/* --- Your characters --- */
/* One card per registered character. An account may hold several — a titan
   pilot in one system, a carrier pilot in another — so each card carries the
   facts that differ between them and its own availability control. */
.character-list { list-style: none; padding: 0; margin: 0 0 1.5rem; display: grid; gap: 1rem; }
.character-card {
  display: flex; align-items: flex-start; gap: 1rem;
  background: var(--bg-raised); border: 1px solid var(--border);
  border-left: 3px solid var(--sec-high);
  border-radius: var(--radius); padding: 1rem 1.25rem;
}
/* A character who is off the board says so at a glance, without hiding it. */
.character-card.is-dark { border-left-color: var(--danger); }
.character-card.is-dark .character-portrait { filter: grayscale(.7); opacity: .75; }

.character-portrait { margin: 0; flex: 0 0 auto; }
.character-body { flex: 1 1 auto; min-width: 0; }
.character-name { font-size: 1.05rem; margin: 0 0 .5rem; }
.character-facts {
  display: grid; grid-template-columns: 6rem 1fr; gap: .25rem .75rem;
  margin: 0; font-size: .9rem;
}
.character-facts dt { color: var(--text-muted); font-size: .85rem; }
.character-facts dd { margin: 0; }
.character-notes {
  margin: .6rem 0 0; color: var(--text-muted); font-size: .88rem;
  white-space: pre-wrap;
}

.character-actions {
  display: flex; flex-direction: column; align-items: stretch; gap: .4rem;
  flex: 0 0 auto;
}
.character-actions form { margin: 0; }
.character-actions .btn { text-align: center; }
.character-actions .availability-toggle { width: 100%; }
/* Destructive, but recoverable — the character can be added back with their
   history. Muted until hovered, so it does not compete with Edit. */
.btn-remove:hover { border-color: var(--danger); color: #f08c94; background: transparent; }

.character-heading-portrait { vertical-align: middle; margin-right: .5rem; }
.crumb { font-size: .88rem; margin: 0 0 .75rem; }
.add-character { max-width: 34rem; }

@media (max-width: 640px) {
  .character-card { flex-wrap: wrap; }
  .character-actions { flex-direction: row; width: 100%; }
}

/* --- Stats --- */
.stats { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
.stat {
  flex: 1 1 12rem; background: var(--bg-raised);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem;
}
.stat-value { display: block; font-size: 1.8rem; font-weight: 700; color: var(--accent); }
.stat-label { color: var(--text-muted); font-size: .85rem; }

/* --- Misc --- */
.ratings { list-style: none; padding: 0; }
.ratings li {
  padding: .6rem .8rem; background: var(--bg-raised);
  border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: .5rem;
}
.pagination { display: flex; gap: 1rem; align-items: center; margin-top: 1rem; color: var(--text-muted); font-size: .9rem; }

@media (max-width: 640px) {
  .site-header { gap: .75rem; }
  .detail { grid-template-columns: 1fr; }
  .detail dt { margin-top: .5rem; }
}

/* --- Star map ---------------------------------------------------------
   Grouped by region and constellation rather than plotted to scale. Region
   fills are the first three categorical slots of the data-viz reference
   palette, stepped for a dark surface and validated with the palette
   validator against #161b22 on the all-pairs list (worst CVD deltaE 9.4,
   worst normal-vision 20.9, all >= 3:1 contrast).

   Markers are lightly filled so the system name stays the loudest thing in
   the box; the same hue at full strength provides the border. Colour is
   reinforcement only — every marker is directly labelled and there is a
   legend, so nothing here depends on telling two hues apart. */

/* Full-bleed: the map is far wider than the 1100px text column, so it breaks
   out to the full window width. No max-height — the container grows to the
   map's full height rather than trapping it in an inner scroller. Only the
   horizontal axis scrolls, and only when the map is wider than the window. */
.map-wrap {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  background: var(--bg-raised);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  margin-bottom: .75rem;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
}
.starmap {
  /* Natural size, so text stays legible; the wrapper scrolls horizontally if
     the window is narrower. width/height come from the element's attributes. */
  display: block; max-width: none; margin: 0 auto;
}

/* Region hues. Each slot supplies a full-strength ink and a light fill. */
.map-region-region-1     { --region-ink: #3987e5; }
.map-region-region-2     { --region-ink: #d95926; }
.map-region-region-3     { --region-ink: #199e70; }
.map-region-region-other { --region-ink: #8b96a5; }
.map-node-region-1     { --region-ink: #3987e5; }
.map-node-region-2     { --region-ink: #d95926; }
.map-node-region-3     { --region-ink: #199e70; }
.map-node-region-other { --region-ink: #8b96a5; }

.swatch-region-1     { background: #3987e5; }
.swatch-region-2     { background: #d95926; }
.swatch-region-3     { background: #199e70; }
.swatch-region-other { background: #8b96a5; }
.swatch-link         { background: var(--border); }

/* Region band: a very light wash, just enough to group. */
.map-region rect {
  fill: color-mix(in srgb, var(--region-ink) 7%, transparent);
  stroke: color-mix(in srgb, var(--region-ink) 35%, transparent);
  stroke-width: 1;
}
.region-label {
  font-size: 13px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; fill: var(--region-ink);
}

/* Constellations are labelled, not boxed. Dropping the container removed the
   blockiness and, with it, any box for a link to appear to cut through — the
   grouping now reads from position and from the link colouring below. */
.const-label {
  font-size: 10px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase;
  fill: color-mix(in srgb, var(--text-muted) 75%, transparent);
}

/* Stargate links carry the boundary they cross, the way dotlan's do. This is
   what replaces the constellation boxes: a route's structure is visible from
   the lines alone. Local links recede; boundary crossings are the ones a
   route actually turns on, so they read progressively stronger. */
.map-links line,
.map-links path { fill: none; stroke-linecap: round; }

.map-links .link-local {
  stroke: var(--text-muted); stroke-width: 1.25; opacity: .45;
}
.map-links .link-constellation {
  stroke: var(--sec-low); stroke-width: 1.5; opacity: .9;
}
.map-links .link-region {
  stroke: #9085e9; stroke-width: 1.75; opacity: .95;
}

/* Ansiblex jump bridge — thick and magenta. Heavier than any gate link,
   because it is a deliberate piece of alliance infrastructure rather than
   terrain, and it has to stay legible where it runs alongside the gate links
   it parallels. Rendered last, so it sits above them.

   #cc3399 is 3.7:1 against the map surface, clearing the 3:1 floor for a
   graphical object, and it is far enough from the violet region-crossing links
   in both hue and lightness that the two never read as the same line. It also
   keeps the bridges clear of the green used by the transport range highlight,
   which can be lit at the same time. */
.map-links .link-ansiblex {
  stroke: #cc3399; stroke-width: 6; opacity: 1;
  /* Bridges are routed as polylines around the markers in the way, so they
     have real corners. The join rounds them visually without moving the
     geometry — the clearance the router proved still holds. */
  stroke-linejoin: round;
}

/* Invisible, wider twin of each bridge: the thing the pointer actually has to
   find. Painted with a transparent stroke rather than `none`, because a stroke
   of none takes no pointer events at all. */
.link-ansiblex-hit {
  stroke: transparent; stroke-width: 20; fill: none;
  stroke-linejoin: round; stroke-linecap: round;
}
/* The visible line must not swallow the hover — the hit area is doing that. */
.map-bridge .link-ansiblex { pointer-events: none; }
/* The attribute is set by the script that handles the hover, so the pointer
   only promises interactivity that has actually loaded. Not keyed on
   .map-interactive: that belongs to the range picker, which is absent when no
   transport types exist, and bridges are hoverable either way. */
.map-bridge[tabindex] { cursor: pointer; }

/* Hovering or focusing a bridge brightens it; the JS lights its two systems. */
.map-bridge:hover .link-ansiblex,
.map-bridge:focus-visible .link-ansiblex,
.map-bridge.bridge-active .link-ansiblex {
  stroke: #ef5fc0; stroke-width: 7;
}
.map-bridge:focus { outline: none; }

.swatch-link-local         { background: var(--text-muted); }
.swatch-link-constellation { background: var(--sec-low); }
.swatch-link-region        { background: #9085e9; }
.swatch-link-ansiblex      { background: #cc3399; }

/* System marker: rounded rectangle, lightly filled, region-coloured. */
.map-node rect {
  fill: color-mix(in srgb, var(--region-ink) 18%, var(--bg-raised));
  stroke: var(--region-ink); stroke-width: 1.25;
}
.map-node text {
  font-size: 10px; font-weight: 600; fill: var(--text);
  text-anchor: middle; pointer-events: none;
}
.map-node:hover rect { fill: color-mix(in srgb, var(--region-ink) 32%, var(--bg-raised)); }

/* Route highlight, applied by the distance calculator. */
.map-node-route rect {
  fill: var(--accent); stroke: var(--text); stroke-width: 2;
}
.map-node-route text { fill: #16191d; font-weight: 700; }

/* Transport range highlight. Only advertised as clickable once the script that
   handles the click has loaded — the class is added by the JS. */
.map-interactive .map-node { cursor: pointer; }
/* SVG shapes take an outline inconsistently across browsers, so focus is shown
   on the marker's own stroke instead. */
.map-node:focus { outline: none; }
.map-node:focus-visible rect { stroke: var(--accent); stroke-width: 2.5; }

/* Out-of-range markers fade rather than vanish: judging what a transport
   reaches means seeing what it doesn't. These rules come after the route
   highlight above, so a system that is both keeps the range colour — that is
   the question being asked while a range is on screen. */
.starmap.range-active .map-node { opacity: .28; }
.starmap.range-active .map-node-in-range,
.starmap.range-active .map-node-origin { opacity: 1; }

/* Systems the transport reaches: a green wash, deliberately a *tint* so a
   whole cluster of them stays readable rather than turning into a solid slab. */
.map-node-in-range rect {
  fill: color-mix(in srgb, var(--sec-high) 38%, var(--bg-raised));
  stroke: var(--sec-high); stroke-width: 1.5;
}
.map-node-in-range text { fill: var(--text); font-weight: 700; }

/* The system you clicked. Amber against the in-range green, and it does not
   rest on hue alone: solid fill against a tint, a pale ring instead of a
   coloured edge, and twice the stroke weight. Those read as "this one" even
   where the red-green difference does not. */
.map-node-origin rect {
  fill: var(--accent); stroke: #f6f8fa; stroke-width: 3;
}
.map-node-origin text { fill: #16191d; font-weight: 700; }

.swatch-origin { background: var(--accent); }

/* The two systems a hovered bridge joins. Magenta to match the connector, so
   the link between line and endpoints is made by colour and not just by
   position — which is the whole point when the line takes a long detour. */
.map-node-bridge-end rect {
  fill: #cc3399; stroke: #f6f8fa; stroke-width: 2.5;
}
.map-node-bridge-end text { fill: #ffffff; font-weight: 700; }
/* Must out-rank the range dimming: pointing at a bridge is a direct question
   about those two systems, whatever else is lit at the time. */
.starmap.range-active .map-node-bridge-end { opacity: 1; }

.swatch-in-range { background: var(--sec-high); }

/* --- Legend --- */
.map-legend {
  list-style: none; display: flex; flex-wrap: wrap; gap: 1rem;
  padding: 0; margin: 0 0 .6rem; font-size: .85rem; color: var(--text-muted);
}
/* Give the map page room to breathe; the full-bleed map ignores this anyway. */
.map-page { max-width: 1400px; }
.map-legend li { display: flex; align-items: center; gap: .4rem; }
.swatch {
  width: 14px; height: 10px; border-radius: 3px; display: inline-block;
  border: 1px solid rgba(255,255,255,.15);
}

/* --- Distance calculator --- */
.calc { display: flex; gap: 1rem; align-items: flex-end; flex-wrap: wrap; }
.calc label {
  display: flex; flex-direction: column; gap: .3rem;
  font-size: .85rem; color: var(--text-muted); font-weight: 600;
}
.calc-result {
  display: block; padding: .45rem .75rem; border-radius: var(--radius);
  background: var(--bg-inset); border: 1px solid var(--border);
  font-weight: 600; color: var(--accent); min-width: 14rem;
}
.calc-warn { color: var(--sec-low); }
