/* Spring Line Visual System — shared kit (theme + spring-vow plugin).
   Tokens are literal inks on :root so every consumer resolves them identically,
   regardless of whether it scopes its own vars on :root (sv-) or body (--c-). */
:root {
  --vs-cream: #F2EAD9;
  --vs-ink:   #191713;
  --vs-spring:#19B584;
  --vs-rust:  #C8452E;
  --vs-gold:  #E3A92B;
  --vs-navy:  #1C3A4B;
  --vs-duo-shadow:   var(--vs-navy);
  --vs-duo-highlight:var(--vs-cream);
  --vs-grain-opacity: .06;
  --vs-ease: cubic-bezier(.22,.61,.36,1);
  --vs-dur: .7s;
}

/* --- Duotone image treatment: full-colour base + two ink blend layers.
   Motion is opacity-only (transform/opacity rule): the navy/cream ink layers
   fade to 0 to "wake" the photo to full colour. No animated filter. --- */
.vs-duo { position: relative; overflow: hidden; }
.vs-duo img { display: block; width: 100%; height: auto; }
.vs-duo::before,
.vs-duo::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  transition: opacity var(--vs-dur) var(--vs-ease);
}
.vs-duo::before { background: var(--vs-duo-shadow);    mix-blend-mode: multiply; opacity: 1; }
.vs-duo::after  { background: var(--vs-duo-highlight); mix-blend-mode: screen;   opacity: .55; }
/* "Wake up" to full colour on in-view (.rv.in from scroll-reveal.js) or hover */
.vs-duo.rv.in::before, .vs-duo:hover::before,
.vs-duo.rv.in::after,  .vs-duo:hover::after { opacity: 0; }

/* --- Paper grain overlay: fixed, non-interactive, cheap SVG noise --- */
.vs-grain::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  opacity: var(--vs-grain-opacity); mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --- Transit route-line: revealed by motion.js toggling .vs-drawn.
   Strict transform-only: the horizontal divider line scales in from the left
   (scaleX 0 -> 1) instead of animating stroke-dashoffset. stroke-width is the
   vertical thickness, unaffected by scaleX, so the line grows in length only.
   (A vertical page-threading route is a later-phase --vertical modifier.) --- */
.vs-route__line {
  fill: none; stroke: var(--vs-navy); stroke-width: 3; stroke-linecap: round;
  transform-box: fill-box; transform-origin: left center;
  transform: scaleX(0);
  transition: transform 1.1s var(--vs-ease);
}
.vs-route__line.vs-drawn { transform: scaleX(1); }
.vs-route__dot {
  fill: var(--vs-rust);
  transform-box: fill-box; transform-origin: center;
  transform: scale(0); opacity: 0;
  transition: transform .4s var(--vs-ease), opacity .4s var(--vs-ease);
}
.vs-route__dot.vs-drawn { transform: scale(1); opacity: 1; }

/* --- Split-flap cell base --- */
.vs-flap { font-family: var(--f-mono, monospace); letter-spacing: .04em; }

/* --- Home stat board layout --- */
.spring-statboard { display: flex; flex-wrap: wrap; gap: .75rem 1.5rem; margin-top: 1.5rem; }
.spring-statboard .vs-flap {
  display: inline-block; padding: .35rem .7rem; background: var(--vs-ink); color: var(--vs-cream);
  border-radius: 3px; font-size: .8rem; text-transform: uppercase; min-width: 9ch; text-align: center;
}

/* --- Guardrail: reduced motion => everything lands in its final static state --- */
@media (prefers-reduced-motion: reduce) {
  .vs-duo::before, .vs-duo::after { opacity: 0; }
  .vs-route__line { transition: none; transform: scaleX(1); }
  .vs-route__dot  { transition: none; transform: scale(1); opacity: 1; }
}
