/* Site-specific styling layered on top of the Dimension theme
   (assets/css/main.css). Multi-page nav/footer/cards/tables/forms that
   the original one-page template didn't need, kept in the same dark
   palette (#1b1f22 / #000 / #fff) with a single accent color added. */

/* Defensive safety net for mobile: found the hard way that a single
   invisible-until-hover tooltip (position:absolute, centered over a
   narrow tile) was enough to silently inflate the entire page's mobile
   layout viewport, because an absolutely positioned element still
   counts toward scrollable overflow even when nothing about it is
   visible. That specific case is fixed at its source (the tooltip's own
   max-width, below), but this is cheap, has no effect on any page that's
   already correctly contained, and catches the same failure mode from
   any future decorative element without needing to hunt it down again. */
html, body {
  overflow-x: hidden;
}

:root {
  --bg: #1b1f22;
  --bg-alt: #000000;
  --fg: #ffffff;
  --fg-light: rgba(255, 255, 255, 0.6);
  --border: rgba(255, 255, 255, 0.15);
  --border-strong: rgba(255, 255, 255, 0.3);
  --accent: #3aa0ff;
  --accent-dim: rgba(58, 160, 255, 0.15);
  --good: #4ade80;
  --bad: #f87171;
  --warn: #fbbf24;

  /* Raised surfaces -- cards/tiles sit on their own panel color rather
     than being outlined holes in the page background, which is what
     gives the layout actual depth instead of a flat wireframe look. */
  --surface: #1d2127;
  --surface-border: #333a44;

  /* Second end of the headline gradient. Anchored to the existing blue
     accent rather than an unrelated purple, so the hero reads as the
     same design system as every project page, just turned up. */
  --accent-violet: #b388ff;
  --accent-glow: rgba(122, 138, 255, 0.35);
}

/* ---------- Typography ----------
   Space Grotesk over the theme's default Source Sans Pro: it has real
   character in the wide, uppercase, letter-spaced headings this site
   leans on, where a neutral UI sans just reads as unstyled. JetBrains
   Mono replaces Courier New everywhere something is deliberately
   terminal/numeric -- the build log, stat values, tabular data. */

body,
input,
select,
textarea,
button {
  font-family: "Space Grotesk", "Source Sans Pro", Helvetica, sans-serif;
}

h1, h2, h3, h4, h5, h6 {
  font-family: "Space Grotesk", "Source Sans Pro", Helvetica, sans-serif;
}

/* The Dimension theme's own h1.major/h2.major/etc. rule (main.css) sets
   `width: max-content` so the underline hugs the heading text instead of
   spanning the full container -- a fine desktop touch, but max-content
   ignores the viewport entirely: on a narrow screen it forces every page
   title wider than the device (e.g. "Trading Simulator / PnL Tracker" at
   713px in a 375px viewport), which doesn't just overflow that one
   element -- it expands the whole page's layout viewport to fit it, so
   the ENTIRE page renders zoomed out instead of just the heading
   wrapping. `max-width: 100%` keeps the max-content sizing (so it still
   hugs short titles exactly as designed) while capping it at whatever
   room is actually available, letting long titles wrap normally. */
h1.major, h2.major, h3.major, h4.major, h5.major, h6.major {
  max-width: 100%;
}

/* ---------- Gradient headline ---------- */

.gradient-text {
  background: linear-gradient(96deg, var(--accent) 5%, #7f9cff 45%, var(--accent-violet) 95%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

/* ---------- Scroll reveal ----------
   Elements start pushed a full self-height down and fade up as their
   section scrolls into view, staggered in source order (delay applied
   per-element by base.js). Gated behind html.js-reveal -- set by an
   inline script in <head> so it applies before first paint -- meaning a
   no-JS visitor gets the finished state instead of a blank page. */

.reveal-up {
  transition:
    opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}

html.js-reveal .reveal-up {
  opacity: 0;
  transform: translateY(100%);
}

html.js-reveal .reveal-up.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Clips the travel so the content wipes up from behind an edge rather
   than visibly sliding over whatever sits below it. */
.reveal-mask {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  html.js-reveal .reveal-up {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ---------- Section headings (eyebrow + title) ---------- */

.section-head {
  text-align: center;
  margin: 5rem 0 2.5rem;
}

.section-head .eyebrow {
  display: block;
  color: var(--accent);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.28rem;
  text-transform: uppercase;
  margin-bottom: 0.9rem;
}

.section-head h2 {
  font-size: clamp(1.7rem, 4vw, 2.4rem);
  margin: 0;
}

.section-head .section-sub {
  color: var(--fg-light);
  margin: 1rem auto 0;
  max-width: 34rem;
}

/* ---------- Page wrapper stacking ----------
   main.css's #bg layer is `position: fixed; z-index: 1/2` (see
   assets/sass/layout/_bg.scss) -- Dimension's own #wrapper sits above it
   via `position: relative; z-index: 3`. This site uses a different DOM
   shape (top nav instead of one big hero + #main), so it needs its own
   rule doing the same job rather than reusing #wrapper's centered flex
   layout, which assumes Dimension's original one-pager structure. */

#page-wrapper {
  position: relative;
  z-index: 3;
  min-height: 100vh;
  width: 100%;
  display: flex;
  flex-direction: column;
}

/* ---------- Background scrim ----------
   Dimension ships #bg as a photo (bg.jpg) behind a 50% scrim
   (#bg:before in main.css). That photo is genuinely light -- it averages
   rgb(126,129,147), and its brightest regions average rgb(174,192,215) --
   so at 50% the muted body text (rgba(255,255,255,0.6)) came out at a
   measured 3.21:1 contrast over those regions, under the 4.5:1 WCAG AA
   floor and visibly hard to read. Deepening the scrim to 82% puts the
   same text at 5.96:1 and plain white at 13.65:1, while still leaving
   enough of the photo to read as texture rather than a flat fill. */

#bg:before {
  background-image:
    linear-gradient(to top, rgba(12, 14, 17, 0.82), rgba(12, 14, 17, 0.82)),
    url("/static/assets/img/overlay.png");
}

/* ---------- Header / nav ---------- */

.site-header {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 1rem 2rem;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(27, 31, 34, 0.92);
  backdrop-filter: blur(6px);
  flex-wrap: wrap;
}

.site-header .brand {
  font-weight: 600;
  letter-spacing: 0.1rem;
  text-transform: uppercase;
  font-size: 0.95rem;
  border-bottom: none;
  color: var(--fg);
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-right: auto;
}

.site-nav {
  display: flex;
  gap: 1.5rem;
}

.site-nav a {
  font-size: 0.8rem;
  letter-spacing: 0.15rem;
  text-transform: uppercase;
  border-bottom: none;
  color: var(--fg-light);
}

.site-nav a:hover,
.site-nav a.is-active {
  color: var(--accent);
}

.site-header-links {
  display: flex;
  gap: 1rem;
}

.site-header-links .icon {
  font-size: 1rem;
  color: var(--fg-light);
}

.site-header-links .icon:hover {
  color: var(--accent);
}

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--border);
  color: var(--fg);
  border-radius: 4px;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
}

@media screen and (max-width: 736px) {
  .site-nav {
    display: none;
    width: 100%;
    flex-direction: column;
    gap: 0.75rem;
    order: 3;
  }
  .site-nav.is-open {
    display: flex;
  }
  .nav-toggle {
    display: inline-block;
  }
}

/* ---------- Flash messages ---------- */

.flash-container {
  max-width: 960px;
  margin: 1rem auto 0;
  padding: 0 2rem;
}

.flash {
  border-radius: 4px;
  padding: 0.75rem 1.1rem;
  margin-bottom: 0.75rem;
  font-size: 0.9rem;
  border: 1px solid var(--border);
}

.flash-success {
  border-color: var(--good);
  color: var(--good);
}

.flash-error {
  border-color: var(--bad);
  color: var(--bad);
}

/* ---------- Page shell ---------- */

#content {
  max-width: 960px;
  margin: 0 auto;
  padding: 2.5rem 2rem 4rem;
  width: 100%;
  flex: 1 0 auto;
}

#content.wide {
  max-width: 1200px;
}

/* ---------- Disclaimer banner ---------- */

.disclaimer-banner {
  border: 1px solid var(--warn);
  color: var(--warn);
  border-radius: 4px;
  padding: 0.85rem 1.1rem;
  font-size: 0.85rem;
  margin-bottom: 2rem;
  line-height: 1.5;
}

.site-footer .disclaimer {
  font-size: 0.75rem;
  color: var(--fg-light);
  max-width: 960px;
  margin: 0 auto;
  padding: 0 2rem;
  line-height: 1.6;
}

.site-footer {
  border-top: 1px solid var(--border);
  padding: 2rem 0;
  margin-top: 3rem;
}

.site-footer .footer-links {
  display: flex;
  gap: 1.5rem;
  justify-content: center;
  margin: 1rem 0 0.5rem;
  font-size: 0.8rem;
}

.site-footer .footer-credit {
  text-align: center;
  font-size: 0.7rem;
  color: var(--fg-light);
  margin-top: 0.75rem;
}

/* ---------- Hero (home page) ---------- */

.hero {
  position: relative;
  text-align: center;
  padding: 5rem 0 4rem;
  isolation: isolate;
}

/* Two stacked background layers, same idea as the template's dot field
   under a dark gradient wash: a fine dot grid for texture, and a radial
   glow centred on the headline so the name has a light source behind it
   instead of sitting on flat black. Both are pure CSS -- no extra image
   asset to ship -- and sit at z-index -1 under the hero's own content. */
/* Horizontal inset stays at 0 rather than bleeding out to -50vw: an
   absolutely-positioned child wider than the viewport pushes the whole
   document's scrollWidth out and creates a horizontal scrollbar, since
   nothing in the ancestor chain clips it. The radial mask below fades
   the dots out well inside the content width anyway, so full-bleed
   bought nothing visually. */
.hero::before {
  content: "";
  position: absolute;
  inset: -3rem 0 auto 0;
  height: 34rem;
  background-image:
    radial-gradient(circle at center, rgba(255, 255, 255, 0.07) 1px, transparent 1px);
  background-size: 22px 22px;
  -webkit-mask-image: radial-gradient(ellipse 45% 70% at 50% 30%, #000 20%, transparent 75%);
  mask-image: radial-gradient(ellipse 45% 70% at 50% 30%, #000 20%, transparent 75%);
  z-index: -2;
  pointer-events: none;
}

.hero::after {
  content: "";
  position: absolute;
  inset: -6rem -2rem auto -2rem;
  height: 30rem;
  background: radial-gradient(ellipse 55% 100% at 50% 10%, var(--accent-glow), transparent 70%);
  filter: blur(30px);
  opacity: 0.7;
  z-index: -1;
  pointer-events: none;
}

.hero-eyebrow {
  margin-bottom: 1.75rem;
}

.hero h1 {
  font-size: clamp(2.4rem, 8vw, 4.5rem);
  line-height: 1.12;
  letter-spacing: 0.22rem;
  margin-bottom: 1.25rem;
  /* The gradient fill needs a hair of vertical room or the descenders
     and the clipped background get cropped by the reveal mask. */
  padding: 0.1em 0;
}

.hero .tagline {
  color: var(--fg-light);
  font-size: 1.05rem;
  max-width: 34rem;
  margin: 0 auto 2.25rem;
}

.hero-actions {
  display: flex;
  gap: 1rem;
  justify-content: center;
  flex-wrap: wrap;
  padding: 0.35rem 0;
}

.tech-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  margin: 3rem auto 0;
  max-width: 40rem;
}

/* ---------- CTA band ---------- */

.cta-band {
  position: relative;
  text-align: center;
  padding: 4rem 2rem;
  margin: 5rem 0 1rem;
  border: 1px solid var(--surface-border);
  border-radius: 12px;
  background:
    radial-gradient(ellipse 60% 120% at 50% 0%, var(--accent-dim), transparent 70%),
    var(--surface);
  overflow: hidden;
}

.cta-band h2 {
  font-size: clamp(1.6rem, 4vw, 2.2rem);
  margin-bottom: 0.75rem;
}

.cta-band p {
  margin-bottom: 2rem;
}

/* ---------- Cards ---------- */

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.5rem;
  margin: 2rem 0;
}

.card {
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  background: var(--surface);
  padding: 1.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  transition:
    border-color 0.25s ease-in-out,
    transform 0.25s ease-in-out,
    box-shadow 0.25s ease-in-out;
}

.card:hover {
  border-color: var(--accent);
  transform: translateY(-4px);
  box-shadow: 0 16px 40px -18px var(--accent-glow);
}

.card-icon {
  width: 48px;
  height: 48px;
  opacity: 0.95;
}

.card h3 {
  margin: 0;
  font-size: 1.1rem;
}

.page-icon {
  width: 40px;
  height: 40px;
  vertical-align: middle;
  margin-right: 0.75rem;
}

.card p {
  color: var(--fg-light);
  font-size: 0.9rem;
  flex-grow: 1;
}

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.tag {
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.05rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0.2rem 0.5rem;
  color: var(--fg-light);
}

/* ---------- Stat tiles ---------- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(155px, 1fr));
  gap: 1rem;
  margin: 1.5rem 0;
}

/* Flex column with the label allowed to take however many lines it
   needs, rather than relying on the tile's intrinsic height matching
   whatever the text happens to wrap to. A two-line label ("IR Delta
   (Rho)", "Scenario Gamma") was spilling past the bottom border on
   narrower columns; letting the label grow inside the tile and giving
   every tile the same min-height keeps a mixed row of one- and two-line
   labels aligned instead of ragged. */
.stat-tile {
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  background: var(--surface);
  padding: 1.4rem 0.9rem;
  text-align: center;
  transition: border-color 0.25s ease-in-out;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.35rem;
  min-height: 6.5rem;
}

.stat-tile:hover {
  border-color: var(--accent);
}

.stat-tile .value {
  font-size: 1.55rem;
  font-weight: 600;
  line-height: 1.2;
  /* `anywhere` used to be here to stop a long value (a wide PV, a
     scenario PnL) from punching out through the tile's side -- but for
     a plain digit string with no natural break point, "anywhere" means
     literally any character boundary, so it was splitting numbers
     mid-digit ("$375" / "6570" / ".57") instead of wrapping at a
     sensible point, which reads as broken rather than merely long.
     A smaller font plus a slightly wider minimum tile (see .stat-grid)
     fits the overwhelming majority of real values on one line;
     genuinely extreme ones (see risk_report.html's money formatting,
     which abbreviates to e.g. "$3.76M" past a threshold) are handled by
     not producing an unreasonably long string in the first place,
     rather than by chopping whatever string shows up -- so it's safe to
     fall back to a normal wrap instead of forcing an ugly one. */
  overflow-wrap: normal;
  word-break: normal;
}

.stat-tile .label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08rem;
  color: var(--fg-light);
  line-height: 1.35;
}

/* ---------- Explain-on-hover tooltips ----------
   Used on the risk metrics, where a bare number like "Scenario Gamma:
   0.368" means nothing without knowing what was actually computed. A
   styled tooltip rather than the `title` attribute: title has a ~1s
   delay, can't hold more than a short phrase legibly, and can't be
   opened from the keyboard at all. This shows on hover *and* on
   keyboard focus, which is why the elements carry tabindex="0". */

[data-tooltip] {
  position: relative;
  cursor: help;
}

[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 0.6rem);
  left: 50%;
  transform: translateX(-50%) translateY(0.25rem);
  width: max-content;
  /* Capped at the viewport width too, not just 20rem: a fixed max-width
     lets a tooltip centered over a narrow tile extend past the screen
     edge on mobile. Even though it's invisible until hover/focus
     (opacity/visibility, not display:none), an absolutely positioned
     element still counts toward the page's scrollable width -- so an
     off-screen tooltip silently inflated the whole page's mobile layout
     viewport, on every page with a data-tooltip tile, whether or not
     anyone ever actually hovered one. */
  max-width: min(20rem, calc(100vw - 2rem));
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  background: #0f1216;
  color: var(--fg);
  font-size: 0.72rem;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
  text-transform: none;
  text-align: left;
  white-space: normal;
  box-shadow: 0 12px 30px -10px rgba(0, 0, 0, 0.8);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease-out, transform 0.15s ease-out;
  /* Above neighbouring tiles, which would otherwise paint over it. */
  z-index: 20;
  pointer-events: none;
}

[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

/* Edge tiles can't centre a 20rem tooltip over themselves without it
   running off the viewport. CSS alone can't detect this: the tiles wrap
   into grid rows that aren't separate elements, so :first-child only
   knows about the very first tile in the whole grid, not the first one
   in each row -- which is exactly where the left-hand overflow happened.
   base.js measures on hover/focus and adds one of these classes. */
[data-tooltip].tip-align-left::after {
  left: 0;
  right: auto;
  transform: translateX(0) translateY(0.25rem);
}

[data-tooltip].tip-align-left:hover::after,
[data-tooltip].tip-align-left:focus-visible::after {
  transform: translateX(0) translateY(0);
}

[data-tooltip].tip-align-right::after {
  left: auto;
  right: 0;
  transform: translateX(0) translateY(0.25rem);
}

[data-tooltip].tip-align-right:hover::after,
[data-tooltip].tip-align-right:focus-visible::after {
  transform: translateX(0) translateY(0);
}

[data-tooltip]:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- Badges / PnL coloring ---------- */

.badge {
  display: inline-block;
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.05rem;
  padding: 0.15rem 0.5rem;
  border-radius: 3px;
  border: 1px solid var(--border);
}

.badge-open {
  border-color: var(--accent);
  color: var(--accent);
}

.badge-closed {
  color: var(--fg-light);
}

/* Cards are flex columns, so a bare badge would stretch the full card
   width and read as a greyed-out button rather than a status label. */
.card .badge {
  align-self: flex-start;
  padding: 0.4rem 0.8rem;
}

.pnl-positive {
  color: var(--good);
}

.pnl-negative {
  color: var(--bad);
}

/* ---------- Forms ---------- */

input[type="number"],
input[type="date"] {
  appearance: none;
  background-color: transparent;
  border-radius: 4px;
  border: 1px solid var(--border-strong);
  color: inherit;
  display: block;
  outline: 0;
  padding: 0 1rem;
  width: 100%;
  height: 2.75rem;
}

input[type="number"]:focus,
input[type="date"]:focus {
  background: var(--accent-dim);
  border-color: var(--accent);
}

.field-hint {
  font-size: 0.7rem;
  color: var(--fg-light);
  margin-top: -1.25rem;
  margin-bottom: 1rem;
}

/* ---------- Charts / tables ---------- */

.chart-wrapper {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1rem;
  margin: 1.5rem 0;
  background: rgba(255, 255, 255, 0.02);
}

.table-wrapper {
  margin-bottom: 1.5rem;
  /* Without this, a table wider than the viewport doesn't just overflow
     its own box -- mobile browsers expand the whole page's layout
     viewport to fit it (window.innerWidth grows past the actual device
     width), so the entire page renders zoomed out instead of just this
     one table scrolling. Found on the trade book's own table, the
     oldest one on the site, predating the .data-table-wrapper +
     .data-table-scroll pattern used everywhere newer -- six templates
     were still on this older class with no scroll containment at all. */
  overflow-x: auto;
}

section + section {
  margin-top: 2.5rem;
}

.section-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.muted {
  color: var(--fg-light);
}

.mono {
  font-family: 'JetBrains Mono', 'Courier New', monospace;
}

.empty-state {
  border: 1px dashed var(--border);
  border-radius: 6px;
  padding: 2rem;
  text-align: center;
  color: var(--fg-light);
}

/* ---------- Live traffic table (Network Sniffer) ----------
   Denser data-table treatment than the default table styling: pill
   badges for direction/method/status instead of plain text, a scrollable
   fixed-height body with a sticky header so 200 rows doesn't turn the
   whole page into one long scroll, and a brief highlight flash on rows
   as they arrive live via SSE. */

.data-table-wrapper {
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  margin-bottom: 1.5rem;
}

.data-table-scroll {
  max-height: 480px;
  overflow-y: auto;
}

.traffic-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
  margin: 0;
}

.traffic-table th {
  position: sticky;
  top: 0;
  background: #20252a;
  color: var(--fg);
  text-align: left;
  padding: 0.65rem 0.9rem;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05rem;
  border-bottom: 1px solid var(--border);
  z-index: 1;
}

.traffic-table td {
  padding: 0.55rem 0.9rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  vertical-align: middle;
}

.traffic-table tbody tr:nth-child(2n) {
  background: rgba(255, 255, 255, 0.02);
}

.traffic-table tbody tr:hover {
  background: rgba(58, 160, 255, 0.06);
}

.traffic-table td.target-cell {
  word-break: break-all;
  max-width: 480px;
}

.traffic-table td.time-cell,
.traffic-table td.duration-cell {
  white-space: nowrap;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.pill {
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.03rem;
  text-transform: uppercase;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  border: 1px solid transparent;
  white-space: nowrap;
}

.pill-in { background: rgba(58, 160, 255, 0.15); color: var(--accent); border-color: rgba(58, 160, 255, 0.35); }
.pill-out { background: rgba(255, 255, 255, 0.08); color: var(--fg-light); border-color: var(--border); }

.pill-method-GET { background: rgba(74, 222, 128, 0.12); color: var(--good); border-color: rgba(74, 222, 128, 0.3); }
.pill-method-POST { background: rgba(251, 191, 36, 0.12); color: var(--warn); border-color: rgba(251, 191, 36, 0.3); }
.pill-method-other { background: rgba(255, 255, 255, 0.08); color: var(--fg-light); border-color: var(--border); }

.pill-status-2 { background: rgba(74, 222, 128, 0.12); color: var(--good); border-color: rgba(74, 222, 128, 0.3); }
.pill-status-3 { background: rgba(58, 160, 255, 0.12); color: var(--accent); border-color: rgba(58, 160, 255, 0.3); }
.pill-status-4 { background: rgba(251, 191, 36, 0.12); color: var(--warn); border-color: rgba(251, 191, 36, 0.3); }
.pill-status-5 { background: rgba(248, 113, 113, 0.12); color: var(--bad); border-color: rgba(248, 113, 113, 0.3); }
.pill-status-none { background: rgba(255, 255, 255, 0.05); color: var(--fg-light); border-color: var(--border); }

@keyframes row-flash-in {
  from { background-color: rgba(58, 160, 255, 0.22); }
  to { background-color: transparent; }
}

.row-flash-in {
  animation: row-flash-in 1.4s ease-out;
}

/* In-cell proportional bar for the "outbound calls by host" table --
   same idea as Great Tables' inline data bars, sized relative to the
   busiest host in the current snapshot. */

.host-bar-cell {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.host-bar-track {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
}

.host-bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 3px;
  transition: width 0.3s ease-out;
}

.host-bar-count {
  min-width: 1.5rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: 0.8rem;
}

/* Volume-over-time chart on the Site Traffic Analytics board -- one
   column per time bucket, inbound/outbound stacked bottom-up so the
   split is visible without a legend fight. Heights are set inline
   (proportional to the busiest bucket), not here. */

.volume-chart {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 140px;
  margin: 1rem 0 0.4rem;
  padding: 0 0.25rem;
}

.volume-bar-col {
  flex: 1;
  display: flex;
  flex-direction: column-reverse;
  height: 100%;
  min-width: 6px;
  border-radius: 3px 3px 0 0;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.04);
}

.volume-bar-in {
  background: var(--accent);
  width: 100%;
}

.volume-bar-out {
  background: var(--accent-violet, var(--fg-light));
  width: 100%;
}

.volume-chart-labels {
  display: flex;
  justify-content: space-between;
  font-size: 0.7rem;
  color: var(--fg-light);
  padding: 0 0.25rem;
}

.error-rate-tile.has-errors .value {
  color: var(--bad);
}

/* ---------- Watchlist live grid (Trading Simulator) ---------- */

.watchlist-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 0.75rem;
  margin: 1.5rem 0;
}

.watchlist-tile {
  /* Dimension's global `button { height: 2.75rem; line-height: 2.75rem;
     white-space: nowrap; display: inline-block; box-shadow: inset 0 0 0
     1px #fff; }` (assets/css/main.css) is built for a single line of
     label text -- these tiles are <button> elements holding 3 stacked
     lines (ticker/price/time), so every one of those needs overriding or
     the fixed short height clips/overflows and staggers content across
     grid rows. */
  all: unset;
  box-sizing: border-box;
  display: block;
  height: auto;
  line-height: normal;
  white-space: normal;
  appearance: none;
  -webkit-appearance: none;

  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.85rem 1rem;
  transition: background-color 0.3s ease-out, border-color 0.15s ease-out;
  background: transparent;
  color: inherit;
  font-family: inherit;
  font-size: inherit;
  text-align: left;
  cursor: pointer;
  width: 100%;
  position: relative;
}

.watchlist-tile:hover {
  border-color: var(--border-strong);
}

.watchlist-tile.is-selected {
  border-color: var(--tile-series-color, var(--accent));
  background: var(--accent-dim);
}

.watchlist-tile.is-selected::before {
  content: '';
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--tile-series-color, var(--accent));
}

.watchlist-tile .tile-ticker {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04rem;
  color: var(--fg-light);
}

.watchlist-tile .tile-price {
  font-size: 1.15rem;
  font-variant-numeric: tabular-nums;
  margin-top: 0.15rem;
}

.watchlist-tile .tile-time {
  font-size: 0.65rem;
  color: var(--fg-light);
  margin-top: 0.3rem;
  font-variant-numeric: tabular-nums;
}

@keyframes tile-flash-up {
  from { background-color: rgba(74, 222, 128, 0.25); border-color: var(--good); }
  to { background-color: transparent; border-color: var(--border); }
}

@keyframes tile-flash-down {
  from { background-color: rgba(248, 113, 113, 0.25); border-color: var(--bad); }
  to { background-color: transparent; border-color: var(--border); }
}

.tile-flash-up {
  animation: tile-flash-up 1.5s ease-out;
}

.tile-flash-down {
  animation: tile-flash-down 1.5s ease-out;
}

/* ---------- Pipeline World: appearance picker + feed ---------- */

.appearance-swatch {
  all: unset; /* same Dimension-button override reasoning as .watchlist-tile */
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.75rem;
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--fg-light);
}

.appearance-swatch::before {
  content: '';
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--swatch-color, var(--accent));
}

.appearance-swatch:hover {
  border-color: var(--border-strong);
  color: var(--fg);
}

.appearance-swatch.is-selected {
  border-color: var(--swatch-color, var(--accent));
  background: rgba(255, 255, 255, 0.06);
  color: var(--fg);
}

#pipeline-feed li {
  padding: 0.5rem 0.9rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  font-size: 0.85rem;
}

#pipeline-feed li:last-child {
  border-bottom: none;
}

#pipeline-feed .feed-fail {
  color: var(--bad);
}

#pipeline-feed .feed-pass {
  color: var(--good);
}

/* ---------- Pipeline tracker (build-history-style table) ---------- */

#tracker-table .stage-cell {
  text-align: center;
}

.stage-status {
  display: inline-block;
  min-width: 2.5rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04rem;
  padding: 0.2rem 0.5rem;
  border-radius: 4px;
  border: 1px solid var(--border);
  color: var(--fg-light);
}

.stage-status-none {
  opacity: 0.4;
}

.stage-status-running {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-dim);
  animation: stage-pulse 1.2s ease-in-out infinite;
}

.stage-status-pass {
  color: var(--good);
  border-color: var(--good);
  background: rgba(74, 222, 128, 0.1);
}

.stage-status-fail {
  color: var(--bad);
  border-color: var(--bad);
  background: rgba(248, 113, 113, 0.1);
}

.stage-duration {
  display: block;
  font-size: 0.62rem;
  color: var(--fg-light);
  margin-top: 0.15rem;
}

.fast-mode-control {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}

#fast-mode-toggle.is-active {
  border-color: var(--good);
  color: var(--good);
  background: rgba(74, 222, 128, 0.1);
}

@keyframes stage-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

#tracker-table tr.row-live td:first-child {
  color: var(--good);
}

#tracker-table tr.row-failed td:first-child {
  color: var(--bad);
}

#tracker-table tr.row-new {
  animation: row-flash-in 1.6s ease-out;
}

/* ---------- Production Town viewer ---------- */

#character-detail {
  min-height: 3.5rem;
}

#character-detail.has-selection {
  border-style: solid;
  border-color: var(--border);
  text-align: left;
  color: var(--fg);
}

/* ---------- Live build log (terminal-styled) ---------- */

.terminal-wrapper {
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  margin-bottom: 1.5rem;
  background: #0a0d10;
}

.terminal {
  margin: 0;
  padding: 1rem 1.1rem;
  max-height: 260px;
  overflow-y: auto;
  font-family: 'JetBrains Mono', 'Courier New', monospace;
  font-size: 0.8rem;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
  color: #c9d1d9;
}

.terminal .log-stage-start {
  color: var(--accent);
  font-weight: 700;
}

.terminal .log-command {
  color: #8b949e;
  padding-left: 1.2rem;
  display: block;
}

.terminal .log-command::before {
  content: '$ ';
  color: #4ade80;
}

.terminal .log-pass {
  color: var(--good);
}

.terminal .log-fail {
  color: var(--bad);
}

/* The sample run shown before any real output exists. Held back visually
   so it never competes with a live run for attention -- it's context for
   an empty panel, not content. */
.terminal .log-placeholder {
  opacity: 0.55;
}

.terminal .log-placeholder-note {
  display: block;
  color: var(--fg-light);
  margin-bottom: 0.5rem;
}

/* Speech bubbles in Production Town are drawn directly on the <canvas>
   (see pipeline_town.js) rather than as HTML overlays -- simpler than
   keeping DOM elements in sync with animated canvas coordinates every
   frame, and consistent with how characters/zones are already drawn. */

/* Timed-Squares board layout.

   #content is normally capped at 1200px (.wide) so body text stays a
   readable line length -- that's exactly wrong for a game board, which
   should use as much of the viewport as it actually has. .ts-page opts
   this one page out of that cap; the two-column info row below the
   board (legend/leaderboard) still reads fine at full width since each
   column caps its own content, not the whole page. */

#content.ts-page {
  max-width: 100%;
  padding-left: clamp(1rem, 4vw, 3rem);
  padding-right: clamp(1rem, 4vw, 3rem);
}

.ts-layout {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
  align-items: start;
  max-width: 1100px;
  margin: 2rem auto 0;
}

@media (max-width: 700px) {
  .ts-layout {
    grid-template-columns: 1fr;
  }
}

.ts-hud {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  margin-bottom: 1rem;
  flex-wrap: wrap;
}

.ts-hud-item {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.ts-hud-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fg-light);
}

.ts-hud-value {
  font-size: 1.4rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.ts-board-wrapper {
  position: relative;
  /* Big and centered: bounded by viewport height (so it never pushes
     the HUD/overlay off-screen on a short window) as much as by width,
     since a square board on a wide desktop monitor is height-limited
     long before it's width-limited. */
  width: min(100%, 82vh, 900px);
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  background: var(--surface);
  overflow: hidden;
}

#ts-canvas {
  width: 100%;
  height: 100%;
  display: block;
}

.ts-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(13, 17, 23, 0.86);
}

/* The `hidden` attribute and this class have equal CSS specificity, and
   this rule loads after the browser's own `[hidden] { display: none }`
   default -- so without this override, `.ts-overlay { display: flex }`
   above always wins the tie and the game-over screen never actually
   hides no matter how many times JS sets `overlay.hidden = true`. This
   selector's extra attribute clause outranks the plain class rule, so
   `hidden` wins regardless of source order. */
.ts-overlay[hidden] {
  display: none;
}

.ts-overlay-card {
  text-align: center;
  padding: 1.5rem;
  max-width: 300px;
}

.ts-overlay-card h2 {
  margin: 0 0 0.5rem;
  color: var(--bad);
}

.ts-overlay-card input {
  width: 100%;
  margin: 0.75rem 0;
  padding: 0.5rem;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--fg);
  text-align: center;
  text-transform: uppercase;
}

.ts-overlay-actions {
  display: flex;
  gap: 0.6rem;
  justify-content: center;
  margin-top: 0.5rem;
}

.ts-legend {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  font-size: 0.85rem;
}

.ts-legend-swatch {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 3px;
  margin-right: 0.5rem;
  vertical-align: middle;
}
