/* cattracks web viewer — "night instrument" shell.
 * Palette values match src/ui/tokens.css (the extension's shared design
 * tokens) but are redeclared locally rather than imported: the web app is a
 * separate build/origin from the extension pages that tokens.css serves.
 *
 * Two screens share this stylesheet: the sign-in card (`.signin`), and the
 * single always-on shell (`.browse-shell`) — topbar, staleness line,
 * `.scrubber` (density strip + range input + ticks + readout), the
 * view-controls bar, then `#timelineMoment` (`.browse-device` →
 * `.browse-window` → `.browse-tab`). There used to be a second, optional
 * "Full timeline" mode with its own shell class; that split is gone (see
 * app.ts's `renderShell` doc comment) — everything lives in one shell now.
 * Mobile-first throughout — this is explicitly the "grab a window from my
 * phone" viewer (design doc §1) — with a widening breakpoint for desktop.
 */

:root {
  color-scheme: dark;

  --ink: #0e1218;
  --panel: #161c26;
  --panel-raised: #1c2430;
  --line: #263140;
  --fg: #c9d4e3;
  --muted: #6b7a8f;
  --signal: #4fd1c5;
  --ember: #e0a458;
  --danger: #e06a5a;

  --radius: 0.6rem;
  --radius-sm: 0.4rem;
}

* {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--ink);
  color: var(--fg);
  font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
  line-height: 1.5;
}

#app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* Visually hidden but present for assistive tech / the Task-1 KDF probe —
 * standard "sr-only" clip pattern, not display:none (which some assistive
 * tech + the probe's own DOM presence checks would rather avoid). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

a {
  color: var(--signal);
}

button {
  font: inherit;
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

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

/* ---------------------------------------------------------------- sign-in */

.signin {
  width: 100%;
  max-width: 30rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 3rem;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* Brand wordmark rainbows across the six pride colors (red->violet), copied
 * (not imported) from the marketing site's .wordmark (public/cattracks/css/
 * base.css) per this file's header note. Unlike that source rule, we do NOT
 * redeclare `color: transparent` after the gradient/clip declarations: the
 * marketing site's version leaves the wordmark genuinely invisible in any
 * engine that ignores `background-clip: text`, because `color` is a
 * universally-supported property whose value always applies regardless of
 * background-clip support -- there's no feature-detection for it the way
 * `-webkit-text-fill-color` effectively gets one (every engine that honors
 * text-fill-color also honors the clip). So `color` here stays the solid
 * fallback declared first/only, and the gradient/clip lines that follow only
 * take visible effect where the whole combo is supported; where it isn't,
 * the solid color paints straight through and the wordmark just reads as
 * plain text instead of vanishing. */
.app-title {
  margin: 1rem 0 0;
  font-size: 1.5rem;
  letter-spacing: 0.02em;
  color: var(--fg);
  /* `background-clip: text` paints the gradient across the element's BOX and
     then clips it to the glyphs — so on a block-level element (this is an
     <h1>) the six stops spread across the full container width while the
     word occupies only its left third, and the visible letters show just the
     red->orange->yellow opening of the gradient. Verified on a real iOS
     Safari device, where the wordmark rendered with no green, blue, or
     violet at all. inline-block shrinks the box to the text so all six stops
     land inside the glyphs. The other three gradient wordmarks
     (.browse-topbar-title here, .wordmark in timeline.css and options.css)
     are all <span>s and are inline already, which is why only this one was
     affected.

     `width: fit-content` and NOT `display: inline-block`: this <h1> is a FLEX
     ITEM (its parent is display:flex), and the flex layout algorithm
     BLOCKIFIES an item's display — `inline-block` computes back to `block`,
     leaving the box full-width and the bug exactly as it was. Measured, not
     assumed: with inline-block the computed display read `block` and the box
     stayed 350px wide against 110px of text. fit-content sizes the box to
     the glyphs regardless of blockification. */
  width: fit-content;
  background: linear-gradient(90deg, #e40303 0%, #ff8c00 20%, #ffed00 40%, #008026 60%, #004dff 80%, #750787 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.trust-notice {
  background: var(--panel);
  border: 1px solid var(--ember);
  border-radius: var(--radius);
  padding: 1rem 1.1rem;
  font-size: 0.9rem;
  color: var(--fg);
}

.trust-notice p {
  margin: 0.6rem 0 0;
}

.trust-notice p:first-of-type {
  margin-top: 0;
}

.trust-notice-headline {
  margin: 0;
  font-weight: 700;
  color: var(--ember);
  text-transform: uppercase;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
}

.signin-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.field-label {
  font-size: 0.85rem;
  color: var(--muted);
}

.field input {
  min-height: 2.75rem;
  padding: 0 0.8rem;
  background: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--fg);
  font-size: 1rem;
}

.field input:focus-visible {
  border-color: var(--signal);
}

/* "Stay signed in" opt-in checkbox — default unchecked (see app.ts). Its
 * at-rest warning sits directly below it, not elsewhere on the page, so the
 * trade-off is visible right where the choice is made. */
.field--checkbox {
  flex-direction: row;
  align-items: center;
  gap: 0.6rem;
  color: var(--fg);
  font-size: 0.95rem;
}

.field--checkbox input {
  width: 1.15rem;
  height: 1.15rem;
  accent-color: var(--signal);
  flex-shrink: 0;
}

.stay-signed-in-warning {
  margin: -0.4rem 0 0;
  font-size: 0.82rem;
  color: var(--muted);
}

.signin-submit {
  min-height: 2.9rem;
  border: none;
  border-radius: var(--radius-sm);
  background: var(--signal);
  color: #08211f;
  font-weight: 700;
  font-size: 1rem;
  cursor: pointer;
}

.signin-submit:disabled {
  opacity: 0.6;
  cursor: progress;
}

.signin-submit:hover:not(:disabled) {
  filter: brightness(1.08);
}

.signin-status {
  margin: 0;
  min-height: 1.2rem;
  font-size: 0.9rem;
  color: var(--muted);
}

.signin-status:not(:empty) {
  color: var(--ember);
}

/* -------------------------------------------------------------- browse UI */

.browse-shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.browse-topbar {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.9rem 1rem;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

/* Same rainbow treatment as .app-title above (see its comment for why the
 * solid `color` fallback stands alone rather than being overridden to
 * transparent afterward). */
.browse-topbar-title {
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--signal);
  background: linear-gradient(90deg, #e40303 0%, #ff8c00 20%, #ffed00 40%, #008026 60%, #004dff 80%, #750787 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.browse-topbar-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.browse-signout,
.browse-refresh-btn,
.browse-export-btn {
  min-height: 2.25rem;
  padding: 0 0.9rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
}

.browse-signout:hover,
.browse-refresh-btn:hover:not(:disabled),
.browse-export-btn:hover:not(:disabled) {
  color: var(--fg);
  border-color: var(--muted);
}

.browse-refresh-btn:disabled {
  opacity: 0.6;
  cursor: progress;
}

/* The "as of HH:MM" staleness line (Step 4) — sits directly under the
 * topbar, above the device/window list, so it reads as a property of the
 * whole view rather than a footnote. */
.staleness-line {
  margin: 0;
  padding: 0.5rem 1rem;
  font-size: 0.8rem;
  color: var(--muted);
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

.browse,
.browse-empty-state {
  flex: 1;
  width: 100%;
  max-width: 46rem;
  margin: 0 auto;
  padding: 1rem 0.85rem 3rem;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.browse-empty-state {
  color: var(--muted);
  text-align: center;
  padding-top: 3rem;
}

/* Task 3: the view-controls bar (hide-unrecoverable, collapse-all, device
 * legend) — its own row between the staleness line and the device/window
 * list, mirroring the extension timeline's `.view-controls` (see
 * src/timeline/timeline.css) but with this file's `browse-` prefix. */
.browse-controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 0.6rem 1rem;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  font-size: 0.8rem;
}

.browse-hide-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}

.browse-hide-toggle input {
  accent-color: var(--signal);
  cursor: pointer;
}

/* Quiet secondary button, shared style with Collapse-all/Expand-all. */
.browse-ghost-btn {
  min-height: 2rem;
  padding: 0 0.75rem;
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-weight: 600;
  font-size: 0.8rem;
  cursor: pointer;
  white-space: nowrap;
}

.browse-ghost-btn:hover {
  border-color: var(--signal);
  color: var(--signal);
}

.browse-device-legend {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-left: auto;
}

.browse-device-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}

.browse-device-chip input {
  accent-color: var(--signal);
  cursor: pointer;
}

.browse-device {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.browse-device-name {
  margin: 0;
  font-size: 1.05rem;
  color: var(--fg);
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--line);
}

.browse-window {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.browse-window-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 0.9rem;
  background: var(--panel-raised);
  border-bottom: 1px solid var(--line);
  cursor: pointer; /* whole header toggles collapse, except Copy URLs */
}

.browse-window-header:hover {
  border-bottom-color: var(--muted);
}

/* Caret: a quiet icon button that lights up on header hover — same treatment
 * as the extension's `.window-collapse` (src/timeline/timeline.css). */
.browse-window-collapse {
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 0.7rem;
  line-height: 1;
  width: 1.2rem;
  padding: 0.15rem 0.25rem;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.browse-window-header:hover .browse-window-collapse {
  color: var(--fg);
}

/* Collapsed: header stays, tab list is omitted from the markup entirely
 * (browse.ts), so there's no `.browse-window--collapsed .browse-window-tabs`
 * display:none rule to write here — nothing to hide, only the header's
 * bottom border to relax so a folded window doesn't look "cut off". */
.browse-window--collapsed .browse-window-header {
  border-bottom-color: transparent;
}

.browse-window-title {
  font-weight: 600;
  color: var(--fg);
}

/* When a nickname is present it becomes the primary label (above) and
 * "Window N" rides along demoted to a muted secondary span. */
.browse-window-ordinal {
  margin-left: 0.4rem;
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 400;
}

.browse-window-count {
  margin-right: auto;
  font-size: 0.8rem;
  color: var(--muted);
}

.browse-copy {
  min-height: 2.2rem;
  padding: 0 0.75rem;
  background: transparent;
  border: 1px solid var(--signal);
  border-radius: var(--radius-sm);
  color: var(--signal);
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
}

.browse-copy:hover {
  background: rgba(79, 209, 197, 0.12);
}

.browse-window-tabs {
  display: flex;
  flex-direction: column;
}

.browse-window-empty {
  margin: 0;
  padding: 0.9rem;
  color: var(--muted);
  font-size: 0.9rem;
}

/* Every tab row is a full-width, min-44px-tall tap target — restorable rows
 * are real <button>s (native keyboard/focus-visible support), unrestorable
 * rows are plain <div>s with no affordance to click. */
.browse-tab {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: 3.25rem;
  padding: 0.6rem 0.9rem;
  background: transparent;
  border: none;
  border-top: 1px solid var(--line);
  text-align: left;
  color: var(--fg);
}

.browse-window-tabs .browse-tab:first-child {
  border-top: none;
}

button.browse-tab {
  cursor: pointer;
}

button.browse-tab:hover {
  background: rgba(255, 255, 255, 0.03);
}

.browse-tab--active {
  border-left: 3px solid var(--signal);
  padding-left: calc(0.9rem - 3px);
}

.browse-tab--unrestorable {
  color: var(--muted);
  opacity: 0.75;
}

.browse-tab-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.browse-tab-title {
  font-size: 0.95rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.browse-tab-url {
  font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono', monospace;
  font-size: 0.75rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.browse-tab-affordance {
  flex-shrink: 0;
  font-size: 0.8rem;
  color: var(--signal);
}

.browse-tab-affordance--muted {
  color: var(--muted);
}

/* ------------------------------------------------------------- timeline UI */
/* The always-on scrub timeline, sitting inside `.browse-shell` between the
 * staleness line and the view-controls bar (there is no separate shell class
 * for it any more — see this file's header comment). Density strip + range
 * scrubber; palette matches the extension's `.density-strip`/`.bar` (see
 * src/timeline/timeline.css) so the "shape of the day" signature reads the
 * same on both surfaces, redeclared locally per this file's header note. */

.scrubber {
  width: 100%;
  max-width: 46rem;
  margin: 0 auto;
  padding: 1rem 0.85rem 0;
}

/* The plain-language range statement ("Recorded 25 Jul 3:59 PM → 27 Jul
 * 5:16 AM · 37 hours") — matches the extension timeline's `.range-summary`
 * (see src/timeline/timeline.css). Sits above the strip, visible without
 * touching the scrubber. */
.range-summary {
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  margin-bottom: 0.6rem;
}

/* Time-axis ticks under the range input — matches the extension timeline's
 * `.ticks` (see src/timeline/timeline.css). This viewer had none before. */
.ticks {
  display: flex;
  justify-content: space-between;
  margin-top: 0.4rem;
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.density-strip {
  display: flex;
  align-items: flex-end;
  gap: 1px;
  height: 72px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0 3px;
  overflow: hidden;
}

.bar {
  flex: 1 1 0;
  min-width: 1px;
  align-self: flex-end;
  /* Recency maps to color temperature: --t runs 0 (oldest) -> 1 (most
   * recent), interpolating ember -> signal — same convention as the
   * extension's timeline strip. */
  background: color-mix(in srgb, var(--ember), var(--signal) calc(var(--t, 0) * 100%));
  opacity: 0.85;
  border-radius: 1px 1px 0 0;
}

/* The range input IS the accessible/touch scrub control (keyboard + drag);
 * the density strip above it is a purely visual aid (aria-hidden). Sized for
 * a comfortable >=44px touch target end-to-end, not just the thin track —
 * the thumb itself is the large tappable/draggable element. */
.timeline-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 44px;
  margin: 0.4rem 0 0;
  background: transparent;
  cursor: pointer;
  touch-action: pan-y;
}

.timeline-range:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

.timeline-range::-webkit-slider-runnable-track {
  height: 6px;
  background: var(--line);
  border-radius: 3px;
}

.timeline-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 28px;
  height: 28px;
  margin-top: -11px;
  background: var(--signal);
  border: 3px solid var(--panel);
  border-radius: 50%;
  box-shadow: 0 0 0 1px var(--line);
}

.timeline-range::-moz-range-track {
  height: 6px;
  background: var(--line);
  border-radius: 3px;
}

.timeline-range::-moz-range-thumb {
  width: 28px;
  height: 28px;
  background: var(--signal);
  border: 3px solid var(--panel);
  border-radius: 50%;
  box-shadow: 0 0 0 1px var(--line);
}

/* Step 3: the readout naming the scrubbed moment ("9:20:15 AM · Mon, Jul
 * 20"), matching the extension timeline's own #readout treatment. Sits
 * right under the range input so it reads as "what the thumb is pointing
 * at" rather than a separate label. */
.timeline-readout {
  margin: 0.6rem 0 0;
  text-align: center;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--fg);
}

#timelineMoment {
  flex: 1;
}

/* ------------------------------------------------------------------ toast */

.toast {
  position: fixed;
  left: 50%;
  bottom: 1.25rem;
  transform: translate(-50%, 0.5rem);
  max-width: calc(100vw - 2rem);
  padding: 0.7rem 1.1rem;
  background: var(--panel-raised);
  border: 1px solid var(--signal);
  border-radius: var(--radius);
  color: var(--fg);
  font-size: 0.9rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 10;
}

.toast--visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* ---------------------------------------------------------------- desktop */

@media (min-width: 640px) {
  .signin {
    padding-top: 3rem;
  }

  .browse-window-header {
    gap: 0.9rem;
  }
}

/* ------------------------------------------------------------------ footer */

/* Step 3: a static footer living OUTSIDE #app in index.html (same rationale
 * as the #out probe node's placement, per that node's comment) so it
 * survives app.ts's wholesale #app innerHTML swaps and shows on every
 * screen -- sign-in and the shell. The only way back to the
 * marketing site and its privacy policy from what would otherwise be a dead
 * end. Root-relative hrefs ("/", "/privacy.html") because this page is
 * served from /app/, one directory below where those pages actually live;
 * a same-directory relative path would 404 here instead of resolving to the
 * site root. */
.app-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  padding: 1.5rem 1rem 2.5rem;
  font-size: 0.85rem;
}

.app-footer a {
  color: var(--muted);
  text-decoration: none;
}

.app-footer a:hover {
  color: var(--fg);
  text-decoration: underline;
}

.app-footer-brand {
  font-weight: 600;
}
