/* Roadmap timeline — a vertical spine with alternating cards.
   Replaces the branching flow chart: every TechForge roadmap is linear at
   heart (a trunk of steps, each with its own sections), so a timeline says the
   same thing with less machinery and reads far better on a phone.

   Colour is assigned per band (Foundations, Core language, Prove it…), not per
   step. PRODUCT.md principle 3: the accent is wayfinding, so the hue tells you
   which phase you are in rather than just adding variety. */

/* The ink used on top of a band colour. This has to flip with the theme: the
   band palette inverts between them (--cyan is #22d3ee in dark, #042533 in
   light), so a fixed near-black gave the numbered spine nodes 1.27:1 in light
   theme — the step numbers were there and unreadable. It was only ever a
   fallback value; nothing declared it. */
:root { --rt-ink: #04070d; }
html.light { --rt-ink: #ffffff; }

.rt-page { width: 100%; }
.rt-main {
  max-width: 1080px;
  margin: 0 auto;
  /* clears the fixed topbar */
  padding: calc(var(--topbar-h) + var(--safe-top) + 22px) max(20px, var(--safe-left, 0px)) 72px max(20px, var(--safe-right, 0px));
}

.rt-crumbs { display: flex; align-items: center; gap: 8px; font-size: 12px; margin-bottom: 18px; }
.rt-crumbs a { display: inline-flex; align-items: center; gap: 6px; color: var(--text2); text-decoration: none; }
.rt-crumbs a:hover { color: var(--text); }

.rt-head { text-align: center; margin-bottom: 40px; }
.rt-eyebrow {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 4px; font-weight: 600;
  color: var(--rt-accent, var(--blue));
  display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 12px;
}
.rt-eyebrow::before, .rt-eyebrow::after {
  content: ''; width: 40px; height: 1px;
  background: linear-gradient(90deg, transparent, var(--rt-accent, var(--blue)));
}
.rt-eyebrow::after { background: linear-gradient(90deg, var(--rt-accent, var(--blue)), transparent); }
.rt-h1 {
  margin: 0; font-family: var(--font-display);
  font-size: clamp(26px, 4.2vw, 42px); font-weight: 800; letter-spacing: -.5px;
  color: var(--text); text-wrap: balance;
}
.rt-h1 span { color: var(--rt-accent, var(--blue)); }
.rt-sub {
  margin: 12px auto 0; max-width: 62ch;
  font-size: 13.5px; line-height: 1.75; color: var(--text2); text-wrap: pretty;
}
.rt-count {
  margin-top: 12px; font-family: var(--font-mono); font-size: 11px;
  font-weight: 600; letter-spacing: 3px; text-transform: uppercase; color: var(--text3);
}

/* ── the spine ─────────────────────────────────────────────────────── */
.rt-road { position: relative; }
.rt-road::before {
  content: ''; position: absolute; top: 6px; bottom: 6px; left: 50%;
  width: 3px; transform: translateX(-50%);
  background: var(--border2); border-radius: 3px; z-index: 0;
}
/* How far the reader has come. Scaled with transform rather than sized with
   height: a height transition is a layout property, and this repaints on every
   scroll frame. transform-origin pins the growth to the top. */
.rt-fill {
  position: absolute; top: 6px; bottom: 6px; left: 50%; width: 3px;
  transform: translateX(-50%) scaleY(0);
  transform-origin: top center;
  background: var(--rt-accent, var(--blue));
  border-radius: 3px; z-index: 0;
  will-change: transform;
}

.rt-steps { list-style: none; margin: 0; padding: 0; }
.rt-step {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: 1fr 44px 1fr;
  column-gap: 24px; align-items: center; margin-bottom: 30px;
}
.rt-step:last-child { margin-bottom: 0; }

/* alternate sides; nth-child drives the whole layout, no side classes */
.rt-step:nth-child(odd) .rt-card { grid-column: 1; grid-row: 1; justify-self: end; flex-direction: row; }
.rt-step:nth-child(even) .rt-card { grid-column: 3; grid-row: 1; justify-self: start; flex-direction: row-reverse; }
.rt-step .rt-node { grid-column: 2; grid-row: 1; justify-self: center; }

/* 560, not 420: at 420 the card filled a third of a 1040px lane and the road
   ran 5.8 screens, so you never saw more than one step at a time and the whole
   thing read as a list of articles. Wider cards let the pills sit two-up and
   put the shape of the path back on screen. */
.rt-card { display: flex; align-items: center; gap: 14px; max-width: 560px; width: 100%; }

/* ── node on the spine ─────────────────────────────────────────────── */
.rt-node {
  position: relative; width: 34px; height: 34px; border-radius: 9px;
  background: var(--c); color: var(--rt-ink, #04070d);
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 12px; font-family: var(--font-mono);
  border: 2px solid var(--bg);
}
/* the short bridge from spine to card */
.rt-step .rt-node::before {
  content: ''; position: absolute; top: 50%; width: 24px; height: 2px;
  background: var(--c); transform: translateY(-50%);
}
.rt-step:nth-child(odd) .rt-node::before { right: 100%; }
.rt-step:nth-child(even) .rt-node::before { left: 100%; }

/* The step icon, in the card header beside the title rather than as a chip
   floating outside the card. Freeing that column is also what lets the
   subnodes sit two-up. */
.rt-ico {
  flex: none; width: 24px; height: 24px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center; font-size: 14px;
  background: color-mix(in srgb, var(--c) 16%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--c) 45%, var(--border));
  color: var(--c);
}

/* ── card ──────────────────────────────────────────────────────────── */
.rt-body {
  background: var(--card, var(--surface));
  /* tinted all the way round, the same treatment the pills get. The band
     colour already arrives four other ways here — chip, spine node, title,
     pill borders — so a bar down one edge was a fifth telling of the same
     thing. */
  border: 1px solid color-mix(in srgb, var(--c) 26%, var(--border));
  border-radius: 12px; padding: 13px 16px;
  flex: 1; min-width: 0;
  transition: transform var(--m-base, .18s) var(--m-out, ease), border-color var(--m-base, .18s) var(--m-out, ease);
}
.rt-card:hover .rt-body { transform: translateY(-3px); border-color: color-mix(in srgb, var(--c) 55%, var(--border)); }
.rt-band {
  font-family: var(--font-mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: 1.6px; text-transform: uppercase; color: var(--text3); margin-bottom: 5px;
}
.rt-title {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font-mono); font-size: 13px; font-weight: 700;
  letter-spacing: .3px; text-transform: uppercase; color: var(--c);
  text-decoration: none;
}
a.rt-title:hover { text-decoration: underline; text-underline-offset: 3px; }
/* A step with no page yet keeps the band colour so the spine still reads as one
   journey; the tag beside it, not a dimmer colour, is what says "not written". */
.rt-soon-title { cursor: default; }
.rt-desc { margin: 7px 0 0; font-size: 12.5px; line-height: 1.65; color: var(--text2); }
/* ── subnodes ──────────────────────────────────────────────────────
   Pills, not bullets: a bullet reads as a list item, a pill reads as a node
   on a diagram, which is the point of showing the branch at all. Left
   unfilled — the step nodes are filled, and if these were too the two levels
   would carry equal weight and the hierarchy would flatten. */
.rt-list {
  list-style: none;
  margin: 9px 0 0;
  padding: 0 0 0 15px;
  display: grid;
  /* two-up once there is room. Pill labels run 5 to 34 characters, so a fixed
     two columns leaves a sawtooth; auto-fill with a floor packs them and still
     drops to one column inside a narrow card. */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 5px 10px;
  position: relative;
}
/* the vertical guide the branches hang from */
.rt-list::before {
  content: ''; position: absolute; left: 3px; top: 4px; bottom: 10px;
  width: 1px; background: color-mix(in srgb, var(--c) 40%, var(--border));
}
/* The row IS the pill: one border around a leading badge and a label, so every
   subnode in a card is the same shape and the same width. Ragged widths — 47px
   beside 290px — were reading as a bulleted list rather than a row of nodes. */
/* Deliberately not `.rt-list li.rt-item` — that element selector outranks the
   .rt-done and .rt-item-soon modifiers below, which is how the done border and
   the dashed coming-soon border both silently stopped applying. */
.rt-item {
  position: relative;
  display: flex; align-items: stretch;
  background: var(--surface2, var(--surface));
  /* the band colour tints the whole border rather than sitting as a bar down
     one edge — same wayfinding, one less visual device */
  border: 1px solid color-mix(in srgb, var(--c) 32%, var(--border));
  border-radius: var(--r-md, 8px);
  overflow: hidden;
  transition: border-color var(--m-base, .18s) var(--m-out, ease);
}
.rt-item:hover { border-color: var(--c); }

.rt-pill-node {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  font-size: 12.5px; line-height: 1.5;
  color: var(--text2);
  background: none; border: 0;
  text-decoration: none;
  overflow-wrap: break-word;          /* 34-char labels in a 227px card */
  transition: color var(--m-base, .18s) var(--m-out, ease);
}
a.rt-pill-node:hover { color: var(--text); }

/* ── the badge ─────────────────────────────────────────────────────────
   Every row carries one, so a group of four reads as four of the same thing.
   At rest it is the position in the required sequence (a middle dot for the
   optional branches, which are deliberately unnumbered — that is what makes
   them visibly not part of the run). Where the row maps to a lesson it is also
   the button that marks it done, showing the tick on hover and keeping it once
   ticked. aria-hidden on the number: the list is already in order for a screen
   reader, so hearing "one" before every label is noise. */
.rt-badge {
  flex: none; width: 26px; align-self: stretch;
  display: grid; place-items: center;
  padding: 0; margin: 0;
  font-family: var(--font-mono); font-size: 10px; font-weight: 700;
  color: color-mix(in srgb, var(--c) 75%, var(--text2));
  background: color-mix(in srgb, var(--c) 9%, transparent);
  border: 0;
  border-right: 1px solid color-mix(in srgb, var(--c) 22%, var(--border));
  transition: background var(--m-base, .16s) var(--m-out, ease),
              color var(--m-base, .16s) var(--m-out, ease);
}
.rt-badge-btn { cursor: pointer; font: inherit; }
.rt-badge-c { display: none; font-size: 12px; line-height: 1; }
.rt-badge-btn:hover { background: color-mix(in srgb, var(--c) 20%, transparent); color: var(--c); }
.rt-badge-btn:hover .rt-badge-n { display: none; }
.rt-badge-btn:hover .rt-badge-c { display: block; }
.rt-badge-btn:focus-visible { outline: 2px solid var(--c); outline-offset: -2px; }

/* ── ticking things off ────────────────────────────────────────────────
   The thing that turns a list of topics into a route you are walking. Reads
   and writes the same store as the lesson pages, so the dashboard agrees. */
.rt-tick {
  flex: none; width: 19px; height: 19px; align-self: center;
  display: flex; align-items: center; justify-content: center;
  padding: 0; cursor: pointer;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--c) 40%, var(--border));
  border-radius: 6px;
  color: transparent;                 /* the check is there, just not inked yet */
  transition: background var(--m-base, .16s) var(--m-out, ease),
              border-color var(--m-base, .16s) var(--m-out, ease),
              color var(--m-base, .16s) var(--m-out, ease);
}
.rt-tick i { font-size: 12px; line-height: 1; }
.rt-tick:hover { border-color: var(--c); color: color-mix(in srgb, var(--c) 55%, transparent); }
.rt-tick:focus-visible { outline: 2px solid var(--c); outline-offset: 2px; }
.rt-tick[aria-pressed="true"] {
  background: var(--c); border-color: var(--c); color: var(--rt-ink, #04070d);
}

/* Done is shown by the filled badge and a struck label — never by fading the
   text out. Dimming state with opacity has broken contrast on this site three
   times; a line through the words survives any theme. */
.rt-done > .rt-pill-node,
.rt-done > .rt-head-row > .rt-title {
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--c) 70%, transparent);
  text-decoration-thickness: 1.5px;
}
.rt-item.rt-done { border-color: color-mix(in srgb, var(--c) 55%, var(--border)); }
.rt-done > .rt-badge {
  background: var(--c);
  color: var(--rt-ink, #04070d);
  border-right-color: var(--c);
}
.rt-done > .rt-badge .rt-badge-n { display: none; }
.rt-done > .rt-badge .rt-badge-c { display: block; }

.rt-head-row { display: flex; align-items: center; gap: 8px; }
.rt-head-row .rt-title { flex: 1; min-width: 0; }

/* ── progress summary ──────────────────────────────────────────────── */
.rt-progress {
  display: flex; align-items: center; gap: 12px;
  max-width: 420px; margin: 0 auto 26px;
}
.rt-pbar {
  flex: 1; height: 6px; border-radius: 999px;
  background: color-mix(in srgb, var(--rt-accent, var(--blue)) 14%, var(--border));
  overflow: hidden;
}
/* scaleX rather than width, matching .rt-fill: this animates on every tick, and
   a width transition relayouts the bar each frame. */
.rt-pbar-fill {
  width: 100%; height: 100%; border-radius: 999px;
  background: var(--rt-accent, var(--blue));
  transform-origin: left center;
  transition: transform var(--m-slow, .4s) var(--m-out, ease);
}
.rt-pnum {
  flex: none; font-family: var(--font-mono); font-size: 11px;
  letter-spacing: .4px; color: var(--text2);
}
.rt-pnum strong { color: var(--rt-accent, var(--blue)); }
a.rt-pill-node:focus-visible { outline: 2px solid var(--c); outline-offset: 2px; }

/* Not written yet. Dashed border and a tag — never dimmed with opacity, which
   is how contrast was broken twice in this codebase. */
.rt-item-soon { border-style: dashed; }
.rt-pill-node.rt-soon { cursor: default; }
.rt-tag {
  font-family: var(--font-mono); font-size: 8.5px; font-weight: 700;
  letter-spacing: 1px; text-transform: uppercase;
  color: var(--text3); margin-left: 6px;
  border: 1px solid var(--border2); border-radius: 4px; padding: 0 4px;
}

/* ── collapse ──────────────────────────────────────────────────────
   grid-template-rows 1fr → 0fr animates the height without animating height. */
.rt-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; margin-left: 6px; padding: 0;
  background: none; border: 1px solid var(--border); border-radius: 5px;
  color: var(--text3); cursor: pointer; vertical-align: middle;
  transition: color var(--m-base, .18s) var(--m-out, ease), border-color var(--m-base, .18s) var(--m-out, ease);
}
.rt-toggle:hover { color: var(--c); border-color: var(--c); }
.rt-toggle:focus-visible { outline: 2px solid var(--c); outline-offset: 2px; }
.rt-toggle i { font-size: 13px; line-height: 1; transition: transform var(--m-base, .2s) var(--m-out, ease); }
.rt-toggle[aria-expanded="false"] i { transform: rotate(-90deg); }

.rt-wrap { display: grid; grid-template-rows: 1fr; }
.rt-wrap[data-open="false"] { grid-template-rows: 0fr; }
/* visibility, not just zero height: a 0fr row still leaves its links in the tab
   order, so keyboard focus would disappear into a collapsed branch. Hiding is
   delayed by the collapse duration so the pills don't vanish before the box
   finishes shrinking; revealing is immediate. */
.rt-wrap > .rt-list { overflow: hidden; min-height: 0; visibility: visible; transition: visibility 0s; }
.rt-wrap[data-open="false"] > .rt-list { visibility: hidden; transition: visibility 0s var(--m-base, .22s); }
@media (prefers-reduced-motion: no-preference) {
  .rt-wrap { transition: grid-template-rows var(--m-base, .22s) var(--m-out, ease); }
}

/* ── mini-map rail ─────────────────────────────────────────────────── */
.rt-map {
  position: fixed; right: 20px; top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 9px;
  padding: 14px 9px; border-radius: 20px; z-index: 50;
  background: color-mix(in srgb, var(--surface) 78%, transparent);
  border: 1px solid var(--border); backdrop-filter: blur(8px);
}
.rt-dot {
  width: 9px; height: 9px; border-radius: 50%; padding: 0; border: none;
  cursor: pointer; background: var(--border2); flex: none;
  transition: background-color var(--m-base, .25s) var(--m-out, ease);
}
.rt-dot:hover { background: color-mix(in srgb, var(--c) 60%, var(--border2)); }
.rt-dot[aria-current="true"] { width: 13px; height: 13px; background: var(--c); }
.rt-dot:focus-visible { outline: 2px solid var(--accent, var(--blue)); outline-offset: 3px; }

/* ── progress pill ─────────────────────────────────────────────────── */
.rt-pill {
  position: fixed; left: 50%; bottom: 26px; z-index: 50;
  transform: translateX(-50%) translateY(16px); opacity: 0; pointer-events: none;
  background: var(--surface); border: 1px solid var(--border); color: var(--text2);
  padding: 8px 18px; border-radius: 999px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 1.4px;
  transition: opacity var(--m-base, .3s) var(--m-out, ease), transform var(--m-base, .3s) var(--m-out, ease);
}
.rt-pill.rt-on { opacity: 1; transform: translateX(-50%) translateY(0); }
.rt-pill strong { color: var(--c, var(--blue)); }

/* ── reveal + active ───────────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  .rt-step { opacity: 0; transform: translateY(26px); transition: opacity .6s var(--m-out, ease), transform .6s var(--m-out, ease); }
  .rt-step.rt-seen { opacity: 1; transform: none; }
  .rt-node { transition: transform .25s var(--m-out, ease); }
  .rt-node.rt-active { transform: scale(1.2); }
  .rt-fill { transition: transform .12s linear; }
}

.rt-foot {
  margin-top: 34px; text-align: center;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text2); padding: 16px 20px; border-radius: 12px;
  font-family: var(--font-mono); font-weight: 700; letter-spacing: 1.4px;
  font-size: clamp(10px, 1.5vw, 12px);
}
.rt-foot span { color: var(--rt-accent, var(--blue)); }

/* ── phone ─────────────────────────────────────────────────────────
   The spine moves to the left edge and the cards stop alternating: at 390px
   two columns of card would be about 160px each, which is narrower than the
   descriptions can be read at. Everything else that makes this look like a
   roadmap rather than a list is kept — the numbered spine nodes, the branch
   rail, and subnodes still sitting two-up. */
@media (max-width: 860px) {
  .rt-map { display: none; }
  .rt-road::before, .rt-fill { left: 13px; }
  .rt-step { grid-template-columns: 26px 1fr; column-gap: 10px; }
  .rt-step:nth-child(odd) .rt-card,
  .rt-step:nth-child(even) .rt-card { grid-column: 2; grid-row: 1; justify-self: stretch; flex-direction: row; }
  .rt-step .rt-node { grid-column: 1; grid-row: 1; justify-self: center; }
  .rt-step:nth-child(odd) .rt-node::before,
  .rt-step:nth-child(even) .rt-node::before { left: 100%; right: auto; width: 10px; }
  .rt-card { max-width: 100%; }
  .rt-body { padding: 12px 12px; }
  .rt-node { width: 26px; height: 26px; font-size: 10px; border-radius: 7px; }
  /* narrower floor so the subnodes still pair up once the gutter is reclaimed */
  .rt-list { grid-template-columns: repeat(auto-fit, minmax(124px, 1fr)); gap: 5px 7px; }
  /* the card body is ~227px at 375px, so the branch column shrinks rather than
     pushing pill text into one word per line */
  .rt-list { padding-left: 11px; }
  .rt-list::before { left: 2px; }
  /* 24px is the floor: the badge is a tap target, and WCAG 2.5.8 asks for
     24×24. Its height already comes from the row. */
  .rt-badge { width: 24px; font-size: 9.5px; }
}
