/* ==============================================================
   login-recap.css
   Post sign-in recap: the full-screen takeover that plays the
   "while you were away" numbers before the dashboard loads.

   Loaded after login-modal.css, which owns the scene tokens
   (--lm-hero-*, --lm-recap-grad, --lm-node-*, --lm-enter-*,
   --lm-ink-rgb, --lm-pulse) for both light and dark themes.
   Nothing here hardcodes a colour, so the theme toggle just works.
   ============================================================== */

.lm-recap {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  color: var(--lm-hero-text);
  opacity: 0;
  transition: opacity 0.5s ease;
}

.lm-recap[hidden] { display: none; }
.lm-recap.is-open { opacity: 1; }

body.lm-recap-active { overflow: hidden; }


/* ---- Backdrop -------------------------------------------------
   Three layers: the drifting brand gradient, a slow aurora sweep
   that keeps the field from reading flat, and the dot grain. */

.lm-recap-bg {
  position: absolute;
  inset: 0;
  z-index: -3;
  background: var(--lm-recap-grad);
  animation: lm-recap-drift 22s ease-in-out infinite alternate;
}

.lm-recap-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(var(--lm-grain) 1px, transparent 1px);
  background-size: 24px 24px;
  -webkit-mask-image: radial-gradient(ellipse 70% 70% at center, #000 25%, transparent 78%);
  mask-image: radial-gradient(ellipse 70% 70% at center, #000 25%, transparent 78%);
}

.lm-recap-veil {
  position: absolute;
  inset: -20%;
  z-index: -2;
  pointer-events: none;
  opacity: 0.5;
  background:
    conic-gradient(
      from 200deg at 50% 45%,
      transparent 0deg,
      rgba(var(--lm-ink-rgb), 0.05) 90deg,
      transparent 200deg,
      rgba(var(--lm-ink-rgb), 0.04) 300deg,
      transparent 360deg
    );
  animation: lm-recap-sweep 38s linear infinite;
}

@keyframes lm-recap-drift {
  from { transform: scale(1)    translateY(0); }
  to   { transform: scale(1.08) translateY(-12px); }
}

@keyframes lm-recap-sweep {
  to { transform: rotate(360deg); }
}


/* ---- Controls: fast forward and skip ------------------------------
   Fast forward goes straight to the summary from any stage. Skip goes
   straight to the dashboard. A tap anywhere else moves one beat forward.
   Both buttons hide on the summary, where Enter dashboard is the one way
   out. */

.lm-recap-controls {
  position: absolute;
  top: 24px;
  right: 24px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 8px;
}

.lm-recap-skip,
.lm-recap-ff {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  color: var(--lm-hero-mute);
  background: var(--lm-hero-glass);
  border: 1px solid var(--lm-hero-rule);
  border-radius: 999px;
  padding: 9px 15px;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: color 0.15s, border-color 0.15s, transform 0.15s;
}

.lm-recap-skip:hover,
.lm-recap-ff:hover {
  color: var(--lm-hero-text);
  border-color: rgba(var(--lm-ink-rgb), 0.34);
}

.lm-recap-skip svg,
.lm-recap-ff svg { transition: transform 0.2s; }
.lm-recap-skip:hover svg,
.lm-recap-ff:hover svg { transform: translateX(2px); }

/* Fast forward is icon only: a circle the same height as the Skip pill. */
.lm-recap-ff {
  width: 36px;
  height: 36px;
  padding: 0;
  justify-content: center;
}


/* ---- Shell ---------------------------------------------------- */

.lm-recap-inner {
  position: relative;
  width: min(720px, 90vw);
  padding: 32px 40px 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  text-align: center;
}

.lm-recap.is-open .lm-recap-inner {
  animation: lm-recap-rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

@keyframes lm-recap-rise {
  from { opacity: 0; transform: translateY(12px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}


/* ---- Avatar ---------------------------------------------------
   The portrait sits inside a hairline ring with a slow conic
   stroke tracking around it, so the header has a heartbeat even
   while a stage is holding still. */

.lm-recap-avatar-wrap {
  position: relative;
  flex-shrink: 0;
  width: 106px;
  height: 106px;
  padding: 3px;
  border-radius: 50%;
  background: linear-gradient(
    135deg,
    rgba(var(--lm-ink-rgb), 0.12) 0%,
    rgba(var(--lm-ink-rgb), 0.04) 50%,
    rgba(var(--lm-ink-rgb), 0.10) 100%
  );
  box-shadow:
    0 2px 8px rgba(var(--lm-ink-rgb), 0.08),
    0 16px 40px -8px rgba(var(--lm-ink-rgb), 0.14);
}

body.theme-dark .lm-recap-avatar-wrap {
  background: linear-gradient(
    135deg,
    rgba(255, 255, 255, 0.14) 0%,
    rgba(255, 255, 255, 0.04) 50%,
    rgba(255, 255, 255, 0.10) 100%
  );
  box-shadow:
    0 2px 8px rgba(0, 0, 0, 0.22),
    0 16px 40px -8px rgba(0, 0, 0, 0.40);
}

/* Breathing halo */
.lm-recap-avatar-wrap::before {
  content: '';
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 1px solid var(--lm-hero-rule);
  pointer-events: none;
  animation: lm-recap-avatar-ring 3.2s ease-out infinite;
}

@keyframes lm-recap-avatar-ring {
  0%   { opacity: 0.55; transform: scale(1); }
  60%  { opacity: 0.16; transform: scale(1.12); }
  100% { opacity: 0.55; transform: scale(1); }
}

/* Tracking conic stroke, masked to a 1.5px ring */
.lm-recap-avatar-orbit {
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  pointer-events: none;
  background: conic-gradient(
    from 0deg,
    transparent 0deg,
    var(--lm-pulse) 40deg,
    transparent 110deg,
    transparent 360deg
  );
  -webkit-mask:
    radial-gradient(farthest-side, transparent calc(100% - 1.5px), #000 calc(100% - 1.5px));
  mask:
    radial-gradient(farthest-side, transparent calc(100% - 1.5px), #000 calc(100% - 1.5px));
  opacity: 0.85;
  animation: lm-recap-orbit 4.6s linear infinite;
}

@keyframes lm-recap-orbit {
  to { transform: rotate(360deg); }
}

.lm-recap-avatar {
  position: relative;
  display: block;
  width: 100px;
  height: 100px;
  max-width: 100px;
  max-height: 100px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--lm-hero-glass);
}

.lm-recap.is-open .lm-recap-avatar-wrap {
  animation: lm-recap-avatar-in 0.72s cubic-bezier(0.16, 1, 0.3, 1) 0.08s both;
}

@keyframes lm-recap-avatar-in {
  0%   { opacity: 0; transform: scale(0.60); filter: blur(8px); }
  55%  { opacity: 1; filter: blur(0); }
  75%  { transform: scale(1.04); }
  100% { opacity: 1; transform: scale(1); filter: blur(0); }
}


/* ---- Stages ---------------------------------------------------
   Every stage occupies the same grid cell so the outgoing and
   incoming ones overlap for a true crossfade. */

.lm-recap-stage-wrap {
  position: relative;
  width: 100%;
  min-height: 320px;
  display: grid;
  place-items: center;
}

.lm-recap-stage {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  width: 100%;
}

.lm-recap-stage[hidden] { display: none; }

.lm-recap-stage.is-in {
  animation: lm-recap-in 0.7s cubic-bezier(0.22, 0.61, 0.18, 1) both;
}

.lm-recap-stage.is-out {
  animation: lm-recap-out 0.42s cubic-bezier(0.4, 0, 0.2, 1) both;
  pointer-events: none;
}

@keyframes lm-recap-in {
  from { opacity: 0; transform: translateY(18px) scale(0.99); filter: blur(2px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

@keyframes lm-recap-out {
  from { opacity: 1; transform: none; filter: blur(0); }
  to   { opacity: 0; transform: translateY(-14px) scale(0.99); filter: blur(3px); }
}


/* ---- Kicker chip ---------------------------------------------- */

.lm-recap-kicker {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--lm-hero-text);
  background: var(--lm-hero-glass);
  border: 1px solid var(--lm-hero-rule);
  padding: 7px 14px;
  border-radius: 999px;
  backdrop-filter: blur(6px);
}

.lm-recap-kicker .pulse {
  position: relative;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--lm-pulse);
}

.lm-recap-kicker .pulse::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--lm-pulse);
  animation: lm-kicker-pulse 2.4s ease-out infinite;
}

/* Metric stages tint their own index chip with the stage accent */
.lm-recap-metric .lm-recap-kicker {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 34%, transparent);
}


/* ---- Headline + the animated hello ---------------------------- */

.lm-recap-greet {
  font-family: var(--font-display, 'General Sans', system-ui, sans-serif);
  font-weight: 600;
  letter-spacing: -0.03em;
  font-size: clamp(28px, 5vw, 48px);
  line-height: 1.14;
  margin: 0;
  color: var(--lm-hero-text);
  max-width: 20ch;
}

.lm-recap-greet em { font-style: italic; font-weight: 500; color: var(--lm-hero-mute); }

/* Stage 0 runs larger than the summary headline: it is the moment. */
.lm-recap-hello-line {
  font-size: clamp(34px, 7vw, 66px);
  letter-spacing: -0.045em;
  max-width: 22ch;
}

.lm-hello-word,
.lm-hello-name {
  display: inline-block;
}

.lm-hello-word { color: var(--lm-hero-mute); }

.lm-hello-name {
  color: var(--lm-hero-text);
  white-space: pre;
}

.lm-hello-ch {
  display: inline-block;
  will-change: transform, opacity, filter;
}

/* "Hello," lands first, then the name resolves character by
   character. The per-glyph delay reads --i off the inline style
   the JS writes, so the timing lives entirely in CSS. */
.lm-recap-hello.is-in .lm-hello-word {
  animation: lm-hello-word 0.66s cubic-bezier(0.16, 1, 0.3, 1) 0.10s both;
}

.lm-recap-hello.is-in .lm-hello-ch {
  animation: lm-hello-ch 0.62s cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: calc(var(--i, 0) * 42ms + 240ms);
}

@keyframes lm-hello-word {
  from { opacity: 0; transform: translateY(16px); filter: blur(6px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

@keyframes lm-hello-ch {
  from { opacity: 0; transform: translateY(18px) rotateX(38deg); filter: blur(8px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

/* Caption waits for the last glyph. --n is the name length, also
   written by the JS onto the stage. */
.lm-recap-hello.is-in .lm-recap-cap-late {
  animation: lm-recap-cap-in 0.6s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  animation-delay: calc(var(--n, 6) * 42ms + 620ms);
}

@keyframes lm-recap-cap-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}


/* ---- Metric stages -------------------------------------------- */

.lm-recap-metric { --accent: var(--lm-node-iris); }
.lm-recap-metric[data-accent='cobalt'] { --accent: var(--lm-node-cobalt); }
.lm-recap-metric[data-accent='green']  { --accent: var(--lm-node-green); }
.lm-recap-metric[data-accent='honey']  { --accent: var(--lm-node-honey); }
.lm-recap-metric[data-accent='iris']   { --accent: var(--lm-node-iris); }

.lm-recap-num {
  position: relative;
  font-family: var(--font-display, 'General Sans', system-ui, sans-serif);
  font-size: clamp(60px, 13vw, 116px);
  font-weight: 600;
  letter-spacing: -0.045em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  padding: 0 2px 6px;
  color: transparent;
  background-image: linear-gradient(168deg, var(--lm-hero-text) 18%, var(--accent) 128%);
  -webkit-background-clip: text;
  background-clip: text;
}

/* If background-clip:text is unavailable the number must not vanish. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .lm-recap-num {
    color: var(--lm-hero-text);
    background-image: none;
  }
}

/* Accent bloom behind the digits */
.lm-recap-metric::before {
  content: '';
  position: absolute;
  top: 46%;
  left: 50%;
  width: min(460px, 78%);
  height: 200px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(ellipse at center, var(--accent), transparent 68%);
  opacity: 0.16;
  filter: blur(28px);
  pointer-events: none;
  z-index: -1;
}

.lm-recap-stage.is-in .lm-recap-num {
  animation: lm-recap-num-pop 0.85s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

@keyframes lm-recap-num-pop {
  from { opacity: 0; transform: translateY(12px) scale(0.94); filter: blur(7px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

.lm-recap-cap {
  margin: 0;
  font-size: clamp(15px, 2vw, 19px);
  line-height: 1.5;
  color: var(--lm-hero-mute);
  max-width: 32ch;
}

.lm-recap-cap em { font-style: italic; color: var(--lm-hero-text); }


/* ---- Summary --------------------------------------------------- */

.lm-recap-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  width: 100%;
  border-top: 1px solid var(--lm-hero-rule);
  border-bottom: 1px solid var(--lm-hero-rule);
  margin: 4px 0;
  position: relative;
}

/* The thread's ending. The strip does not travel into the summary (that is a
   separate section, crossfaded in), so the line resolves into the one rule
   the summary already has: a stroke in the line's own colour draws left to
   right along the top border and then settles into it. The recap ends on the
   same pen it was drawn with instead of the line simply disappearing. */
.lm-recap-grid::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 0;
  height: 2px;
  width: 100%;
  background: var(--lm-line, #b75c4e);
  transform: scaleX(0);
  transform-origin: 0 50%;
  opacity: 0;
  pointer-events: none;
}

.lm-recap.is-summary .lm-recap-grid::before {
  animation: lm-recap-rule 760ms cubic-bezier(0.22, 1, 0.36, 1) 120ms forwards;
}

@keyframes lm-recap-rule {
  0%   { transform: scaleX(0); opacity: 1; }
  70%  { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0.25; }
}

@media (prefers-reduced-motion: reduce) {
  .lm-recap.is-summary .lm-recap-grid::before { animation: none; opacity: 0.25; transform: scaleX(1); }
}

/* Column layout with the label pushed to the bottom, so a value that wraps
   (long codes like UGX 91,400) does not drop its label below the others. */
.lm-recap-cell {
  display: flex;
  flex-direction: column;
  padding: 20px 8px;
}

.lm-recap-cell + .lm-recap-cell { border-left: 1px solid var(--lm-hero-rule); }

.lm-recap-summary.is-in .lm-recap-cell {
  animation: lm-recap-cell-in 0.6s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.lm-recap-summary.is-in .lm-recap-cell:nth-child(1) { animation-delay: 0.18s; }
.lm-recap-summary.is-in .lm-recap-cell:nth-child(2) { animation-delay: 0.28s; }
.lm-recap-summary.is-in .lm-recap-cell:nth-child(3) { animation-delay: 0.38s; }
.lm-recap-summary.is-in .lm-recap-cell:nth-child(4) { animation-delay: 0.48s; }

@keyframes lm-recap-cell-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

.lm-recap-cell .v {
  display: block;
  font-family: var(--font-display, 'General Sans', system-ui, sans-serif);
  font-size: clamp(20px, 2.6vw, 30px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.15;
  color: var(--lm-hero-text);
  font-variant-numeric: tabular-nums;
}

.lm-recap-cell .l {
  display: block;
  margin-top: auto;
  padding-top: 10px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--lm-hero-faint);
}

.lm-recap-enter {
  appearance: none;
  border: 0;
  cursor: pointer;
  margin-top: 4px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  color: var(--lm-enter-ink);
  background: var(--lm-enter-bg);
  border-radius: 12px;
  padding: 14px 22px;
  box-shadow: 0 10px 26px -14px rgba(20, 24, 34, 0.5);
  transition: transform 0.05s, background 0.15s;
}

.lm-recap-enter:hover { background: var(--lm-enter-bg-h); }
.lm-recap-enter:active { transform: translateY(1px); }
.lm-recap-enter svg { transition: transform 0.2s; }
.lm-recap-enter:hover svg { transform: translateX(3px); }


/* ---- Progress ---------------------------------------------------
   Each stage owns a segment that fills over its own duration. The
   JS writes --dur onto the active segment from the DURATIONS table. */

.lm-recap-progress {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}

.lm-recap-progress .dot {
  position: relative;
  width: 7px;
  height: 7px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--lm-hero-rule);
  transition: width 0.45s cubic-bezier(0.2, 0.7, 0.2, 1), background 0.3s;
}

.lm-recap-progress .dot.is-done { background: var(--lm-hero-mute); }

.lm-recap-progress .dot.is-on { width: 34px; }

.lm-recap-progress .dot.is-on::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--lm-pulse);
  transform-origin: left center;
  animation: lm-recap-fill var(--dur, 2600ms) linear both;
}

@keyframes lm-recap-fill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}


/* ---- Responsive -------------------------------------------------- */

@media (max-width: 720px) {
  .lm-recap-inner { padding: 24px 20px 32px; gap: 18px; }
  .lm-recap-stage-wrap { min-height: 300px; }
  .lm-recap-avatar-wrap { width: 88px; height: 88px; }
  .lm-recap-avatar { width: 82px; height: 82px; max-width: 82px; max-height: 82px; }
  .lm-recap-grid { grid-template-columns: repeat(2, 1fr); }
  .lm-recap-cell:nth-child(3) { border-left: 0; }
  .lm-recap-cell:nth-child(n + 3) { border-top: 1px solid var(--lm-hero-rule); }
  .lm-recap-controls { top: 14px; right: 14px; }
}

@media (max-width: 380px) {
  .lm-recap-cap { max-width: 26ch; }
}


/* ---- Reduced motion ---------------------------------------------
   login-modal.css already kills every animation and transition
   globally under this query. The hello also renders as plain text
   (no per-glyph spans) and the counters jump to their final value,
   both handled in login-v7.js. Only the fill segment needs pinning
   so the active step still reads as active. */

@media (prefers-reduced-motion: reduce) {
  .lm-recap-progress .dot.is-on::after { transform: scaleX(1); }
}


/* ---- Steps track + the one line ---------------------------------
   The four stat steps sit side by side on a translateX track inside a
   clipped, full-bleed viewport, so every connector runs from one edge of
   the screen to the other. The line's <svg> is a child of the track and
   moves with the steps pixel for pixel. In every step the icon slot sits
   at the top centre, where the avatar was, with the number and caption
   below it; the slot centre is the anchor the pen is held on during
   travel. */

.lm-recap-inner { width: min(960px, 92vw); }

/* The stage-wrap column is sized by the wrapper, never by the steps'
   content: the counters grow as they count and would otherwise widen the
   track mid-sequence. */
.lm-recap-stage-wrap { grid-template-columns: minmax(0, 100%); }

/* Full bleed: the stage is centred in its column, so a 100vw width
   overflows equally on both sides and lands on the screen edges. */
.lm-recap-steps { width: 100vw; min-width: 0; position: relative; }

.lm-recap-viewport {
  --recap-anchor: 0.5;
  position: relative;
  width: 100%;
  min-width: 0;
  overflow: hidden;
  contain: inline-size;
}

.lm-recap-track {
  position: relative;
  display: flex;
  align-items: stretch;
  width: 100%;
  transform: translate3d(0, 0, 0);
}

/* ---- The icon strip -------------------------------------------------
   Two layers at two speeds. The text track still moves a full viewport
   per beat; the icons move one slot. That difference is the whole point:
   a connector spans one slot instead of one screen, so its length is a
   constant instead of a function of the window, and the two layers read
   as parallax rather than as one block sliding.

   The strip sits where the slot used to sit inside each step, so the
   composition above the number is unchanged.

   --recap-slot is the strip's step. The line engine measures it from the
   slots themselves, so changing it here moves the geometry with it. */
/* The strip occupies exactly the band the icon used to occupy inside each
   step: same height, same place in the stack, so the chip, number and
   sentence below it do not move. It is the step's first row, matching the
   step's own 8px top padding. */
.lm-recap-strip {
  --recap-slot: 320px;
  /* Where the active icon rests, as a fraction of the viewport width. It must
     be declared HERE: the engine reads it off the strip, and the 0.5 on
     .lm-recap-viewport is on a sibling, not an ancestor, so the strip
     inherited nothing from it and silently fell back to the engine's own
     default. 0.5 puts the icon on the same centre axis as the text below. */
  --recap-anchor: 0.5;
  position: absolute;
  top: 8px;
  left: 0;
  width: 100%;
  height: 180px;
  min-width: 0;
  overflow: hidden;
  contain: inline-size;
  z-index: 1;
  pointer-events: none;
}

/* Centred on the viewport, not pinned to its left edge: the track is
   half a viewport wide at its origin, and the engine's translate then
   carries slot i to that centre. Without this the strip starts at x=0 and
   the active icon drifts left as the sequence runs. */
.lm-recap-strip-track {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: center;
  height: 100%;
  transform: translate3d(0, 0, 0);
  will-change: transform;
}

/* Fixed pixel step, never a percentage of the viewport: the constant slot
   is what bounds the thread. The slot is the STEP; the icon drawn inside it
   keeps its own size, so spacing and mark size stay independent. The engine
   measures the icon box (not the slot) for its scale. */
.lm-recap-strip .lm-recap-slot {
  width: var(--recap-slot);
  height: 180px;
  flex: 0 0 var(--recap-slot);
  display: flex;
  align-items: center;
  justify-content: center;
}

.lm-recap-icon-box {
  width: 180px;
  height: 180px;
  flex: none;
}

/* The ghost ladder. Scale carries "further back" and opacity only tops it
   up: the ground behind is very pale, so a 15% icon on its own reads as a
   smudge while a small one reads as distance. Nothing sits to the right of
   centre; the strip never previews the next mark. */
/* No transition here. The slot is an empty layout box: the visible icon is
   an svg path over it, and recap-line.js drives its scale and opacity from
   the transition timeline. A 420ms transition on this element was a second
   clock that nothing coordinated. */
.lm-recap-slot {
  width: 180px;
  height: 180px;
  transform-origin: 50% 50%;
}
/* The ladder itself lives in recap-line.js (GHOSTS), because the icons are
   svg paths in one svg over the strip, not children of these slots: styling
   the slot scaled an empty layout box and left every ghost at full size.
   data-ghost is still set on the slot as a hook for debugging and for the
   static mode below. */

.lm-recap-step {
  position: relative;
  flex: 0 0 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  min-height: 440px;
  /* The 180px top padding is the band the icon strip overlays. The strip is
     positioned on the stage, so each step reserves the space rather than
     holding the icon itself, and the text stack below starts exactly where
     it did when the slot was a real child here. */
  padding: 188px 24px 24px;
  text-align: center;
}

/* The text does not slide sideways any more, and it does not carry a CSS
   transition: the transition timeline in recap-line.js drives opacity and
   translateY on each of the three rows directly, so the text cannot drift
   from the strip and the thread. Only the resting state lives here. */
.lm-recap-step-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin-top: 40px;
  opacity: 1;
}

/* The three staggered rows. Transform is set per frame from the timeline. */
.lm-recap-step-text > * {
  will-change: opacity, transform;
}

/* Only the active card's text is visible; the timeline fades it. A card
   that has not played yet stays blank. */
.lm-recap-step:not(.is-active) .lm-recap-step-text { opacity: 0; }

.lm-recap-step .lm-recap-num {
  font-size: clamp(52px, 8vw, 96px);
  padding: 0 0 4px;
  /* Tabular figures: every digit occupies the same advance width, so a 3
     turning into an 8 mid count does not nudge its neighbours. Without this
     the whole number shimmers as it climbs. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  /* The counting number is centred, and it grows from one character to as
     many as six ("12,480"), so each edge was travelling about 110px over the
     count while the digits themselves changed. The element is sized to its
     FINAL value up front (see reserveNumberWidth in recap-line.js) and the
     text is centred inside that fixed box, so only the glyphs change. */
  display: inline-block;
  text-align: center;
  white-space: nowrap;
}

.lm-recap-step .lm-recap-cap { max-width: 30ch; }

/* Per-step accents stay on the number only; the line is one colour. */
.lm-recap-step[data-accent='cobalt'] { --accent: var(--lm-node-cobalt); }
.lm-recap-step[data-accent='green']  { --accent: var(--lm-node-green); }
.lm-recap-step[data-accent='honey']  { --accent: var(--lm-node-honey); }
.lm-recap-step[data-accent='iris']   { --accent: var(--lm-node-iris); }
.lm-recap-step .lm-recap-kicker {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 34%, transparent);
}
.lm-recap-step .lm-recap-num {
  color: transparent;
  background-image: linear-gradient(168deg, var(--lm-hero-text) 18%, var(--accent) 128%);
  -webkit-background-clip: text;
  background-clip: text;
  /* The gradient is painted over the element's box, and that box is now the
     reserved final width rather than the current text. Pinning the size and
     origin to the border box keeps the ramp identical from the first frame of
     the count to the last, instead of resolving against a box that would
     otherwise change with the content. */
  background-size: 100% 100%;
  background-origin: border-box;
  background-repeat: no-repeat;
}

.lm-recap-line {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  overflow: visible;
  pointer-events: none;
  color: var(--lm-line, #b75c4e);
  opacity: 0;
  shape-rendering: geometricPrecision;
  contain: paint;
}

.lm-recap-line.is-ready { opacity: 1; transition: opacity 150ms ease; }
.lm-recap-line.is-fading { opacity: 0; transition: opacity 400ms ease; }
.lm-recap-line path { transition: opacity 200ms ease; }

/* The line colour: the brand accent, one value along the whole length. */
.lm-recap { --lm-line: #b75c4e; }
body.theme-dark .lm-recap { --lm-line: #d2755e; }

/* From the first step on: the avatar folds away (the icon takes its place)
   and nothing moves but the line, the label and the number. */
.lm-recap-avatar-wrap {
  transition: opacity 240ms ease, height 320ms cubic-bezier(0.22, 1, 0.36, 1), width 320ms cubic-bezier(0.22, 1, 0.36, 1), padding 320ms ease;
}
.lm-recap.lm-recap-steps-active .lm-recap-avatar-wrap {
  height: 0;
  width: 0;
  padding: 0;
  opacity: 0;
  overflow: hidden;
  box-shadow: none;
  pointer-events: none;
}
.lm-recap.lm-recap-steps-active .lm-recap-avatar-wrap::before,
.lm-recap.lm-recap-steps-active .lm-recap-avatar-orbit { display: none; }
.lm-recap.lm-recap-steps-active .lm-recap-bg,
.lm-recap.lm-recap-steps-active .lm-recap-veil { animation-play-state: paused; }

/* Narrower screens: a shorter step and one ghost, so the ladder never
   crowds the number. Below the phone breakpoint the strip stops moving
   altogether and the icon crossfades in place (see .is-static). */
@media (max-width: 1100px) {
  .lm-recap-strip { --recap-slot: 250px; }
  /* One ghost only on narrower screens: recap-line.js reads this. */
}

@media (max-width: 720px) {
  .lm-recap-slot { width: 140px; height: 140px; }
  .lm-recap-strip { --recap-slot: 170px; height: 140px; top: 4px; }
  .lm-recap-strip .lm-recap-slot { width: var(--recap-slot); height: 140px; flex-basis: var(--recap-slot); }
  .lm-recap-icon-box { width: 140px; height: 140px; }
  /* Same rule as the desktop step: reserve the icon band the strip overlays. */
  .lm-recap-step { min-height: 400px; padding: 144px 16px 16px; }
  .lm-recap-step-text { margin-top: 32px; }
  .lm-recap-step .lm-recap-cap { max-width: 24ch; }
}

/* Static strip: one icon at a time, in place, no ghosts and no travel.
   Used below the mobile breakpoint and for prefers-reduced-motion. The
   track is not translated in this mode, so every slot stacks on the
   viewport's centre and only the active one is visible. */
.lm-recap-strip.is-static .lm-recap-strip-track {
  transform: none !important;
  display: block;
  width: 100%;
  height: 100%;
}
.lm-recap-strip.is-static .lm-recap-slot {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 180px;
  height: 180px;
  flex: none;
  transform: none !important;
  transition: opacity 200ms ease;
}
.lm-recap-strip.is-static .lm-recap-icon-box { margin: 0 auto; }
.lm-recap-strip.is-static .lm-recap-slot[data-ghost='0'] { opacity: 1; }
.lm-recap-strip.is-static .lm-recap-slot:not([data-ghost='0']) { opacity: 0; }

@media (max-width: 560px) {
  .lm-recap-icon-box { width: 140px; height: 140px; }
}

/* Reduced motion bypasses the timeline entirely (static mode), so a plain
   CSS crossfade is the right mechanism there and cannot compete with it. */
@media (prefers-reduced-motion: reduce) {
  .lm-recap-strip-track { transition: none; }
  .lm-recap-line path { transition: opacity 200ms ease; }
}


/* ---- End screen: one way to the dashboard, plus replay ------------ */

.lm-recap.is-summary .lm-recap-controls { display: none; }

.lm-recap-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 4px;
}

.lm-recap-actions .lm-recap-enter { margin-top: 0; }

.lm-recap-replay {
  appearance: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--lm-hero-mute);
  background: var(--lm-hero-glass);
  border: 1px solid var(--lm-hero-rule);
  border-radius: 12px;
  padding: 13px 18px;
  backdrop-filter: blur(6px);
  transition: color 0.15s, border-color 0.15s, transform 0.05s;
}

.lm-recap-replay:hover {
  color: var(--lm-hero-text);
  border-color: rgba(var(--lm-ink-rgb), 0.34);
}

.lm-recap-replay:active { transform: translateY(1px); }
.lm-recap-replay svg { transition: transform 0.3s; }
.lm-recap-replay:hover svg { transform: rotate(-40deg); }

/* Countdown to the auto-redirect, inside the dashboard button. */
.lm-recap-countdown {
  display: inline-grid;
  place-items: center;
  min-width: 24px;
  height: 24px;
  padding: 0 7px;
  margin-left: 2px;
  border-radius: 999px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--lm-enter-ink);
  background: rgba(255, 255, 255, 0.14);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
}
body:not(.theme-dark) .lm-recap-countdown {
  background: rgba(255, 255, 255, 0.14);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
}
body.theme-dark .lm-recap-countdown {
  background: rgba(20, 23, 28, 0.08);
  box-shadow: inset 0 0 0 1px rgba(20, 23, 28, 0.14);
}
.lm-recap-countdown[hidden] { display: none; }
