/* TruckSpots SPA — "Gallery" design system: radical minimal monochrome. Pure
   white surfaces, #eaeaea hairlines as the only separation, ink text, and lots
   of white space. Terracotta (#c2410c) is the single accent — primary CTAs,
   active nav, status stamps — color never appears in chrome otherwise. Cards
   are frosted glass (translucent fill + backdrop blur); corners are square.
   Oswald (uppercase) for display, Inter for body, Spline Sans Mono for data.
   Fonts are the vendored woff2 served from /static/fonts so the SPA runs fully
   offline. All colors live in :root — no raw hex sprawl.

   Amendments (2026-07 live production review, founder-approved):
   1. Soft neutral elevation (--shadow-rest/--shadow-hover) on INTERACTIVE
      result cards and the modal ONLY — form/section containers stay flat
      hairline (.ts-card--interactive marks "this opens the detail modal").
   2. A narrow --danger red exception on the destructive delete action
      (danger-zone heading marker + delete button border/text).
   3. The modal carries the heaviest shadow in the system (--shadow-modal).
   Square corners, monochrome surfaces, and the single terracotta accent
   remain law. */
:root {
  --bg: #ffffff;
  --surface: #ffffff;
  --surface-2: #f7f7f7;
  --border: #eaeaea;
  /* Form-control boundary: hairlines measure ~1.2:1 on white, below the 3:1
     WCAG minimum for UI components — controls get this ~3.2:1 gray instead. */
  --border-strong: #8f8f8f;
  --text: #111111;
  --text-muted: #6f6f6f;

  /* Terracotta — the single accent (primary CTAs, active nav, stamps). */
  --accent: #c2410c;
  --accent-hover: #9a3412;
  --on-accent: #ffffff;

  /* Status colors live in data (banners, checks, connection state) only. */
  --ok: #15803d;
  --warn: #a16207;
  --danger: #b91c1c;
  --ring: rgba(194, 65, 12, 0.35);

  /* Elevation (Amendments 1 + 3): neutral ink-tinted shadows. Interactive
     result cards + the modal only — never containers or form cards. */
  --shadow-rest: 0 1px 2px rgba(17, 17, 17, 0.04), 0 4px 12px rgba(17, 17, 17, 0.06);
  --shadow-hover: 0 2px 4px rgba(17, 17, 17, 0.06), 0 12px 28px rgba(17, 17, 17, 0.12);
  --shadow-modal: 0 24px 64px rgba(17, 17, 17, 0.24);

  /* Frosted-glass card treatment. */
  --glass: rgba(255, 255, 255, 0.55);
  --glass-border: rgba(17, 17, 17, 0.08);

  /* Neutral chart bars (busy-times). Darker than a hairline so bars read as
     data, quieter than ink; the accent marks the current hour only. */
  --chart-bar: #d6d3d0;

  --radius: 0px;

  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-display: "Oswald", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: "Spline Sans Mono", ui-monospace, SFMono-Regular, "SF Mono", monospace;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/static/fonts/inter-latin-variable.woff2") format("woff2");
}
@font-face {
  font-family: "Oswald";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/static/fonts/oswald-latin-variable.woff2") format("woff2");
}
@font-face {
  font-family: "Spline Sans Mono";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/static/fonts/spline-sans-mono-latin-variable.woff2") format("woff2");
}

* { box-sizing: border-box; }

html, body { margin: 0; padding: 0; }

body {
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--accent); color: var(--on-accent); }

/* Display treatment: Oswald, uppercase, tracked. */
.ts-brand,
.ts-page-header h1,
.ts-card-title,
.ts-btn,
.ts-nav-link,
.ts-stamp {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: 0.04em;
}

/* Mono for numeric/data values. */
.ts-mono,
.ts-zip,
.ts-stat strong { font-family: var(--font-mono); }

/* Tracked-out micro-labels. */
.ts-section-title,
.ts-table th,
.ts-stat span { text-transform: uppercase; letter-spacing: 0.1em; }

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

/* Skip link for keyboard users. */
.ts-skip {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--accent);
  color: var(--on-accent);
  padding: 8px 14px;
  z-index: 100;
  text-decoration: none;
}
.ts-skip:focus { left: 0; }

/* ====================== App shell (sidebar layout) ====================== */
.ts-shell { display: flex; min-height: 100vh; }

.ts-sidebar {
  width: 248px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-right: 1px solid var(--border);
  transition: width 0.16s ease;
}
.ts-shell.is-collapsed .ts-sidebar { width: 72px; }

/* Sidebar header row: brand + the desktop collapse control beside it. The
   hairline lives on the row so brand and toggle share one underline. */
.ts-sidebar-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding-right: 12px;
  border-bottom: 1px solid var(--border);
}
.ts-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 18px 20px;
  font-size: 1.05rem;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
}
.ts-sidebar-head .ts-brand { flex: 1; min-width: 0; }
.ts-brand .ts-spark { color: var(--accent); }

.ts-sidebar-toggle {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  font-size: 1.05rem;
  color: var(--text-muted);
}
.ts-sidebar-toggle:hover { color: var(--text); }
/* Collapsed: stack mark over toggle so the way back stays reachable at 72px. */
.ts-shell.is-collapsed .ts-sidebar-head {
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: 0 0 12px;
}
.ts-shell.is-collapsed .ts-sidebar-head .ts-brand { padding: 18px 0 4px; }

.ts-nav {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 12px;
  overflow-y: auto;
}

.ts-nav-link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--radius);
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.82rem;
  white-space: nowrap;
  overflow: hidden;
}
.ts-nav-link:hover { background: var(--surface-2); color: var(--text); }
.ts-nav-link.ts-active {
  background: var(--surface-2);
  color: var(--text);
  box-shadow: inset 3px 0 0 var(--accent);
}
.ts-nav-ico { width: 18px; text-align: center; flex-shrink: 0; }

.ts-avatar {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: var(--radius);
  background: var(--text);
  color: var(--bg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 600;
}
.ts-user-meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.ts-user-name { font-weight: 600; font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ts-user-email { color: var(--text-muted); font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ts-shell.is-collapsed .ts-hide-collapsed { display: none; }
/* The compact "T·S" mark shows only when the sidebar is collapsed. */
.ts-show-collapsed { display: none; }
.ts-shell.is-collapsed .ts-show-collapsed { display: inline; }
.ts-shell.is-collapsed .ts-nav-link,
.ts-shell.is-collapsed .ts-brand { justify-content: center; }

.ts-mainwrap { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.ts-topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 24px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  /* Frosted chrome: content scrolling beneath shows through the blur. */
  background: rgba(255, 255, 255, 0.8);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  z-index: 5;
}

.ts-iconbtn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  width: 38px;
  height: 38px;
  border-radius: var(--radius);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
}
.ts-iconbtn:hover { border-color: var(--text); }

.ts-topbar-title {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: 0.04em;
  font-size: 1.02rem;
}

/* Right edge of the topbar: connection status + the account avatar menu. */
.ts-topbar-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
}

/* Quiet connection summary — hairline chip, muted text; the green check is
   connection state (data), same treatment as the connect list. */
.ts-connstat {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: 0.78rem;
  text-decoration: none;
  white-space: nowrap;
}
.ts-connstat:hover { border-color: var(--text); color: var(--text); }
.ts-connstat-item { display: inline-flex; align-items: center; gap: 6px; }

/* Avatar button + dropdown (Settings / Account / Sign out). */
.ts-avatarbtn {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  display: inline-flex;
  border-radius: var(--radius);
}
.ts-avatarbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ts-usermenu { position: relative; display: inline-flex; }
.ts-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 10px);
  min-width: 210px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.12);
  padding: 6px 0;
  z-index: 60;
}
.ts-menu-id {
  padding: 8px 14px 10px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px;
}
.ts-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 9px 14px;
  font-family: var(--font-display);
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: 0.04em;
  font-size: 0.78rem;
  color: var(--text);
  cursor: pointer;
}
.ts-menu-item:hover { background: var(--surface-2); }
.ts-menu-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  box-shadow: none;
}

.ts-content { flex: 1; padding: 32px 28px 56px; max-width: 1040px; width: 100%; }

.ts-drawer-toggle, .ts-scrim { display: none; }

@media (max-width: 760px) {
  /* The in-sidebar collapse control is desktop-only; on mobile the sidebar is
     an off-canvas drawer driven from the topbar. */
  .ts-sidebar-toggle { display: none; }
  /* A labeled "Menu" button, not a bare hamburger glyph. */
  .ts-drawer-toggle {
    display: inline-flex;
    width: auto;
    min-height: 44px;
    padding: 0 14px;
    font-family: var(--font-display);
    text-transform: uppercase;
    font-weight: 600;
    letter-spacing: 0.06em;
    font-size: 0.78rem;
  }
  /* Primary nav affordance on touch — comfortable 44px target. */
  .ts-iconbtn { width: 44px; height: 44px; }
  /* The persistent topbar controls (messages bell + account avatar) are the
     only way to reach messages / settings / sign-out on a phone — give them
     the same 44px touch target as every other control. */
  .ts-bell, .ts-avatarbtn {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  /* A desktop-persisted collapsed state must not shrink the drawer. */
  .ts-shell.is-collapsed .ts-sidebar { width: 248px; }
  .ts-shell.is-collapsed .ts-hide-collapsed { display: revert; }
  .ts-shell.is-collapsed .ts-show-collapsed { display: none; }
  .ts-shell.is-collapsed .ts-nav-link,
  .ts-shell.is-collapsed .ts-brand { justify-content: flex-start; }
  .ts-shell.is-collapsed .ts-sidebar-head { flex-direction: row; padding: 0 12px 0 0; }
  .ts-shell.is-collapsed .ts-sidebar-head .ts-brand { padding: 18px 20px; }
  /* Connection detail lives in Settings on small screens. */
  .ts-connstat { display: none; }
  .ts-sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 40;
    transform: translateX(-100%);
    transition: transform 0.18s ease;
  }
  .ts-shell.is-open .ts-sidebar { transform: translateX(0); }
  .ts-shell.is-open .ts-scrim {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(17, 17, 17, 0.35);
    z-index: 30;
  }
}

/* ====================== Public (auth) layout ====================== */
/* Centered so the white space frames the form instead of hanging under it. */
.ts-public {
  max-width: 440px;
  margin: 0 auto;
  padding: 2rem 1.25rem;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* gap: 0 — the bare "Truck" text node is a flex item; the brand gap would
   split the wordmark apart. */
.ts-public-brand { padding: 0 0 1.5rem; border-bottom: none; font-size: 1.2rem; gap: 0; }

/* ====================== Headings ====================== */
.ts-page-header { margin-bottom: 1.4rem; }
.ts-page-header h1 { margin: 0; font-size: 1.5rem; }
.ts-subtitle { margin: 0.25rem 0 0; color: var(--text-muted); }
.ts-section-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.8rem;
  color: var(--text-muted);
  margin: 0.5rem 0 0.75rem;
}

/* ====================== Cards (frosted glass) ====================== */
.ts-card {
  background: var(--glass);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  padding: 1.4rem;
  margin-bottom: 1.4rem;
}
.ts-card-title { margin: 0 0 0.85rem; font-size: 1rem; }

/* Interactive result cards (Amendment 1): soft neutral elevation marks "this
   opens the detail modal". TopSpots grid cards + Find-a-Spot tickets only —
   never form/section containers. Under prefers-reduced-motion the global
   kill-switch stops the transition and the hover lift is removed below; the
   rest→hover shadow swap still reads state without motion. */
.ts-card--interactive {
  border: 1px solid var(--border);
  box-shadow: var(--shadow-rest);
  cursor: pointer;
  transition: box-shadow 150ms ease, transform 150ms ease;
}
.ts-card--interactive:hover {
  box-shadow: var(--shadow-hover);
  transform: translateY(-1px);
}
.ts-card--interactive:focus-visible {
  outline: none;
  box-shadow: var(--shadow-hover), 0 0 0 2px var(--ring);
}

/* Amendment 2: the danger-zone heading carries a small --danger marker. */
.ts-card--danger .ts-card-title::before {
  content: "";
  display: inline-block;
  width: 0.55em;
  height: 0.55em;
  background: var(--danger);
  margin-right: 0.5em;
}

/* ====================== Forms ====================== */
/* Visible to screen readers only: labels and live-region announcements whose
   text would be redundant on screen but is the only cue a non-visual user
   gets. Never use it to hide something a sighted user also needs. */
.ts-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.ts-form { display: flex; flex-direction: column; gap: 0.85rem; max-width: 520px; }
.ts-inline-form { display: flex; gap: 0.75rem; align-items: flex-end; flex-wrap: wrap; }
.ts-block { display: block; }

label { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.82rem; color: var(--text-muted); }

/* A field group that wraps more than one control (so it can't be a <label> —
   a label only ever implicitly names the first control inside it) but still
   needs to read exactly like one: label's own box model and text styling,
   reproduced on a plain div + span. */
.ts-fieldgroup { display: flex; flex-direction: column; gap: 0.3rem; }
.ts-fieldgroup-label { font-size: 0.82rem; color: var(--text-muted); }

input, textarea, select {
  /* Block, not inline: inside block labels (.ts-block) a capped-width inline
     input would sit beside the label text instead of under it. */
  display: block;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text);
  padding: 0.55rem 0.7rem;
  font: inherit;
  width: 100%;
  /* Short values in metre-wide fields read as unconsidered — cap them. */
  max-width: 420px;
}
textarea { max-width: 680px; }
input[type="checkbox"] { display: inline-block; width: auto; accent-color: var(--accent); }
/* A checkbox with its label on one line (e.g. Settings' "remind me weekly"
   toggle) — plain labels default to a stacked column for text-above-input
   fields, which reads wrong for a single checkbox + sentence. */
.ts-checkrow { flex-direction: row; align-items: center; gap: 0.5rem; font-size: 0.9rem; color: var(--text); margin-top: 0.75rem; }
input:focus-visible, textarea:focus-visible, select:focus-visible,
.ts-btn:focus-visible, .ts-nav-link:focus-visible, .ts-iconbtn:focus-visible, a:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--ring);
  border-color: var(--accent);
}
/* Borderless elements (nav links, text links): border-color is a no-op there,
   and the soft ring alone is ~1.7:1 — give them a crisp accent outline. */
a:focus-visible, .ts-nav-link:focus-visible, .ts-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  box-shadow: none;
}

/* ====================== Buttons ====================== */
.ts-btn {
  display: inline-block;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.55rem 1rem;
  font-size: 0.82rem;
  cursor: pointer;
  text-decoration: none;
}
.ts-btn:hover { border-color: var(--text); }
/* Disabled is neutral gray, not half-opacity accent — faded terracotta reads
   as a wrong brand color rather than an unavailable action. */
.ts-btn:disabled,
.ts-btn-primary:disabled {
  background: var(--surface-2);
  color: var(--text-muted);
  border-color: var(--border);
  cursor: not-allowed;
}
.ts-btn-primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.ts-btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.ts-btn-danger { background: var(--danger); color: #fff; border-color: var(--danger); }
/* Amendment 2: the disarmed delete button reads as danger — --danger
   border/text on white — and fills red only once armed (enabled). */
.ts-btn-danger:disabled {
  background: var(--surface);
  color: var(--danger);
  border-color: var(--danger);
  cursor: not-allowed;
}
.ts-link { background: none; border: none; color: var(--text); padding: 0; text-decoration: underline; font-family: var(--font); text-transform: none; letter-spacing: 0; cursor: pointer; }
.ts-link:hover { color: var(--accent); }

.ts-actions { display: flex; gap: 0.75rem; margin-top: 0.5rem; flex-wrap: wrap; }

/* A small side-by-side figure row (Dashboard sales tile: yesterday / last 7
   days) — each stat is a muted label over a bold value. */
.ts-stat-row { display: flex; gap: 2rem; flex-wrap: wrap; }
.ts-stat p { margin: 0; }
.ts-stat-value { font-size: 1.1rem; font-weight: 600; }

.ts-muted { color: var(--text-muted); }
.ts-source { color: var(--text-muted); font-size: 0.85rem; margin-top: 0.5rem; font-family: var(--font-mono); }
/* Inline per-field validation message (e.g. onboarding's ZIP format check) —
   smaller and tighter than a page-level banner since it sits right under the
   offending input. */
.ts-field-error { color: var(--danger); font-size: 0.85rem; margin: -0.5rem 0 0.75rem; }

/* ====================== Banners ====================== */
/* A left accent rule, not a full outlined box — full-width accent borders
   shout louder than the hairline language around them. */
.ts-banner { padding: 0.7rem 0.9rem; border-radius: var(--radius); margin-bottom: 1rem; border: none; border-left: 3px solid; }
.ts-banner-error { background: rgba(185, 28, 28, 0.06); border-color: var(--danger); }
.ts-banner-success { background: rgba(21, 128, 61, 0.06); border-color: var(--ok); }
.ts-banner-info { background: rgba(194, 65, 12, 0.06); border-color: var(--accent); }

/* A dismissible nudge banner (e.g. Settings' "Update permissions" prompt) is
   an info banner with its copy and a close control on one line. Scoped to its
   own modifier class — flexing .ts-banner-info itself would break every
   existing info banner whose children are inline text + links (Events, Menu,
   FindASpot, …) into spread-apart flex items. */
.ts-banner-dismissible { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; }
.ts-dismiss {
  background: transparent;
  border: none;
  color: var(--text-muted);
  width: 24px;
  height: 24px;
  min-width: 24px;
  border-radius: var(--radius);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
}
.ts-dismiss:hover { color: var(--text); }

.ts-spinner { padding: 2rem; text-align: center; color: var(--text-muted); }

/* Demo-parse badge: a calm, informational note (not an error/warning color) —
   the mock LLM's canned output is by design, not a malfunction. */
.ts-demo-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.7rem;
  margin: 0 0 0.75rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: 0.82rem;
  background: rgba(0, 0, 0, 0.015);
}

/* ====================== Cuisine chips ====================== */
.ts-cuisines { display: flex; flex-wrap: wrap; gap: 0.5rem; border: 1px solid var(--border); border-radius: var(--radius); padding: 0.75rem; }
.ts-cuisines legend { padding: 0 0.4rem; color: var(--text-muted); }
.ts-chip { flex-direction: row; align-items: center; gap: 0.4rem; background: var(--surface-2); padding: 0.35rem 0.6rem; border-radius: var(--radius); color: var(--text); font-size: 0.85rem; }
.ts-chip-custom { border: 1px solid var(--accent); color: var(--accent); }
.ts-chip-x { background: none; border: none; color: inherit; cursor: pointer; font-size: 1rem; line-height: 1; padding: 0; min-width: 24px; min-height: 24px; display: inline-flex; align-items: center; justify-content: center; }
.ts-add-cuisine { display: flex; gap: 0.5rem; align-items: center; flex-basis: 100%; margin-top: 0.4rem; }
.ts-add-cuisine input { max-width: 220px; }

/* ====================== Checklist ====================== */
.ts-checklist { list-style: none; padding: 0; margin: 0 0 0.75rem; }
.ts-checklist li { padding: 0.35rem 0; color: var(--text-muted); }
.ts-checklist li.ts-done { color: var(--text); }
.ts-check { display: inline-block; width: 1.5rem; color: var(--ok); }

/* ====================== Tables / feed ====================== */
.ts-table { width: 100%; border-collapse: collapse; }
.ts-table th, .ts-table td { text-align: left; padding: 0.55rem 0.4rem; border-bottom: 1px solid var(--border); vertical-align: top; }
.ts-table th { color: var(--text-muted); font-weight: 600; font-size: 0.78rem; }

.ts-feed { list-style: none; padding: 0; margin: 0; }
.ts-feed li { padding: 0.6rem 0; border-bottom: 1px solid var(--border); }

.ts-stat { display: flex; flex-direction: column; }
.ts-stat span { font-size: 0.72rem; color: var(--text-muted); }

/* Terracotta status stamp. */
.ts-stamp {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border-radius: var(--radius);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--accent);
  border: 1px solid var(--accent);
  background: rgba(194, 65, 12, 0.06);
  white-space: nowrap;
}
.ts-events { margin: 0.25rem 0 0.5rem; padding-left: 1.1rem; }
.ts-events li { margin: 0.1rem 0; }

/* ====================== Placement advisor ====================== */
/* Results header: the section title on the left, the secondary "Get advice"
   action on the right (Search stays the terracotta primary). */
.ts-results-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.ts-results-head .ts-section-title { margin-bottom: 0; }

/* The advisor's ranked summary panel: a plain card with a numbered list keyed
   to the candidate cards below (which carry a matching "#n advisor pick" stamp). */
.ts-advisor-panel { margin-bottom: 1.4rem; }
.ts-advisor-title {
  margin: 0 0 0.35rem;
  font-size: 0.95rem;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.ts-advisor-list { list-style: none; padding: 0; margin: 0.75rem 0 0; }
.ts-advisor-list li {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.6rem 0;
  border-top: 1px solid var(--border);
}
.ts-advisor-list li:first-child { border-top: none; }
.ts-advisor-list strong { font-size: 0.95rem; }
.ts-advisor-list p { margin: 0.15rem 0 0; }
/* The rank chip is status/data, so it may carry the accent. */
.ts-advisor-rank {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.6rem;
  height: 1.6rem;
  padding: 0 0.4rem;
  font-size: 0.8rem;
  font-family: var(--font-mono);
  color: var(--on-accent);
  background: var(--accent);
}

/* ====================== Connection status dot (W12) ====================== */
/* Traffic-light connection indicator for Square/Calendar: green = connected &
   healthy, amber = a real permissions/connection-issue signal already
   computed by the backend, red = not connected. Never color-only — a visible
   text label always sits beside the dot (see StatusDot in components.tsx), so
   the state reads for a color-blind operator and a screen reader alike. Hues
   are chosen for status contrast, not brand — terracotta stays the only
   accent; these never appear as decoration. */
.ts-status { display: inline-flex; align-items: center; gap: 0.45rem; }
.ts-status-dot {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  flex: none;
}
.ts-status-dot--ok { background: var(--ok); }
.ts-status-dot--warn { background: var(--warn); }
.ts-status-dot--off { background: var(--danger); }
.ts-status-label {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* ====================== Connect-both nudge ====================== */
.ts-connect-list { list-style: none; padding: 0; margin: 0 0 0.75rem; }
.ts-connect-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
}
.ts-connect-list li:last-child { border-bottom: none; }
.ts-conn-label { flex: 1; }
/* ts-conn-on/off remain for the topbar's compact ConnStatus summary
   (app.tsx) — out of W12's scope (Settings' two connection cards + the
   connect-both nudge, which now use .ts-status-dot above). */
.ts-conn-on { color: var(--ok); font-family: var(--font-mono); }
.ts-conn-off { color: var(--text-muted); font-family: var(--font-mono); }

/* ====================== Modal (detail + onboarding wizard) ====================== */
.ts-modal-scrim {
  position: fixed;
  inset: 0;
  /* Ink scrim, opacity only. No backdrop-filter here: blur on the scrim
     breaks the sticky modal header in Chromium. */
  background: rgba(17, 17, 17, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4vh 1rem;
  z-index: 100;
  overflow-y: auto;
}
/* Amendment 3: the modal carries the heaviest shadow in the system. The panel
   is its own scroll container (85vh cap) so the header stays sticky inside. */
.ts-modal {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-modal);
  width: 100%;
  max-width: 640px;
  max-height: 85vh;
  overflow-y: auto;
  outline: none;
}
.ts-modal--detail { max-width: 560px; }
.ts-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 1;
}
.ts-modal-title {
  margin: 0;
  font-size: 1.05rem;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* Muted line under the modal title (address / ZIP fallback + chip). */
.ts-modal-sub {
  margin: 0.25rem 0 0;
  color: var(--text-muted);
  font-size: 0.85rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
/* Touch-comfortable close target (≥44px). */
.ts-modal-close { width: 44px; height: 44px; flex-shrink: 0; }
.ts-modal-body { padding: 1.25rem; }

/* Hairline-divided sections inside the detail modal body. */
.ts-detail-section { padding: 0.9rem 0; border-top: 1px solid var(--border); }
.ts-detail-section:first-child { border-top: none; padding-top: 0; }
.ts-detail-section h3 {
  margin: 0 0 0.4rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.78rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.ts-detail-section p { margin: 0.25rem 0; }
.ts-detail-coords { font-size: 0.8rem; }
/* Terracotta action link ("Open in Maps") — a CTA, so it may carry the accent. */
.ts-accent-link { color: var(--accent); }
.ts-accent-link:hover { color: var(--accent-hover); }

/* ====================== Dashboard top-spots list ====================== */
/* Compact hairline-separated rows — the dashboard summary of Find-a-Spot's
   heavy order tickets. The whole row is one link to /find. */
.ts-spot-controls { display: flex; gap: 0.9rem; flex-wrap: wrap; margin: 0.35rem 0 0.6rem; }
.ts-spot-controls select { width: auto; min-width: 150px; }
/* Find a Spot's W10 search filters (date/time-of-day/radius/category) — a
   distinct row above the results from .ts-spot-controls above (that one only
   re-slices already-fetched candidates; this one re-runs the search). A
   hairline + align-items keeps it visually its own group, not a continuation
   of the ZIP form card above it. */
.ts-search-filters {
  align-items: flex-end;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
}
.ts-search-filters input[type="date"] { width: auto; min-width: 150px; }
.ts-microlabel {
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.72rem;
  color: var(--text-muted);
}
/* Cards / List segmented toggle — monochrome: the active segment inverts to
   ink, no accent (accent stays reserved for CTAs / active nav / stamps). */
.ts-viewtoggle-wrap { display: flex; flex-direction: column; gap: 0.3rem; }
.ts-viewtoggle {
  display: inline-flex;
  width: max-content;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}
.ts-vt-btn {
  background: var(--surface);
  border: none;
  padding: 0.5rem 0.85rem;
  font-family: var(--font-display);
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: 0.04em;
  font-size: 0.72rem;
  color: var(--text-muted);
  cursor: pointer;
}
.ts-vt-btn + .ts-vt-btn { border-left: 1px solid var(--border-strong); }
.ts-vt-btn:hover { color: var(--text); }
.ts-vt-btn[aria-pressed="true"] { background: var(--text); color: var(--bg); }
.ts-vt-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--ring);
  position: relative;
  z-index: 1;
}

/* Card view: compact Gallery cards in a responsive grid (2-3 across desktop,
   one column on narrow screens via auto-fill). Whole card is one link. */
.ts-spotgrid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}
.ts-spotcard {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  height: 100%;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  text-decoration: none;
}
.ts-spotcard:hover { border-color: var(--text); }
.ts-spotcard:hover .ts-spot-title { color: var(--accent); }
/* Stamps hug their label. As a direct child of the column-flex card a stamp
   would otherwise stretch to the full card width (align-items: stretch),
   reading as an empty bordered bar instead of a compact status marker. */
.ts-spotcard > .ts-stamp { align-self: flex-start; }
.ts-spotcard-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.ts-spotlist { list-style: none; margin: 0; padding: 0; }
.ts-spotlist li { border-bottom: 1px solid var(--border); }
.ts-spotlist li:last-child { border-bottom: none; }
.ts-spotrow {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  padding: 0.6rem 0;
  color: var(--text);
  text-decoration: none;
  cursor: pointer;
}
.ts-spotrow:hover .ts-spot-title { color: var(--accent); }
.ts-spotrow:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.ts-spot-rank { color: var(--text-muted); font-size: 0.78rem; }
.ts-spot-title {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: 0.04em;
  font-size: 0.88rem;
}
.ts-spot-meta { font-size: 0.78rem; }
.ts-spot-note { font-size: 0.8rem; margin: 0.5rem 0 0; }
.ts-skeleton-row {
  height: 40px;
  margin: 0.4rem 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: linear-gradient(90deg, var(--surface-2) 0%, var(--border) 50%, var(--surface-2) 100%);
  background-size: 200% 100%;
  animation: ts-shimmer 1.4s ease-in-out infinite;
}

/* ====================== Dashboard skeleton ====================== */
.ts-skeleton-grid { display: grid; gap: 1.4rem; margin-top: 1.4rem; }
.ts-skeleton-card {
  height: 120px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: linear-gradient(90deg, var(--surface-2) 0%, var(--border) 50%, var(--surface-2) 100%);
  background-size: 200% 100%;
  animation: ts-shimmer 1.4s ease-in-out infinite;
}
@keyframes ts-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ====================== Enrichment status (Find-a-Spot) ====================== */
/* Live chip while background signal compilation settles. Hairline chip, muted
   text; the terracotta dot is status/data, so it may carry the accent. The
   reduced-motion kill-switch below stops the pulse — the dot stays visible as
   a static marker, so the state still reads without motion. */
.ts-enrich-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.4rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 1rem;
  color: var(--text-muted);
  font-size: 0.85rem;
}
.ts-enrich-dot {
  width: 7px;
  height: 7px;
  flex: none;
  background: var(--accent);
  animation: ts-enrich-pulse 1.3s ease-in-out infinite;
}
@keyframes ts-enrich-pulse {
  50% { opacity: 0.25; }
}
/* Quiet settled/timeout line once (or if) the pending chip retires. */
.ts-enrich-note { color: var(--text-muted); font-size: 0.85rem; margin: 0 0 1rem; }

/* ====================== Busy times (popular-times) ====================== */
/* The detail modal's 24-bar day chart, the candidate-card peak line, and the
   dashboard best-time tile. Monochrome bars on a hairline baseline; the
   terracotta accent marks exactly one thing — the current hour. */
.ts-busy { margin-top: 0.35rem; }
.ts-busy-days { display: flex; gap: 0.3rem; margin-bottom: 0.65rem; }
/* Day selector: monochrome segmented buttons (accent stays reserved); the
   active day inverts to ink, same treatment as the cards/list view toggle. */
.ts-busy-day {
  width: 30px;
  height: 30px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  cursor: pointer;
}
.ts-busy-day[aria-pressed="true"] {
  background: var(--text);
  border-color: var(--text);
  color: var(--bg);
}
.ts-busy-day:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ts-busy-figure { margin: 0; }
.ts-busy-svg { display: block; width: 100%; height: 88px; }
.ts-busy-bar { fill: var(--chart-bar); }
.ts-busy-bar--now { fill: var(--accent); }
.ts-busy-baseline { stroke: var(--border); stroke-width: 1; }
.ts-busy-ticks { position: relative; height: 0.9rem; }
.ts-busy-tick {
  position: absolute;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  color: var(--text-muted);
}
.ts-busy-caption {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  font-size: 0.8rem;
  margin-top: 0.45rem;
}
/* Candidate-card peak line — data-adjacent copy, muted like the event count. */
.ts-busy-chip { color: var(--text-muted); font-size: 0.85rem; }
/* Dashboard best-time tile: a small flat hairline tile above Top spots. */
.ts-besttime {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.7rem 0.9rem;
  margin-bottom: 1rem;
  background: var(--surface);
}
.ts-besttime-when { font-size: 1.05rem; }
.ts-besttime-where { font-size: 0.85rem; }

/* Respect reduced-motion: drop the sidebar/drawer transitions + skeleton
   shimmer, and the interactive-card hover lift (the rest→hover shadow swap
   still reads without motion; focus keeps the ring). */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
  .ts-card--interactive:hover { transform: none; }
}

/* ============================================================================
   Human-first redesign (Today answer card, Your Week, Money, Messages).
   Extends the Gallery system — tokens only, square corners, single terracotta
   accent, Oswald display for the answer. Mobile-first; the Today card is tuned
   to read cleanly at 375px.
   ========================================================================== */

/* Button size modifiers (base .ts-btn unchanged). */
.ts-btn-lg { padding: 0.8rem 1.4rem; font-size: 0.95rem; }
.ts-btn-sm { padding: 0.35rem 0.7rem; font-size: 0.75rem; }
/* A compact button (.ts-btn-sm) still needs a real touch target — bump the
   tap area without touching its compact font/padding, the same override
   pattern .ts-iconbtn/.ts-bell use for their own 44px minimum. */
.ts-btn-touch { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }

/* --- Today: the answer card ------------------------------------------------ */
.ts-today { max-width: 40rem; }
.ts-answer {
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent);
  background: var(--surface);
  padding: 1.75rem 1.5rem;
  box-shadow: var(--shadow-rest);
}
.ts-answer--set { border-top-color: var(--ok); }
.ts-answer-kicker {
  margin: 0;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.8rem;
  color: var(--text-muted);
}
.ts-answer-spot {
  margin: 0.35rem 0 0.15rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 2rem;
  line-height: 1.1;
  color: var(--text);
}
.ts-answer-window {
  margin: 0 0 0.75rem;
  font-family: var(--font-mono);
  font-size: 1rem;
  color: var(--accent);
}
.ts-answer-badge { margin-left: 0.6rem; font-family: var(--font); vertical-align: middle; }
.ts-answer-why {
  margin: 0 0 1.25rem;
  font-size: 1.05rem;
  line-height: 1.5;
  color: var(--text);
}
.ts-answer-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; }
.ts-answer-actions .ts-btn-lg { flex: 1 1 auto; text-align: center; }
.ts-answer-set { margin: 0; font-size: 1.05rem; color: var(--ok); font-weight: 600; }
.ts-answer-check { display: inline-block; margin-right: 0.25rem; }
.ts-today-links {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-top: 1.25rem;
}
.ts-today-links .ts-link { text-decoration: none; color: var(--text-muted); text-align: left; }
.ts-today-links .ts-link:hover { color: var(--accent); }

/* --- Today: the current calendar week as day columns ----------------------- */
/* Desktop/tablet: seven columns, always the full week, never a partial or
   overflowing grid — `minmax(0, 1fr)` lets each column shrink together as the
   viewport narrows instead of one column overflowing its cell. */
.ts-weekgrid {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.6rem;
  /* Stretch (the grid default) so every column matches the row's tallest —
     a result day (short) and a suggestion day (why text + 3 buttons) would
     otherwise end at very different heights, leaving a ragged bottom edge.
     Pairs with .ts-weekcol-btnrow's margin-top: auto below, which needs the
     extra column height to push into. */
  align-items: stretch;
}
.ts-weekcol {
  border: 1px solid var(--border);
  border-top: 3px solid var(--border);
  background: var(--surface);
  padding: 0.75rem 0.65rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  min-width: 0;
}
/* Tiers, ground-truth-first (docs/goals/ground-truth-first.md G4): a result
   already happened (strongest tier this view has); approved is the owner's
   own committed pick; suggested is the agent's opinion and reads as one —
   dashed, the same "not-actual" language .ts-chart-trend already uses. */
.ts-weekcol--actual { border-top-color: var(--text-muted); background: var(--surface-2); }
.ts-weekcol--approved { border-top-color: var(--ok); }
.ts-weekcol--suggested { border-top-style: dashed; border-top-color: var(--text-muted); }
/* Today is anchored independently of its tier color — an accent ring so it
   reads as "you are here" regardless of whether today is done/approved
   /suggested. */
.ts-weekcol--today { box-shadow: 0 0 0 2px var(--accent) inset; }
.ts-weekcol-head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.35rem; }
.ts-weekcol-day {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.72rem;
  color: var(--text-muted);
}
.ts-weekcol--today .ts-weekcol-day { color: var(--accent); }
.ts-weekcol-date { font-family: var(--font-mono); font-size: 0.7rem; color: var(--text-muted); }
.ts-weekcol-kicker {
  margin: 0;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.68rem;
  color: var(--text-muted);
}
.ts-weekcol-spot { margin: 0; font-weight: 600; font-size: 0.92rem; line-height: 1.25; }
.ts-weekcol-window { margin: 0; font-family: var(--font-mono); font-size: 0.78rem; color: var(--accent); }
.ts-weekcol-why { margin: 0; color: var(--text-muted); font-size: 0.82rem; line-height: 1.4; }
.ts-weekcol-result { margin: 0; font-size: 0.92rem; }
.ts-weekcol-badge { margin: 0.2rem 0 0; font-size: 0.65rem; padding: 2px 7px; }
.ts-weekcol-again { display: flex; flex-direction: column; gap: 0.35rem; align-items: flex-start; }
.ts-weekcol-btnrow { display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: center; margin-top: auto; padding-top: 0.2rem; }

/* --- Your Week ------------------------------------------------------------- */
.ts-week-cta { margin-bottom: 1rem; }
.ts-week { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; }
.ts-week-row {
  display: flex;
  gap: 0.9rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  background: var(--surface);
  padding: 0.9rem 1rem;
}
.ts-week-row--approved { border-left-color: var(--ok); }
.ts-week-row--done { border-left-color: var(--text-muted); background: var(--surface-2); }
.ts-week-day {
  flex: 0 0 4.5rem;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.8rem;
  color: var(--text-muted);
  padding-top: 0.1rem;
}
.ts-week-body { flex: 1 1 auto; min-width: 0; }
.ts-week-spot { font-weight: 600; font-size: 1.05rem; }
.ts-week-window { font-weight: 400; color: var(--accent); font-family: var(--font-mono); font-size: 0.85rem; }
.ts-week-why { margin: 0.3rem 0 0.6rem; color: var(--text-muted); font-size: 0.92rem; line-height: 1.45; }
.ts-week-result { margin: 0.3rem 0 0.6rem; font-size: 1rem; }
.ts-week-actions, .ts-again { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.ts-week-ok { color: var(--ok); font-weight: 600; font-size: 0.85rem; }
.ts-again-label { color: var(--text-muted); font-size: 0.9rem; }
.ts-again-recorded { color: var(--text); font-size: 0.9rem; font-weight: 600; }
/* Transient landing highlight when "Plan for it" (Events tab) jumps here. */
.ts-week-row--highlight { border-left-color: var(--accent); box-shadow: 0 0 0 2px var(--accent) inset; }

/* --- Events ------------------------------------------------------------ */
.ts-events-list { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; }
.ts-event-row {
  display: flex;
  gap: 0.9rem;
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 0.9rem 1rem;
}
.ts-event-day {
  flex: 0 0 7rem;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.8rem;
  color: var(--text-muted);
  padding-top: 0.1rem;
}
.ts-event-body { flex: 1 1 auto; min-width: 0; }
.ts-event-title { font-weight: 600; font-size: 1.05rem; }
.ts-event-actions { margin-top: 0.5rem; }

/* Swap picker list (in the modal). */
.ts-swap-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.ts-swap-item {
  width: 100%;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 0.7rem 0.9rem;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.ts-swap-item:hover { border-color: var(--text); }
.ts-swap-item:disabled { color: var(--text-muted); cursor: not-allowed; }
.ts-swap-name { font-weight: 600; }
.ts-swap-meta { color: var(--text-muted); font-size: 0.82rem; }

/* --- Money ----------------------------------------------------------------- */
.ts-money-hero {
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent);
  padding: 1.5rem;
  margin-bottom: 1rem;
  box-shadow: var(--shadow-rest);
}
.ts-money-kicker {
  margin: 0;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.8rem;
  color: var(--text-muted);
}
.ts-money-total {
  margin: 0.2rem 0 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 2.6rem;
  line-height: 1;
  color: var(--text);
}
.ts-money-best { margin: 0 0 0.75rem; font-weight: 600; }
.ts-money-spots { list-style: none; margin: 0; padding: 0; }
.ts-money-spot {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
}
.ts-money-spot:last-child { border-bottom: none; }
.ts-money-spot-name { font-weight: 500; }
.ts-money-spot-figs { white-space: nowrap; }

/* --- Your spots (calendar⋈Square actuals) --------------------------------- */
.ts-hist-spot { align-items: flex-start; }
.ts-hist-main { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.ts-hist-name { font-weight: 600; }
.ts-hist-trend { font-size: 0.85rem; color: var(--text-muted); }
.ts-hist-trend--up { color: var(--ok); }
.ts-hist-figs { text-align: right; white-space: nowrap; }
.ts-hist-typical { font-weight: 600; }
.ts-hist-sub { display: block; font-size: 0.85rem; color: var(--text-muted); }
.ts-hist-footer { margin: 0.7rem 0 0; font-size: 0.85rem; color: var(--text-muted); line-height: 1.5; }
.ts-hist-reconnect { margin: 0 0 0.5rem; line-height: 1.5; }

/* --- Messages + bell ------------------------------------------------------- */
.ts-bell {
  position: relative;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text);
  font-size: 1.1rem;
  padding: 0.25rem 0.4rem;
  line-height: 1;
}
.ts-bell:hover { color: var(--accent); }
.ts-bell-badge {
  position: absolute;
  top: -0.1rem;
  right: -0.1rem;
  min-width: 1rem;
  height: 1rem;
  padding: 0 0.2rem;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 0.65rem;
  line-height: 1rem;
  text-align: center;
  font-family: var(--font-mono);
}
.ts-msg-cta { margin-bottom: 0.5rem; }
.ts-msg-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.ts-msg {
  width: 100%;
  text-align: left;
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 0.85rem 1rem;
  cursor: pointer;
}
.ts-msg:hover { border-color: var(--text); }
.ts-msg--unread { border-left: 3px solid var(--accent); background: var(--surface); }
.ts-msg-dot { flex: 0 0 auto; width: 0.6rem; height: 0.6rem; margin-top: 0.35rem; background: var(--text-muted); border-radius: 50%; }
.ts-msg-dot--result { background: var(--ok); }
.ts-msg-dot--today { background: var(--accent); }
.ts-msg-dot--week_ready { background: var(--text-muted); }
.ts-msg-text { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.ts-msg-title { font-weight: 600; }
.ts-msg-body { color: var(--text-muted); font-size: 0.9rem; line-height: 1.4; }

/* --- Onboarding confirmation ---------------------------------------------- */
.ts-onboard-alt { margin-top: 1rem; color: var(--text-muted); }
.ts-confirm-line { margin: 0.75rem 0 0.35rem; font-size: 1.05rem; }

/* --- Small screens (375px): the answer must read cleanly ------------------ */
@media (max-width: 460px) {
  .ts-answer { padding: 1.4rem 1.15rem; }
  .ts-answer-spot { font-size: 1.7rem; }
  .ts-answer-actions { flex-direction: column; }
  .ts-answer-actions .ts-btn-lg { width: 100%; }
  .ts-money-total { font-size: 2.1rem; }
  .ts-week-row { flex-direction: column; gap: 0.35rem; }
  .ts-week-day { flex-basis: auto; }
  /* Seven columns cannot fit 375px — scroll instead of squeezing (never a
     broken/illegible grid). Snap-scrolling keeps one day roughly full-width
     with a peek of the next, so it still reads as "columns", not a list. */
  .ts-weekgrid {
    display: flex;
    grid-template-columns: none;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: 0.3rem;
    -webkit-overflow-scrolling: touch;
  }
  .ts-weekcol { flex: 0 0 82%; scroll-snap-align: start; }
}

/* Money first-week empty state: named next step + pending plan rows. */
.ts-money-empty { margin: 0 0 0.75rem; line-height: 1.5; }
.ts-money-spot--pending .ts-money-spot-name { color: var(--text-muted); }

/* Square-confirm name suggestion (never a silent overwrite). */
.ts-name-suggestion { margin: 0.35rem 0 0.75rem; color: var(--text-muted); font-size: 0.9rem; }

/* ============================================================================
   Owner exclusions ("Not for me" / Spots you've hidden) — the feasibility fix.
   Extends the Gallery system: tokens only, quiet by default so hiding a spot
   never competes with the primary action on a card.
   ========================================================================== */

/* The quiet "Not for me" control on a candidate card / list row. Muted until
   hovered so it reads as a secondary escape hatch, not a call to action. */
.ts-notforme {
  align-self: flex-start;
  margin-top: 0.15rem;
  padding: 0;
  background: none;
  border: none;
  color: var(--text-muted);
  font-family: var(--font);
  font-size: 0.72rem;
  text-transform: none;
  letter-spacing: 0;
  text-decoration: underline;
  cursor: pointer;
}
.ts-notforme:hover { color: var(--accent); }
.ts-notforme:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ts-spotrow .ts-notforme { margin-top: 0; margin-left: auto; }

/* A quieter button variant for "Don't suggest this spot" / "Not feasible": a
   bordered ghost that stays out of the way of the primary action beside it. */
.ts-btn-quiet {
  background: none;
  color: var(--text-muted);
  border-color: var(--border);
}
.ts-btn-quiet:hover { color: var(--accent); border-color: var(--accent); }

/* Today card's inline "Not for me — find another": a plain link under the
   actions, so the answer stays the hero. */
.ts-answer-notforme {
  display: inline-block;
  margin-top: 0.9rem;
  font-size: 0.82rem;
  color: var(--text-muted);
}
.ts-answer-notforme:hover { color: var(--accent); }

/* Settings "Spots you've hidden" list. */
.ts-hidden-list { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.ts-hidden-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
}
.ts-hidden-row:last-child { border-bottom: none; }
.ts-hidden-name { font-size: 0.9rem; }

/* ============================================================================
   Money — per-spot cards + drill-down (goal actionable-spots-and-money, W2).
   One card per spot he has actually worked, each opening a filterable modal.
   Gallery law holds: white surfaces, #eaeaea hairlines, square corners, Oswald
   uppercase for display, and terracotta as the ONLY accent. Charts use exactly
   two data colors — --accent for the one emphasized mark, --chart-bar for
   everything else — and both are tokens, so the figures follow the system
   (including any future dark theme) instead of pinning their own hex.
   ========================================================================== */

.ts-spots-section { margin: 1.5rem 0; }
.ts-spots-title { margin-bottom: 0.85rem; }
/* The honest covered-period line under the section title ("Since March 2023").
   It pulls up under the title (which keeps its own bottom margin for the
   no-coverage case) so the two read as a title + subtitle pair. */
.ts-spots-coverage { margin: -0.6rem 0 0.85rem; font-size: 0.85rem; color: var(--text-muted); }

/* Auto-fitting grid: two-up on a laptop, one-up under ~420px. The min track is
   sized so the "$1,240 a typical day" lead never wraps mid-figure. */
.ts-moneycards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* The <li> is a semantic wrapper only (it carries the list count for screen
   readers); the card inside is the grid item that should stretch. */
.ts-moneycards > li { display: flex; }
.ts-moneycards > li > .ts-moneycard { flex: 1; }

/* The card is a single button — one target, one tab stop, identical by mouse,
   keyboard and touch. Elevation marks it as "this opens the detail modal"
   (Amendment 1); `overflow: hidden` keeps the full-bleed backdrop graph inside
   the square corners. */
.ts-moneycard {
  position: relative;
  overflow: hidden;
  display: block;
  width: 100%;
  max-width: none;
  text-align: left;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-rest);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.ts-moneycard:hover { box-shadow: var(--shadow-hover); transform: translateY(-2px); }
.ts-moneycard:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--ring); border-color: var(--accent); }

/* The background growth graph. It is pinned to the card's BOTTOM BAND while the
   figures occupy the band above (via the body's padding-bottom), so legibility
   is structural — the numbers and the graph never contend for the same pixels,
   rather than relying on opacity alone to keep text readable. */
.ts-moneycard-graph {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 62px;
  pointer-events: none;
}
.ts-moneycard-body { position: relative; display: block; padding: 1.15rem 1.15rem 4.6rem; }

/* align-items is pinned rather than left to the default: this card is a
   <button>, and the UA button styles centre flex children. */
.ts-moneycard-head { display: flex; flex-direction: column; align-items: flex-start; gap: 0.15rem; margin-bottom: 0.9rem; }
.ts-moneycard-name {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: 0.02em;
  font-size: 1rem;
  line-height: 1.25;
}

/* The lead figure: the one number he should read first. The unit rides directly
   under it so "$1,240" is never a bare figure whose period must be guessed. */
.ts-moneycard-lead { display: flex; flex-direction: column; align-items: flex-start; gap: 0.1rem; margin-bottom: 0.9rem; }
.ts-moneycard-amount {
  font-family: var(--font-display);
  font-size: 2rem;
  line-height: 1;
  font-weight: 600;
}
.ts-moneycard-unit { font-size: 0.82rem; color: var(--text-muted); }

.ts-moneycard-stats { display: flex; flex-wrap: wrap; gap: 0.35rem 1.4rem; }
.ts-moneycard-stat { display: flex; flex-direction: column; align-items: flex-start; gap: 0.05rem; }
.ts-moneycard-stat-v { font-weight: 600; font-size: 0.95rem; }
.ts-moneycard-stat-l { font-size: 0.75rem; color: var(--text-muted); }

/* The affordance sits at the bottom, over the graph's quiet left edge. */
.ts-moneycard-more {
  position: absolute;
  left: 1.15rem;
  bottom: 1rem;
  font-size: 0.78rem;
  color: var(--text-muted);
}
.ts-moneycard:hover .ts-moneycard-more { color: var(--accent); }

/* ---- Shared chart marks (card backdrop + modal charts) ---- */
/* The area fill is what makes the card backdrop read as texture rather than as
   a chart demanding to be measured; the line gives it just enough definition to
   show direction. */
.ts-spotgraph-area { fill: var(--accent); opacity: 0.08; }
.ts-spotgraph-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  opacity: 0.3;
  vector-effect: non-scaling-stroke;
}

/* ---- Modal: revenue-over-time ---- */
.ts-chart { margin: 0; }
.ts-chart-plot { position: relative; }
.ts-chart-svg { display: block; width: 100%; height: 120px; }
.ts-chart-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
/* The fitted trend is the one non-actual mark in the system: neutral, dashed,
   and never terminated in a dollar figure — it summarizes the actuals, it is
   not money he earned. */
.ts-chart-trend {
  stroke: var(--chart-bar);
  stroke-width: 1.5;
  stroke-dasharray: 4 4;
  vector-effect: non-scaling-stroke;
}
/* A hairline zero baseline: with the fill gone the line needs something to sit
   against, and it makes the zero-anchored scale visible rather than implied. */
.ts-chart-base { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.ts-chart-caption {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  margin-top: 0.4rem;
  font-size: 0.75rem;
  border-top: 1px solid var(--border);
  padding-top: 0.35rem;
}
.ts-chart-legend { text-align: center; }
.ts-chart-empty { margin: 0.3rem 0; font-size: 0.9rem; }

/* ---- Modal: weekday emphasis columns ---- */
/* Note on type sizes below: nothing in this block goes under 0.75rem (12px).
   These labels carry the derivation of every figure ("median of 51 days",
   "51d"), and they are read at --text-muted in a truck in daylight — the one
   place the system must not get precious about small type. */
.ts-wd { margin: 0; }
.ts-wd-cols { display: flex; align-items: flex-end; gap: 0.3rem; }
.ts-wd-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 0.25rem; min-width: 0; }
/* 56px, not 76px: a single worked weekday renders as one solid terracotta
   block, and at 76px that block dominated the modal for what is a one-line
   fact ("you work Fridays here"). */
.ts-wd-track { width: 100%; height: 56px; display: flex; align-items: flex-end; }
.ts-wd-bar { width: 100%; background: var(--chart-bar); border-radius: var(--radius); }
/* The single emphasized column. Terracotta here is an implicit recommendation,
   which is why bestWeekday() requires repeat visits before awarding it. */
.ts-wd-bar--best { background: var(--accent); }
/* A weekday never worked: a hairline stub at the baseline, so the absence sits
   where a bar would start instead of floating at the top of the column. */
.ts-wd-none { width: 100%; height: 2px; background: var(--border); }
.ts-wd-fig { font-family: var(--font-mono); font-size: 0.75rem; color: var(--text-muted); }
.ts-wd-fig--best { color: var(--accent); font-weight: 500; }
.ts-wd-day { font-size: 0.75rem; color: var(--text-muted); }
.ts-wd-day--best { color: var(--text); font-weight: 600; }
.ts-wd-count { font-size: 0.75rem; color: var(--text-muted); min-height: 0.9em; }
.ts-wd-caption { justify-content: flex-start; }
.ts-wd-key { font-family: var(--font-mono); }

/* ---- Modal: layout ---- */
/* The date range keeps each date whole. Inline after the sentence it broke
   mid-date at 375px ("Wed, Jul" / "15, 2026"), which reads as a broken range;
   on its own line it fits intact. */
.ts-spotmodal-range { display: inline-block; }
@media (max-width: 480px) {
  .ts-spotmodal-range { display: block; margin-top: 0.15rem; }
}

.ts-spotmodal-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--border);
}
/* Date/weekday controls are short values — the global 420px input cap is far too
   wide for them and would read as unconsidered. */
.ts-spotmodal-filters input,
.ts-spotmodal-filters select { width: auto; min-width: 9.5rem; max-width: 100%; }
.ts-spotmodal-clear { font-size: 0.82rem; padding-bottom: 0.55rem; }

.ts-spotmodal-figs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 1rem;
  padding: 1rem 0;
  border-bottom: 1px solid var(--border);
}
/* Every modal figure carries label + value + derivation, so a filtered number
   can never be ambiguous about the period or the maths behind it. */
.ts-spotfig { display: flex; flex-direction: column; gap: 0.1rem; }
.ts-spotfig-label { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); font-family: var(--font-display); }
.ts-spotfig-value { font-size: 1.3rem; font-weight: 600; font-family: var(--font-display); }
.ts-spotfig-note { font-size: 0.75rem; color: var(--text-muted); }

.ts-spotmodal-section { padding: 1rem 0; border-bottom: 1px solid var(--border); }
.ts-spotmodal-section:last-child { border-bottom: none; padding-bottom: 0; }
.ts-spotmodal-h {
  margin: 0 0 0.6rem;
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  font-weight: 600;
}
.ts-spotmodal-lede { margin: 0 0 0.7rem; font-size: 0.85rem; color: var(--text-muted); line-height: 1.5; }

/* ---- Modal: the calendar⋈Square correlation table ---- */
.ts-corr { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.ts-corr th {
  text-align: left;
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  font-weight: 600;
  padding: 0.35rem 0.5rem 0.35rem 0;
  border-bottom: 1px solid var(--border);
}
.ts-corr td { padding: 0.4rem 0.5rem 0.4rem 0; border-bottom: 1px solid var(--border); }
.ts-corr tr:last-child td { border-bottom: none; }
.ts-corr-num { text-align: right; padding-right: 0 !important; white-space: nowrap; }

/* The honest block: real money we deliberately refuse to attribute. Set on the
   quiet grey surface so it reads as a caveat, not as a second set of results. */
.ts-corr-honest { margin-top: 1rem; padding: 0.8rem; background: var(--surface-2); }
.ts-corr-honest-h { margin: 0 0 0.35rem; font-size: 0.85rem; font-weight: 600; }
.ts-corr-honest-note { margin: 0 0 0.6rem; font-size: 0.8rem; line-height: 1.5; }

/* Not-yet-available sections state their absence in words; they must never look
   like data (ground-truth-first: no hypothetical where a fact belongs). */
.ts-spotmodal-soon p { font-size: 0.85rem; line-height: 1.5; margin: 0; }

@media (max-width: 480px) {
  /* At 375px a single column keeps the lead figure whole and the tap target
     full-width. */
  .ts-moneycards { grid-template-columns: 1fr; }
  .ts-moneycard-amount { font-size: 1.75rem; }
  .ts-spotmodal-filters input,
  .ts-spotmodal-filters select { min-width: 0; width: 100%; }
  .ts-spotmodal-filters label { flex: 1 1 8rem; }
  /* Seven columns of money labels don't fit 375px — drop the per-column figure
     and keep the bars, day letters and the spoken aria description. */
  .ts-wd-fig { display: none; }
  .ts-wd-cols { gap: 0.15rem; }
  /* Three items on one caption row squeeze the end dates onto two lines each.
     Give the legend its own row and keep the dates as a clean pair. */
  .ts-chart-caption { flex-wrap: wrap; }
  .ts-chart-legend { order: 3; flex-basis: 100%; text-align: left; }
}

@media (prefers-reduced-motion: reduce) {
  .ts-moneycard:hover { transform: none; }
}

/* ====================== Operating-cost reminder toast (W6) ====================== */
/* A floating card, not an inline banner — it must survive navigation until
   acted on, so it sits fixed above the chrome. z-index sits below the modal
   scrim (100) so an open modal always wins if both are ever on screen. The
   accent-left rule + white surface matches .ts-banner-info; the elevation
   (--shadow-modal) is what marks it as floating rather than in-flow. */
.ts-costtoast {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 90;
  width: min(380px, calc(100vw - 2.5rem));
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  box-shadow: var(--shadow-modal);
  padding: 1rem 1.1rem;
}
.ts-costtoast-title {
  margin: 0 0 0.35rem;
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: 0.85rem;
  letter-spacing: 0.03em;
}
.ts-costtoast-copy { margin: 0 0 0.85rem; font-size: 0.85rem; line-height: 1.5; color: var(--text-muted); }
.ts-costtoast-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
/* A quiet text action, not a button — "turn off" is a real, findable setting
   (it's also in Settings), so it doesn't need equal visual weight to Save/Not now. */
.ts-costtoast-off {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 0.8rem;
  text-decoration: underline;
  cursor: pointer;
  /* min-height + inline-flex, not just padding: a fixed floor keeps the tap
     target at 44px regardless of font metrics, matching the 44px minimum the
     rest of the app holds to. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  padding: 0 0.2rem;
  margin-left: auto;
}
.ts-costtoast-off:hover { color: var(--text); }
.ts-costtoast-off:disabled { color: var(--border-strong); cursor: default; }

@media (max-width: 480px) {
  /* Full-width, docked to the bottom edge at 375px rather than a floating
     card with margins on both sides. */
  .ts-costtoast { left: 0.75rem; right: 0.75rem; bottom: 0.75rem; width: auto; }
  .ts-costtoast-off { margin-left: 0; }
}
