/* ============================================================
   THE THREAD — the animated version
   ------------------------------------------------------------
   The one idea the whole product rests on: you type it once and
   a file carries it forward. That is currently a paragraph, and
   a paragraph is the wrong medium for it. This is the same claim
   played out — a file leaves one tool, travels the thread, and
   arrives in the next one already full.

   Global on purpose. Write the markup below anywhere and
   thread-anim.js animates it.

   Two colour hooks, so a page can decide what colour MEANS:
     --thr-in    the tool you type into
     --thr-out   the tool that inherits
   Set them to the same hue and the thread reads as one PLACE
   (tab colour wins). Set them coral/jade and colour reads as a
   ROLE (in/out grammar wins). That is the whole T.4 / T.5 fork.
   ============================================================ */

:root{
  /* --thr-node  the two TOOL boxes. They are furniture, so ink.
     --thr-in / --thr-out  the thread itself: the line, the arrowhead, the
       travelling file and the value each tool is holding. This is the data
       moving, which is the thing worth colouring.
     --thr-file  the saved file's own box. The one object the whole page is
       about, so it is the one outline that is allowed to carry colour. */
  --thr-node:var(--ink);
  --thr-in:var(--coral);
  --thr-out:var(--jade);
  --thr-file:var(--ink);

  /* live-tunable — every one of these is read by thread-anim.js */
  --thr-travel:1.15s;    /* how long the file takes to cross a segment */
  --thr-hold:.62s;       /* dwell at each node before moving on        */
  --thr-rest:2.6s;       /* pause on the finished state before repeat  */
  --thr-lift:.5s;        /* how long a node takes to come alive        */
  --thr-dim:.24;         /* opacity of a node that has not woken yet   */
  --thr-token:32px;      /* the travelling file                        */
  --thr-seg-len:170px;   /* length of a run of thread                  */
  --thr-ease:cubic-bezier(.4,0,.15,1);
}

/* ---- the frame ------------------------------------------- */
.thr{border:1px solid var(--ink);padding:30px 30px 32px;position:relative}
.thr-head h3{font-size:clamp(24px,2.8vw,34px);line-height:1.1;margin:12px 0 12px}
.thr-head p{font-size:16.5px;color:var(--soft);max-width:48em;margin:0 0 34px}

/* ---- the rail -------------------------------------------- */
.thr-rail{display:flex;align-items:stretch;gap:0}

/* vertically centred so the row reads as one object, rather than three
   boxes with a pool of white under the short ones */
.thr-node{flex:1 1 0;min-width:0;border:1.5px solid var(--rule);padding:20px 18px;
  background:var(--paper);opacity:var(--thr-dim);
  display:flex;flex-direction:column;justify-content:center;
  transition:opacity var(--thr-lift) var(--thr-ease),
             border-color var(--thr-lift) var(--thr-ease),
             transform var(--thr-lift) var(--thr-ease)}
.thr-node .k{font-family:var(--serif);font-size:19px;line-height:1.15;margin-bottom:10px}
.thr-node .v{font-family:var(--sans);font-size:9px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--muted);line-height:1.6;
  opacity:0;transform:translateY(6px);
  transition:opacity var(--thr-lift) var(--thr-ease),
             transform var(--thr-lift) var(--thr-ease),
             color var(--thr-lift) var(--thr-ease)}

/* the saved file: narrower, ink, and the only thing here that is not a tool */
.thr-file{flex:0 0 196px;text-align:center;
  display:flex;flex-direction:column;align-items:center;justify-content:center}
.thr-file .k{font-family:var(--sans);font-size:9px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--muted);margin:0 0 10px}
.thr-file .v{font-family:var(--serif);font-size:19px;letter-spacing:0;text-transform:none;
  color:var(--ink);opacity:0;transform:none}

/* a document mark — deliberately the SAME object as the travelling token,
   so what lands in the node is visibly what crossed the thread */
.filemark{width:30px;height:37px;margin:0 auto 14px;position:relative;
  border:1.5px solid var(--rule);
  transition:border-color var(--thr-lift) var(--thr-ease)}
.filemark::before{content:"";position:absolute;left:5px;right:10px;top:9px;height:1px;
  background:var(--rule);box-shadow:0 6px 0 var(--rule),0 12px 0 var(--rule);
  transition:background var(--thr-lift) var(--thr-ease),box-shadow var(--thr-lift) var(--thr-ease)}
.filemark::after{content:"";position:absolute;right:-1.5px;top:-1.5px;
  border-left:10px solid var(--paper);border-bottom:10px solid transparent}

/* ---- a run of thread, with a file travelling it ----------- */
.thr-seg{flex:0 1 var(--thr-seg-len);min-width:86px;
  display:flex;flex-direction:column;justify-content:center;align-items:center}
.thr-seg .lab{font-family:var(--sans);font-size:8.5px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--muted);text-align:center;padding:0 8px 14px;line-height:1.55;
  opacity:var(--thr-dim);
  transition:opacity var(--thr-lift) var(--thr-ease),color var(--thr-lift) var(--thr-ease)}
.thr-seg .ln{position:relative;width:100%;height:1.5px;background:var(--rule)}
.thr-seg .ln::after{content:"";position:absolute;inset:0;background:var(--thr-c,var(--ink));
  transform:scaleX(0);transform-origin:0 50%;
  transition:transform var(--thr-travel) var(--thr-ease)}

/* the arrowhead lands only once the line has arrived */
.thr-seg .tip{position:absolute;right:-1px;top:50%;margin-top:-5px;
  border-left:9px solid var(--thr-c,var(--ink));
  border-top:5px solid transparent;border-bottom:5px solid transparent;
  opacity:0;transition:opacity .3s ease}

/* the token: a small page that rides the line.
   Travel is animated on `left`/`top` rather than transform, because the
   distance is the LINE's length and a transform percentage would be the
   token's own. */
.thr-seg .tok{position:absolute;left:0;top:50%;
  width:var(--thr-token);height:calc(var(--thr-token) * 1.24);
  margin-top:calc(var(--thr-token) * -.62);
  border:1.5px solid var(--thr-c,var(--ink));background:var(--paper);
  opacity:0;
  transition:left var(--thr-travel) var(--thr-ease),
             top   var(--thr-travel) var(--thr-ease),
             opacity .22s ease}
/* three ruled lines, so it reads as a document and not a chip */
.thr-seg .tok::before{content:"";position:absolute;left:5px;right:10px;top:9px;height:1px;
  background:var(--thr-c,var(--ink));opacity:.55;
  box-shadow:0 7px 0 var(--thr-c,var(--ink)),0 14px 0 var(--thr-c,var(--ink))}
.thr-seg .tok::after{content:"";position:absolute;right:-1.5px;top:-1.5px;
  border-left:9px solid var(--paper);border-bottom:9px solid transparent}

/* ============================================================
   THE TIMELINE
   data-step is written by thread-anim.js, so the JS is only a
   clock and everything below is "what is true at step N".
     1  the first tool wakes, holding what you typed
     2  Save My Progress fires, the file crosses
     3  the file lands: the Thread exists
     4  Import fires, the file crosses again
     5  the second tool wakes, already full
   ============================================================ */

/* --- 1 · the tool you type into --------------------------- */
.thr:not([data-step="0"]) .thr-node:first-child{opacity:1;border-color:var(--thr-node)}
.thr:not([data-step="0"]) .thr-node:first-child .v{opacity:1;transform:none;color:var(--thr-in)}

/* --- 2 · save, and cross ---------------------------------- */
.thr[data-step="2"] .seg1,.thr[data-step="3"] .seg1,
.thr[data-step="4"] .seg1,.thr[data-step="5"] .seg1{--thr-c:var(--thr-in)}
.thr[data-step="2"] .seg1 .lab,.thr[data-step="3"] .seg1 .lab,
.thr[data-step="4"] .seg1 .lab,.thr[data-step="5"] .seg1 .lab{opacity:1;color:var(--thr-in)}
.thr[data-step="2"] .seg1 .ln::after,.thr[data-step="3"] .seg1 .ln::after,
.thr[data-step="4"] .seg1 .ln::after,.thr[data-step="5"] .seg1 .ln::after{transform:scaleX(1)}
.thr[data-step="2"] .seg1 .tok{opacity:1;left:calc(100% - var(--thr-token))}
.thr[data-step="3"] .seg1 .tip,.thr[data-step="4"] .seg1 .tip,
.thr[data-step="5"] .seg1 .tip{opacity:1}

/* --- 3 · the file exists ---------------------------------- */
.thr[data-step="3"] .thr-file,.thr[data-step="4"] .thr-file,
.thr[data-step="5"] .thr-file{opacity:1;border-color:var(--thr-file)}
.thr[data-step="3"] .thr-file .v,.thr[data-step="4"] .thr-file .v,
.thr[data-step="5"] .thr-file .v{opacity:1}
.thr[data-step="3"] .filemark,.thr[data-step="4"] .filemark,
.thr[data-step="5"] .filemark{border-color:var(--thr-file)}
.thr[data-step="3"] .filemark::before,.thr[data-step="4"] .filemark::before,
.thr[data-step="5"] .filemark::before{background:var(--thr-file);
  box-shadow:0 6px 0 var(--thr-file),0 12px 0 var(--thr-file)}
.thr[data-step="3"] .thr-file{animation:thr-land .52s var(--thr-ease)}
@keyframes thr-land{0%{transform:scale(.95)}58%{transform:scale(1.025)}100%{transform:scale(1)}}

/* --- 4 · import, and cross again -------------------------- */
.thr[data-step="4"] .seg2,.thr[data-step="5"] .seg2{--thr-c:var(--thr-out)}
.thr[data-step="4"] .seg2 .lab,.thr[data-step="5"] .seg2 .lab{opacity:1;color:var(--thr-out)}
.thr[data-step="4"] .seg2 .ln::after,.thr[data-step="5"] .seg2 .ln::after{transform:scaleX(1)}
.thr[data-step="4"] .seg2 .tok{opacity:1;left:calc(100% - var(--thr-token))}
.thr[data-step="5"] .seg2 .tip{opacity:1}

/* --- 5 · the tool that inherits --------------------------- */
.thr[data-step="5"] .thr-node:last-of-type{opacity:1;border-color:var(--thr-node)}
.thr[data-step="5"] .thr-node:last-of-type .v{opacity:1;transform:none;color:var(--thr-out)}

/* the line that only earns its meaning once it has played through */
.thr-foot{font-family:var(--sans);font-size:9px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);margin-top:26px;text-align:center;
  opacity:0;transition:opacity .5s ease}
.thr[data-step="5"] .thr-foot{opacity:1}

/* a replay, for anyone who looked away */
.thr-replay{position:absolute;right:12px;top:12px;background:none;border:0;cursor:pointer;
  font-family:var(--sans);font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);padding:6px 8px}
.thr-replay:hover{color:var(--ink)}

/* ---- stacked ---------------------------------------------- */
@media(max-width:820px){
  .thr{padding:24px 20px 26px}
  .thr-rail{flex-direction:column}
  .thr-file{flex:0 0 auto}
  .thr-seg{flex:0 0 116px;width:100%;flex-direction:row;align-items:stretch;
    justify-content:flex-start;gap:16px}
  .thr-seg .ln{order:-1;flex:0 0 1.5px;width:1.5px;height:100%;margin-left:32px}
  .thr-seg .ln::after{transform:scaleY(0);transform-origin:50% 0}
  .thr-seg .lab{padding:0;text-align:left;max-width:13em;align-self:center}
  .thr[data-step="2"] .seg1 .ln::after,.thr[data-step="3"] .seg1 .ln::after,
  .thr[data-step="4"] .seg1 .ln::after,.thr[data-step="5"] .seg1 .ln::after,
  .thr[data-step="4"] .seg2 .ln::after,.thr[data-step="5"] .seg2 .ln::after{transform:scaleY(1)}
  .thr-seg .tip{right:auto;left:50%;top:auto;bottom:-1px;margin:0 0 0 -5px;
    border-left:5px solid transparent;border-right:5px solid transparent;
    border-top:9px solid var(--thr-c,var(--ink));border-bottom:0}
  .thr-seg .tok{left:50%;top:0;
    margin:0 0 0 calc(var(--thr-token) * -.5)}
  .thr[data-step="2"] .seg1 .tok,
  .thr[data-step="4"] .seg2 .tok{left:50%;top:calc(100% - var(--thr-token) * 1.24)}
}

/* ---- respect the setting: show the finished state, do not play --- */
@media (prefers-reduced-motion:reduce){
  .thr *{transition:none !important;animation:none !important}
}


/* ---- optional sticky runway -------------------------------
   Wrapping a scrub block in .thr-runway pins it while the sequence plays, so
   the whole progression is watched with the block centred instead of finishing
   as it leaves the screen. This is position:sticky, NOT scroll-jacking: the
   page scrolls normally throughout and she can leave at any point.

   Dropped on narrow screens, where the rail stacks vertically and the block is
   too tall to centre in a viewport. There the JS falls back to range-based
   scrubbing tuned to finish while the block is still in frame. */
.thr-runway{position:relative;height:170vh}
/* The stage HUGS the block. Giving it height:100vh centred the block inside a
   full-screen box, which put 281px of dead white between the Thread and the
   sections either side of it. The pin offset is set in JS instead, so the block
   still lands centred while stuck without carrying that space in normal flow. */
.thr-stage{position:sticky;top:0}
.thr-stage > .thr{width:100%}
@media(max-width:820px){
  .thr-runway{height:auto}
  .thr-stage{position:static;height:auto;display:block}
}
@media (prefers-reduced-motion:reduce){
  .thr-runway{height:auto}
  .thr-stage{position:static;height:auto;display:block}
}

/* ============================================================
   SCRUB MODE  ·  .thr[data-mode="scrub"]
   ------------------------------------------------------------
   Stepping through data-step and letting CSS transitions cover
   the gaps always reads as chop: the wheel moves continuously
   and the animation moves in five jumps.

   Here every property is a direct function of scroll instead.
   thread-anim.js writes five sub-progress values and nothing
   else, and transitions are switched OFF so nothing lags behind
   the wheel. Scrolling up runs it backwards for free, because
   there is no timeline to rewind — only a number going down.

     --a   the first tool waking
     --s1  the file crossing to the saved plan
     --f   the saved plan becoming real
     --s2  the file crossing to the second tool
     --b   the second tool waking
   ============================================================ */
/* The universal selector does NOT match pseudo-elements, and the line, the
   arrowhead, the file mark and the token's rules are all ::before / ::after.
   Without the second rule those kept their 1.15s transition and eased toward
   the wheel instead of tracking it, which is precisely the lag this mode
   exists to remove. */
.thr[data-mode="scrub"] *{transition:none !important;animation:none !important}
.thr[data-mode="scrub"] *::before,
.thr[data-mode="scrub"] *::after,
.thr[data-mode="scrub"]::before,
.thr[data-mode="scrub"]::after{transition:none !important;animation:none !important}

/* --- the tool you type into --- */
.thr[data-mode="scrub"] .thr-node:first-child{
  opacity:calc(var(--thr-dim) + (1 - var(--thr-dim)) * var(--a,0));
  border-color:color-mix(in srgb, var(--thr-node) calc(var(--a,0) * 100%), var(--rule))}
.thr[data-mode="scrub"] .thr-node:first-child .v{
  opacity:var(--a,0);transform:translateY(calc((1 - var(--a,0)) * 6px));color:var(--thr-in)}

/* --- run one --- */
.thr[data-mode="scrub"] .seg1{--thr-c:var(--thr-in)}
.thr[data-mode="scrub"] .seg1 .lab{
  opacity:calc(var(--thr-dim) + (1 - var(--thr-dim)) * clamp(0, var(--s1,0) * 4, 1));
  color:color-mix(in srgb, var(--thr-in) calc(clamp(0, var(--s1,0) * 4, 1) * 100%), var(--muted))}
.thr[data-mode="scrub"] .seg1 .ln::after{transform:scaleX(var(--s1,0))}
.thr[data-mode="scrub"] .seg1 .tok{
  left:calc(var(--s1,0) * (100% - var(--thr-token)));
  opacity:calc(clamp(0, var(--s1,0) * 9, 1) * clamp(0, (1 - var(--s1,0)) * 9, 1))}
.thr[data-mode="scrub"] .seg1 .tip{opacity:clamp(0, (var(--s1,0) - .82) * 6, 1)}

/* --- the saved file --- */
.thr[data-mode="scrub"] .thr-file{
  opacity:calc(var(--thr-dim) + (1 - var(--thr-dim)) * var(--f,0));
  border-color:color-mix(in srgb, var(--thr-file) calc(var(--f,0) * 100%), var(--rule));
  transform:scale(calc(.95 + .05 * var(--f,0)))}
.thr[data-mode="scrub"] .thr-file .v{opacity:var(--f,0)}
.thr[data-mode="scrub"] .filemark{
  border-color:color-mix(in srgb, var(--thr-file) calc(var(--f,0) * 100%), var(--rule))}
.thr[data-mode="scrub"] .filemark::before{
  background:color-mix(in srgb, var(--thr-file) calc(var(--f,0) * 100%), var(--rule));
  box-shadow:0 6px 0 color-mix(in srgb, var(--thr-file) calc(var(--f,0) * 100%), var(--rule)),
             0 12px 0 color-mix(in srgb, var(--thr-file) calc(var(--f,0) * 100%), var(--rule))}

/* --- run two --- */
.thr[data-mode="scrub"] .seg2{--thr-c:var(--thr-out)}
.thr[data-mode="scrub"] .seg2 .lab{
  opacity:calc(var(--thr-dim) + (1 - var(--thr-dim)) * clamp(0, var(--s2,0) * 4, 1));
  color:color-mix(in srgb, var(--thr-out) calc(clamp(0, var(--s2,0) * 4, 1) * 100%), var(--muted))}
.thr[data-mode="scrub"] .seg2 .ln::after{transform:scaleX(var(--s2,0))}
.thr[data-mode="scrub"] .seg2 .tok{
  left:calc(var(--s2,0) * (100% - var(--thr-token)));
  opacity:calc(clamp(0, var(--s2,0) * 9, 1) * clamp(0, (1 - var(--s2,0)) * 9, 1))}
.thr[data-mode="scrub"] .seg2 .tip{opacity:clamp(0, (var(--s2,0) - .82) * 6, 1)}

/* --- the tool that inherits --- */
.thr[data-mode="scrub"] .thr-node:last-of-type{
  opacity:calc(var(--thr-dim) + (1 - var(--thr-dim)) * var(--b,0));
  border-color:color-mix(in srgb, var(--thr-node) calc(var(--b,0) * 100%), var(--rule))}
.thr[data-mode="scrub"] .thr-node:last-of-type .v{
  opacity:var(--b,0);transform:translateY(calc((1 - var(--b,0)) * 6px));color:var(--thr-out)}
.thr[data-mode="scrub"] .thr-foot{opacity:var(--b,0)}

/* stacked, the runs are vertical, so the file travels down instead of across */
@media(max-width:820px){
  .thr[data-mode="scrub"] .seg1 .ln::after,
  .thr[data-mode="scrub"] .seg2 .ln::after{transform:scaleY(var(--s1,0))}
  .thr[data-mode="scrub"] .seg2 .ln::after{transform:scaleY(var(--s2,0))}
  .thr[data-mode="scrub"] .seg1 .tok{left:50%;top:calc(var(--s1,0) * (100% - var(--thr-token) * 1.24))}
  .thr[data-mode="scrub"] .seg2 .tok{left:50%;top:calc(var(--s2,0) * (100% - var(--thr-token) * 1.24))}
}
