/**
 * WatchVault Pro — Collection Journey
 *
 * All journey visuals live here; nothing in this file affects other views.
 * Colours are drawn from the variables in main.css so the page feels native.
 */

.journey-view {
  padding: var(--space-lg, 1.5rem);
  --journey-ink: var(--text-primary);
  /* #c9cfe0 was hue 224 at 27% saturation -- a saturated blue-grey ring on a
     page whose neutrals sit at hue 20-33 and 5-10%. It read cool against a
     warm ground: the "gold or almost brown" problem of 2026-08-24 in reverse.
     #c2bdb7 is --medium-gray, already the app's card-edge weight, so this is a
     palette value rather than a new one. Chosen by Rex from three previewed
     weights on the real page.
     ⚠️ TWO rules read this: .journey-node-photo (the node ring) and
     .journey-gap (the year/turn chip border). The chips move with it, which is
     intended -- they are the same hairline -- and Rex saw both in the preview.
     ⚠️ A THIRD rule, .journey-detail-photo, does NOT: see the light block at
     the foot of this file for why. */
  --journey-ring: #c2bdb7;
  --journey-ring-hover: var(--secondary-color);
  --journey-chip-bg: var(--white);
}

[data-theme="dark"] .journey-view {
  --journey-ring: #3f4a5f;
  --journey-chip-bg: var(--bg-secondary);
  /* .journey-view declares FOUR journey variables and this block used to
     repoint only two. --journey-ink inherits --text-primary, which inverts
     correctly and needs nothing. --journey-ring-hover inherited
     --secondary-color, which is #4285f4 in dark -- the bright blue rejected on
     the login page, on Storage's location headers and on Assign Watch. It was
     therefore the colour of the single most-touched interaction on this page. */
  --journey-ring-hover: #cbb894;
}

/* ---------- header + summary ---------- */

.journey-header {
  margin-bottom: 1rem;
}

/* Matches .view-header h1 in main.css:1128 so the Journey title reads as the
   same page title as Collection, Wish List, Wear Calendar and the rest.
   Only the margin differs — the summary line sits directly beneath it. */
.journey-header h1 {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 600;
  color: var(--text-primary);
  margin: 0 0 0.25rem;
}

.journey-summary {
  margin: 0;
  color: var(--text-secondary);
  font-size: 0.9rem;
  letter-spacing: 0.01em;
}

.journey-notice {
  margin-bottom: 1rem;
  padding: 0.625rem 0.875rem;
  border-radius: var(--radius-md);
  /* Was a full amber wash: rgba(243,156,18,0.12) on a 0.35 edge. #f39c12 is
     --warning-light in this palette -- live, not retired -- but written as a
     raw literal, which is why no palette pass ever reached it.

     ⚠️ IT IS A NOTICE, NOT A WARNING. It explains why a watch is ABSENT from
     the ribbon; nothing is at risk and nothing needs doing. A tinted alert
     box overstated that and competed with the ribbon for attention.

     So the box becomes an ordinary card and the amber survives as a left edge
     only -- the pattern the app's own toasts already use (utils.js builds them
     with a 4px border-left carrying the semantic colour). Rex's choice from
     five previewed on the real page.
     ⚠️ ORDER MATTERS: border-left must follow the border shorthand or the
     shorthand resets it. This is the same mistake that would destroy a toast's
     colour coding, recorded in CLAUDE.md on 2026-08-14. */
  background: var(--bg-primary);
  border: 1px solid #e2e0df;
  border-left: 3px solid var(--warning-color);
  color: var(--text-primary);
  font-size: 0.85rem;
}

/* ---------- stage ---------- */

.journey-stage {
  position: relative;
  width: 100%;
}

.journey-ribbon-svg {
  position: absolute;
  inset: 0;
  overflow: visible;
  pointer-events: none;
}

.journey-ribbon {
  /* The ribbon STROKE is a blue gradient (journey.js:768) -- only this glow
     behind it was still #667eea, so the shadow disagreed with the thing
     casting it. Fourth #667eea to survive the purple sweep of 2026-08-14, and
     like the other three it hid in something you have to look for rather than
     read. Dark moved to brass on 08-17 and logged this as a light item; this
     closes it. */
  filter: drop-shadow(0 2px 10px rgba(42, 85, 128, 0.35));
  opacity: 0.9;
}

/* ---------- nodes ---------- */

.journey-node {
  position: absolute;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.journey-node-btn {
  position: relative;
  padding: 0;
  border: none;
  background: none;
  border-radius: var(--radius-full, 50%);
  cursor: pointer;
  transition: transform 0.22s cubic-bezier(0.34, 1.4, 0.64, 1), box-shadow 0.22s ease;
  box-shadow: 0 6px 18px rgba(44, 62, 80, 0.18);
}

.journey-node-photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  background: var(--bg-tertiary);
  border: 3px solid var(--journey-ring);
  box-sizing: border-box;
  transition: border-color 0.2s ease, filter 0.2s ease;
}

.journey-node-btn:hover,
.journey-node-btn:focus-visible {
  transform: translateY(-6px) scale(1.04);
  box-shadow: 0 14px 30px rgba(44, 62, 80, 0.28);
  outline: none;
}

.journey-node-btn:hover .journey-node-photo,
.journey-node-btn:focus-visible .journey-node-photo {
  border-color: var(--journey-ring-hover);
}

/* Divested watches stay on the curve — removing them would silently inflate
   the gaps between their neighbours — but read as clearly past-tense. */
.journey-node.is-divested .journey-node-photo {
  filter: grayscale(0.85) opacity(0.72);
  border-style: dashed;
}

.journey-node.is-divested .journey-node-label {
  opacity: 0.7;
}

.journey-node-label {
  margin-top: 0.55rem;
  display: flex;
  flex-direction: column;
  gap: 1px;
  width: 100%;
  line-height: 1.25;
}

.journey-node-brand {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.journey-node-model {
  font-size: 0.74rem;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.journey-node-date {
  font-size: 0.7rem;
  color: var(--text-muted);
  letter-spacing: 0.02em;
}

/* ---------- milestone badges ---------- */

.journey-badges {
  position: absolute;
  top: -4px;
  right: -4px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  pointer-events: none;
}

.journey-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  font-size: 0.72rem;
  line-height: 1;
  color: var(--white);
  background: var(--secondary-color);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
}

.journey-badge.is-first { background: linear-gradient(135deg, #667eea, #764ba2); }
.journey-badge.is-brand { background: var(--secondary-color); }
.journey-badge.is-top   { background: linear-gradient(135deg, #f5b041, #d68910); }

/* ---------- gap labels on the ribbon ---------- */

.journey-gap {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.18rem 0.5rem;
  border-radius: 999px;
  background: var(--journey-chip-bg);
  border: 1px solid var(--journey-ring);
  color: var(--text-secondary);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  box-shadow: 0 1px 4px rgba(44, 62, 80, 0.12);
  pointer-events: none;
}

.journey-gap.is-year {
  border-color: transparent;
  /* Deliberately MILDER than the blued steel on the login page (#1b3a5c).
     That colour is a full-bleed page background, where a deep tone reads as
     rich; this is a ~60x20 chip on a light page, where the same tone reads as
     a heavy blot -- it measured 3x the contrast against the page and Rex read
     it, correctly, as harsh. Matching the login page by hex does not survive
     the change of context.
     Also kept clear of the ribbon behind it: the ribbon's top stop is #6bb0e5
     and these chips sit on top of it, so pill and ribbon must not converge. */
  background: linear-gradient(135deg, #3568a0 0%, #4a7ab5 100%);
  color: var(--white);
  padding: 0.22rem 0.6rem;
}

.journey-gap-year {
  font-weight: 700;
  letter-spacing: 0.04em;
}

.journey-gap-sep {
  opacity: 0.55;
}

/* ---------- states ---------- */

.journey-loading,
.journey-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  min-height: 320px;
  color: var(--text-secondary);
  text-align: center;
}

.journey-empty h2 {
  font-size: 1.15rem;
  color: var(--text-primary);
  margin: 0;
}

.journey-empty p {
  margin: 0;
  max-width: 34ch;
}

/* ============================================================
   Mobile
   ============================================================ */

@media (max-width: 767px) {
  .journey-view {
    padding: 1rem 0.75rem 4.5rem;
  }

  /* 1.5rem matches .view-header h1 at this breakpoint (main.css:901). */
  .journey-header h1 {
    font-size: 1.5rem;
  }

  .journey-summary {
    font-size: 0.8rem;
  }

  /* Captions hug the outer edge of their own half so the ribbon and its day
     chips have the middle of the column to themselves. */
  .journey-node.is-left  { align-items: center; }
  .journey-node.is-right { align-items: center; }

  .journey-node-brand { font-size: 0.78rem; }
  .journey-node-model { font-size: 0.72rem; }
  .journey-node-date  { font-size: 0.68rem; }

  .journey-gap {
    font-size: 0.66rem;
    padding: 0.16rem 0.45rem;
  }

  .journey-badge {
    width: 19px;
    height: 19px;
    font-size: 0.64rem;
  }
}

@media (max-width: 480px) {
  .journey-node-brand { font-size: 0.74rem; }
  .journey-node-model { font-size: 0.68rem; }
  .journey-node-date  { font-size: 0.65rem; }
}

/* ---------- year-jump FAB (modelled on the app's alphabet FAB) ---------- */

.journey-year-fab {
  position: fixed;
  bottom: 20px;
  right: 20px;
  min-width: 56px;
  height: 56px;
  padding: 0 0.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 28px;
  /* Matches .journey-gap.is-year, not the ribbon: this is a small control
     floating over light content, the same context as the year chips.
     PHONE/NARROW SCREENS ONLY -- position: fixed, and it does not appear at
     desktop widths, so it was not part of what Rex reviewed on the desktop. */
  background: linear-gradient(135deg, #3568a0 0%, #4a7ab5 100%);
  color: var(--white);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
  z-index: 999;
  cursor: pointer;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.journey-year-fab:active {
  transform: scale(0.95);
}

/* ---------- bottom sheet ---------- */

.journey-sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  z-index: 9999;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.journey-sheet-backdrop.show {
  opacity: 1;
}

.journey-sheet {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  max-height: 85vh;
  overflow-y: auto;
  background: var(--bg-primary);
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
  transform: translateY(100%);
  transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.journey-sheet.show {
  transform: translateY(0);
}

.journey-sheet-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--bg-tertiary);
}

.journey-sheet-header h3 {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-primary);
}

.journey-sheet-close {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  font-size: 28px;
  line-height: 1;
  color: var(--text-secondary);
  cursor: pointer;
}

.journey-year-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.5rem;
  padding: 1rem 1.25rem 1.75rem;
}

.journey-year-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 52px;
  padding: 0.4rem 0.25rem;
  border: 1px solid var(--bg-tertiary);
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  color: var(--text-primary);
  cursor: pointer;
  transition: var(--transition-fast);
  -webkit-tap-highlight-color: transparent;
}

.journey-year-btn:active {
  transform: scale(0.96);
  border-color: var(--secondary-color);
}

.journey-year-btn.is-top {
  background: rgba(39, 174, 96, 0.12);
  border-color: rgba(39, 174, 96, 0.4);
}

.journey-year-label {
  font-size: 0.9rem;
  font-weight: 700;
}

.journey-year-count {
  font-size: 0.68rem;
  color: var(--text-muted);
}

@media (max-width: 380px) {
  .journey-year-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ============================================================
   Toolbar — filters, spacing toggle, density toggle
   ============================================================ */

.journey-scope {
  /* Stays on the body font — it is a filter indicator, not part of the title,
     and would otherwise inherit the serif from .journey-header h1. */
  font-family: var(--font-primary);
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--secondary-color);
  vertical-align: middle;
  margin-left: 0.4rem;
}

.journey-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem 1.25rem;
  margin-bottom: 1.25rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--bg-tertiary);
}

.journey-filters,
.journey-options {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
}

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

.journey-field > span {
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}

.journey-select {
  max-width: 220px;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--medium-gray);
  border-radius: var(--radius-md);
  background: var(--bg-primary);
  color: var(--text-primary);
  font-size: 0.82rem;
  font-family: inherit;
  cursor: pointer;
}

.journey-select:focus {
  outline: none;
  border-color: var(--secondary-color);
  /* #3498db is the accent RETIRED on 2026-08-24, so the glow and the
     border-color directly above it -- which resolves to the CURRENT accent
     #2a5580 -- were two different blues on one control. Dark sets
     `box-shadow: none` at 0-3-0 below, so this is light-only in effect. */
  box-shadow: 0 0 0 3px rgba(42, 85, 128, 0.18);
}

.journey-year-range {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.journey-year-range .journey-select {
  max-width: 90px;
}

.journey-year-dash {
  color: var(--text-muted);
}

.journey-reset {
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--medium-gray);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.78rem;
  font-family: inherit;
  cursor: pointer;
  transition: var(--transition-fast);
}

.journey-reset:hover {
  border-color: var(--danger-color);
  color: var(--danger-color);
}

/* segmented toggles */

.journey-segmented {
  display: inline-flex;
  padding: 2px;
  border-radius: var(--radius-md);
  background: var(--bg-tertiary);
}

.journey-seg {
  padding: 0.32rem 0.7rem;
  border: none;
  border-radius: calc(var(--radius-md) - 2px);
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.78rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: var(--transition-fast);
}

.journey-seg.is-active {
  background: var(--bg-primary);
  color: var(--text-primary);
  box-shadow: var(--shadow-xs);
}

.journey-check {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  color: var(--text-secondary);
  cursor: pointer;
  user-select: none;
}

.journey-check input {
  width: 15px;
  height: 15px;
  accent-color: var(--secondary-color);
  cursor: pointer;
}

/* The density toggle only means something on the mobile layouts. */
.journey-mobile-only {
  display: none;
}

@media (max-width: 767px) {
  .journey-mobile-only {
    display: inline-flex;
  }

  .journey-toolbar {
    gap: 0.6rem;
    margin-bottom: 1rem;
  }

  .journey-filters,
  .journey-options {
    width: 100%;
    gap: 0.5rem;
  }

  .journey-field {
    flex: 1 1 100%;
  }

  .journey-select {
    max-width: none;
    width: 100%;
    padding: 0.5rem 0.6rem;
    font-size: 0.85rem;
  }

  .journey-year-range .journey-select {
    max-width: none;
    flex: 1;
  }

  .journey-options {
    justify-content: space-between;
  }

  .journey-scope {
    display: block;
    margin: 0.2rem 0 0;
    font-size: 0.85rem;
  }
}

/* ============================================================
   Node detail — popover (desktop) / bottom sheet (mobile)
   ============================================================ */

.journey-popover {
  position: absolute;
  z-index: 1200;
  width: 300px;
  padding: 1rem 1.1rem 1.1rem;
  border-radius: var(--radius-lg);
  background: var(--bg-primary);
  border: 1px solid var(--bg-tertiary);
  box-shadow: 0 18px 45px rgba(44, 62, 80, 0.28);
  opacity: 0;
  transform: translateY(6px) scale(0.98);
  transition: opacity 0.16s ease, transform 0.16s ease;
}

.journey-popover.show {
  opacity: 1;
  transform: translateY(0) scale(1);
}

.journey-popover-close {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  font-size: 22px;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: 50%;
}

.journey-popover-close:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
}

.journey-detail-sheet .journey-detail {
  padding: 1rem 1.25rem 2rem;
}

.journey-detail-top {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin-bottom: 0.85rem;
}

.journey-detail-photo {
  width: 68px;
  height: 68px;
  flex-shrink: 0;
  object-fit: cover;
  border-radius: 50%;
  background: var(--bg-tertiary);
  border: 3px solid var(--journey-ring, #c9cfe0);
}

.journey-detail-title {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.journey-detail-brand {
  font-size: 1rem;
  font-weight: 700;
  color: var(--text-primary);
}

.journey-detail-model {
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.journey-detail-flag {
  align-self: flex-start;
  margin-top: 3px;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px dashed var(--medium-gray);
  font-size: 0.66rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

.journey-detail-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-bottom: 0.75rem;
}

.journey-detail-badge {
  padding: 2px 8px;
  border-radius: 999px;
  /* Was rgba(102,126,234,0.14) on #5a6bd8 -- purple, and the last of the
     sweep's survivors. Now the accent tint, with the hairline dark has carried
     since 08-17 and light never had.
     ⚠️ ONLY RENDERS ON A MILESTONE WATCH (journey.js:996 guards it on
     node.milestones.length), so roughly 2 of every 3 nodes have no badge at
     all. A preview of this looks inert on most clicks and is not broken. */
  background: rgba(42, 85, 128, 0.10);
  color: #2a5580;
  border: 1px solid rgba(42, 85, 128, 0.30);
  font-size: 0.68rem;
  font-weight: 600;
}

.journey-detail-rows {
  margin: 0 0 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.journey-detail-rows > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}

.journey-detail-rows dt {
  flex-shrink: 0;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

.journey-detail-rows dd {
  margin: 0;
  text-align: right;
  font-size: 0.82rem;
  color: var(--text-primary);
}

.journey-detail-link {
  display: inline-block;
  padding: 0.45rem 0.85rem;
  border-radius: var(--radius-md);
  background: var(--gradient-primary);
  color: var(--white);
  font-size: 0.8rem;
  font-weight: 600;
  text-decoration: none;
  transition: var(--transition-fast);
}

.journey-detail-link:hover {
  filter: brightness(1.08);
}

@media (max-width: 767px) {
  .journey-detail-rows dd {
    font-size: 0.86rem;
  }

  .journey-detail-link {
    display: block;
    text-align: center;
    padding: 0.7rem;
  }
}

/* The sheet header already names the brand, so the body leads with the model. */
.journey-detail-sheet .journey-detail-brand {
  display: none;
}

.journey-detail-sheet .journey-detail-model {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-primary);
}

/* ============================================================
   Scroll animation

   Only active when the stage carries .is-animated, which the JS adds
   solely when prefers-reduced-motion is not set. The static render is
   therefore the default, not a state that has to be restored.
   ============================================================ */

.journey-stage.is-animated .journey-node {
  opacity: 0;
  transform: translateX(-50%) translateY(18px);
  transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

.journey-stage.is-animated .journey-node.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.journey-stage.is-animated .journey-ribbon {
  transition: stroke-dashoffset 0.12s linear;
}

@media (prefers-reduced-motion: reduce) {
  .journey-stage .journey-node,
  .journey-stage.is-animated .journey-node {
    opacity: 1 !important;
    transform: translateX(-50%) !important;
    transition: none !important;
  }

  .journey-ribbon {
    stroke-dasharray: none !important;
    stroke-dashoffset: 0 !important;
    transition: none !important;
  }

  .journey-node-btn,
  .journey-popover,
  .journey-sheet,
  .journey-sheet-backdrop {
    transition: none !important;
  }
}

/* ==========================================================================
   COLLECTION JOURNEY - dark theme
   ==========================================================================
   This page was the cleanest in the app when it was reached: no undefined
   custom properties, three inline styles, and a proper --journey-* variable
   layer drawing from main.css. Its dark theme was simply half-finished -- one
   rule, repointing two of the four journey variables (see the block at the top
   of this file, where --journey-ring-hover is now fixed).

   Everything below is scoped to dark. LIGHT IS DELIBERATELY UNTOUCHED, on
   Rex's instruction: every rule these override is currently unscoped, so it
   keeps serving light exactly as before.
   ========================================================================== */

/* THE RIBBON. Rex's call, from four ramps previewed live on the real page with
   the year pills toggled on top of each -- the two cannot be judged apart,
   because the pills sit ON the ribbon.

   Chosen: a LOW-CHROMA brass, not the full brass he first suggested. The
   reasoning that survived the preview: the ribbon is the largest permanently
   visible surface on the page, and brass is this theme's INTERACTION accent --
   the page is monochrome until you point at something. A saturated brass ramp
   at button strength would spend the accent on the one element that never
   responds, and weaken every brass thing sitting on top of it. Desaturated, it
   reads warm without claiming the accent, and a real #b08d57 pill still
   separates from it.

   The ramp still runs pale at the top to deep at the bottom. That direction is
   information -- the top is where the journey STARTS -- and it is carried by
   VALUE, not hue, which is exactly why the hue was free to change at all.

   Light keeps its blue, in the stop-color attributes in journey.js. */
[data-theme="dark"] .journey-ribbon-stop-1 {
  stop-color: #c2ae90;
}

[data-theme="dark"] .journey-ribbon-stop-2 {
  stop-color: #8f7d61;
}

[data-theme="dark"] .journey-ribbon-stop-3 {
  stop-color: #5c4f3c;
}

/* ⚠️ A THIRD SURVIVOR OF THE 2026-08-14 PURPLE SWEEP, on this same page. The
   ribbon casts drop-shadow(... rgba(102,126,234,0.35)) -- that is #667eea, the
   generic purple-blue removed from the rest of the app that day. It went
   unnoticed because a 10px blurred halo behind a blue ribbon reads as part of
   the ribbon. Behind a brass one it would not. Retinted to the ribbon's own
   mid stop so the glow belongs to what casts it. Light keeps the purple with
   its blue ribbon, where the pairing is at least internally consistent -- it
   goes on the light list rather than being changed here.
   ⚠️ CLOSED 2026-08-28: light is now rgba(42,85,128,0.35), the accent, so the
   sentence above about light keeping the purple is HISTORY, not current. */
[data-theme="dark"] .journey-ribbon {
  filter: drop-shadow(0 2px 10px rgba(143, 125, 97, 0.35));
}

/* The year pills, which sit ON the ribbon above. journey.css:221 records that
   pill and ribbon must not converge; with both now in the brass family the
   separation is carried entirely by VALUE -- a deeper, more saturated pill on
   a desaturated ribbon -- which is what Rex chose seeing them together. */
[data-theme="dark"] .journey-gap.is-year {
  background: linear-gradient(135deg, #a8814c 0%, #c49a5f 100%);
  color: #121212;
}

/* ⚠️ The same pill colour on a DIFFERENT class, which is why the rule above
   does not reach it: .journey-year-fab is a phone-only floating control,
   declared inside @media (max-width: 480px) at :316, and its own comment
   records that it deliberately matches .journey-gap.is-year -- and that it
   "does not appear at desktop widths, so it was not part of what Rex reviewed
   on the desktop." Without this it would have stayed blue on phones while the
   whole page around it went brass.

   No media query needed: this is 0-2-0 and the rule it overrides is 0-1-0,
   and media queries add no specificity. */
[data-theme="dark"] .journey-year-fab {
  background: linear-gradient(135deg, #a8814c 0%, #c49a5f 100%);
  color: #121212;
}

/* View full details is the primary action of the detail sheet, so it takes the
   brass fill. It was var(--gradient-primary), the heat-blued steel from the
   login page, which is right there and wrong here. */
[data-theme="dark"] .journey-detail-link {
  background: #b08d57;
  color: #121212;
}

/* ⚠️ THE PURPLE THAT SURVIVED THE 2026-08-14 SWEEP. rgba(102,126,234,0.14)
   with #5a6bd8 text -- the #667eea family removed from the whole app that day.
   The sweep caught this page's ribbon, year chips and mobile button and missed
   this one, because it only renders inside the detail popover. Over a #121212
   popover the tint composited to a dark purple-grey with dark purple text on
   it: Rex reported "First Tissot" as unreadable, and it was.

   NOTE: on the TIMELINE the badges are a three-way colour code (.is-first
   purple, .is-brand blue, .is-top gold, deliberate -- see CLAUDE.md). In the
   popover all three share this ONE class, so they have never been colour-coded
   there. Left that way: the popover spells the meaning out in words ("First
   Tissot"), so hue carries nothing. Carrying the code through would mean
   adding a modifier class in journey.js:989. */
[data-theme="dark"] .journey-detail-badge {
  background: rgba(176, 141, 87, 0.18);
  color: #cbb894;
  border: 1px solid rgba(176, 141, 87, 0.45);
}

/* ⚠️ The popover's × was missed by the "all 27 close controls" pass (ddc8970)
   because that commit only touched components.css and this control lives in
   journey.css. Same values as the app-wide treatment at components.css:13534,
   so it now behaves like every other × in the product. */
[data-theme="dark"] .journey-popover-close {
  color: #c4c7c5;
  border-radius: 6px;
  transition: background 0.2s ease, color 0.2s ease;
}

[data-theme="dark"] .journey-popover-close:hover {
  background: #2d2d30;
  color: #cbb894;
}

/* The filter selects. border-color was var(--secondary-color) -- the rejected
   #4285f4 -- with a hardcoded blue glow beside it.

   ⚠️ color-scheme: dark is what turns the OPEN native popup from a white sheet
   into a dark one. The highlight bar on the options is the browser/OS accent
   and is NOT settable from CSS; that is settled, see components.css:6970 and
   :10898. Do not add rules trying to set it. */
[data-theme="dark"] .journey-select {
  color-scheme: dark;
}

[data-theme="dark"] .journey-select:hover {
  border-color: #cbb894;
}

[data-theme="dark"] .journey-select:focus {
  border-color: #cbb894;
  box-shadow: none;
}

/* Include divested. accent-color was var(--secondary-color), so the checked
   box was the rejected blue; color-scheme darkens the empty box. */
[data-theme="dark"] .journey-check input {
  accent-color: #b08d57;
  color-scheme: dark;
}

/* The year picker's press state, same rejected blue. */
[data-theme="dark"] .journey-year-btn:active {
  border-color: #cbb894;
}

/* The filter indicator inside the page title -- "Your Collection Journey ·
   Rex · 2020-2024". It renders ONLY when a filter is active (journey.js:303
   gates it on scope.length), which is why it was not in the list Rex compiled
   from looking at the page. Also var(--secondary-color), so also #4285f4.

   Takes the brass TINT #cbb894, not the fill #b08d57: this is small text, and
   the palette picks between the two brasses by area. */
[data-theme="dark"] .journey-scope {
  color: #cbb894;
}

/* THE NOTICE, dark half. Rex asked for this one to move in BOTH themes --
   unlike everything else in this session, which is light-only.

   ⚠️ IT HAD NO DARK RULE AT ALL. One base rule served both themes, so the
   amber wash rgba(243,156,18,0.12) was the LITERALLY IDENTICAL declaration in
   light and dark -- this project's own "no pass has ever touched it" signature,
   and the reason it survived the whole dark walk of 2026-08-17.

   Dark's own values, not light's: #2d2d30 is the raised card surface in dark
   (1.21 against the #1e1e1e page), #3c4043 is the app's dark hairline (91 uses
   in components.css), and the bar takes --warning-color, which is #fbbc04 in
   dark -- 8.04 against the fill.

   ⚠️ NOT BRASS. Brass is dark's INTERACTION accent; this bar is semantic and
   nothing here is clickable, so it stays in the warning family.

   ⚠️ ORDER: border-color sets all four edges INCLUDING the left, so
   border-left-color must come after it or the bar turns grey. */
[data-theme="dark"] .journey-notice {
  background: #2d2d30;
  border-color: #3c4043;
  border-left-color: var(--warning-color);
}

/* ===========================================================================
   LIGHT
   ---------------------------------------------------------------------------
   The first and only [data-theme="light"] block in this file. Every other
   light change on this page was made IN PLACE, in the base rules, because dark
   already overrides them -- which keeps specificity and source order untouched
   and cannot shift light's own cascade. This one rule cannot be done that way.

   ⚠️ .journey-detail-photo IS THE ONE JOURNEY RULE NOTHING CAN THEME IN PLACE,
   AND THE REASON IS STRUCTURAL, NOT COSMETIC.

   All four --journey-* variables are declared on `.journey-view`. But the node
   detail is built by document.body.appendChild -- .journey-popover on desktop
   (journey.js:923) and .journey-sheet.journey-detail-sheet under 768px
   (journey.js:906). Both are SIBLINGS of .journey-view, not descendants, so
   they inherit none of those variables and `var(--journey-ring, #c9cfe0)`
   serves its FALLBACK, in both themes, forever.

   ⚠️ THE FOURTH FAILURE SHAPE, AND NO AUDIT OF UNDEFINED PROPERTIES CAN SEE
   IT: the declaration is perfectly VALID. It resolves, it paints, it looks
   designed. The only tell is that it measures IDENTICAL in light and dark --
   3px solid rgb(201,207,224) in both, confirmed by getComputedStyle -- which
   this project already records as a defect signature, because nothing in this
   app paints a surface the same in both themes.

   ⚠️ AND IT IS THE ONLY ONE OF THE FOUR --journey-* CONSUMERS CARRYING A
   FALLBACK, which suggests someone met the missing value and papered over the
   symptom rather than finding the cause.

   Rex's decision, 2026-08-28: accent blue, LIGHT ONLY. So DARK IS KNOWINGLY
   LEFT ON THE LIGHT-THEME GREY #c9cfe0 -- it is a real dark defect, it is
   recorded here and in COLOUR-DECISIONS.md, and it is NOT an oversight.
   ⛔ Do not "fix" dark by moving the variable declarations up to :root without
   asking: that changes the node ring and the gap chips in both themes too,
   which is a much larger change than the one symptom it cures. */
/* THE POPUP DISC. Same element type as the node, same
   `background: var(--bg-tertiary)`, same transparent cutout in front of it --
   so it had the same tan. Held back from the node's change deliberately,
   because Rex had named only the node; he looked at the result and asked for
   the popup to match. Recorded that way round on purpose: the two are one
   design decision, but it was HIS to make, not a tidy-up to bundle in.

   ⚠️ LIGHT ONLY, which is what KEEPS the two matched. In dark both elements
   resolve --bg-tertiary to #2d2d30 already, so scoping this to light leaves
   node and popup identical in both themes; making it theme-agnostic would
   have broken the match it exists to create. */
[data-theme="light"] .journey-detail-photo {
  background: var(--white);
  border-color: #2a5580;
}

/* THE NODE DISC. --bg-tertiary is #eae8e6 in light -- the tan -- and it is not
   a fallback that rarely shows: EVERY journey thumbnail is a transparent WebP
   cutout (photo_thumb_path, 160x160, verified with sharp), and the two nodes
   without a photo get the deliberately background-free placeholder SVG. So
   this fill is the visible disc behind 133 of user_id=1's 133 nodes.

   ⚠️ --bg-tertiary is the INSET/WELL colour, and on a 96%-lightness page a
   darker fill reads RECESSED. That is right for the two other things in this
   file using it -- the .journey-segmented track and the close button's hover
   -- and wrong for a disc a watch sits on. The same role-name-inverts-the-
   visual-role trap already recorded on the back button and the Watch Details
   rail.

   ⚠️ LIGHT ONLY, at Rex's instruction. In dark --bg-tertiary is #2d2d30, which
   reads as raised on a near-black page and is correct there.

   ⚠️ THE TRADE, and Rex chose it knowingly: the disc-to-page contrast FALLS
   (1.12 -> 1.07), so the node stops reading as a coin; the ring gains 20%
   (1.53 -> 1.84) and does the shape work instead, which is what makes the
   cutout itself the thing you see.

   ⛔ Do NOT redefine --bg-tertiary to achieve this. It is a defined, correct
   variable doing its job everywhere else in the app.

   📌 .journey-detail-photo carries the IDENTICAL background and was
   deliberately NOT changed -- Rex asked for the node. It is a named control in
   css-tools/targets.js so the diff proves it held. If the popup disc should
   match, that is a separate decision. */
[data-theme="light"] .journey-node-photo {
  background: var(--white);
}
