/* ============================================================================
   exotic — css/motion-parts.css · motion for the parts a product is made of
   ========================================================================== */

/* ============================================================================
   1 · HOLD — slow to press, fast to release
   ========================================================================== */
.pt-hold{position:relative;overflow:hidden;isolation:isolate;
  font-family:var(--font-sans);font-size:13px;font-weight:600;min-height:40px;
  padding:10px 20px;border-radius:var(--rad-pill,999px);cursor:pointer;
  border:1px solid color-mix(in oklab,var(--down) 42%,var(--hairline));
  background:var(--surface);color:var(--down);white-space:nowrap;
  transition:transform var(--t-press,.07s) var(--ease-out),color var(--t-med,.24s) var(--ease-out)}
.pt-hold:active{transform:scale(.98)}
.pt-hold:focus-visible{outline:none;box-shadow:var(--focus-ring)}
/* the fill is BEHIND the label and clipped from the left; the label flips to
   the on-fill colour only once the fill has passed it */
.pt-hold .pt-fill{position:absolute;inset:0;z-index:-1;border-radius:inherit;
  background:color-mix(in oklab,var(--down) 88%,black);
  clip-path:inset(0 100% 0 0)}
.pt-hold.holding{color:oklch(0.99 0.002 262)}
.pt-hold.held{color:oklch(0.99 0.002 262);
  border-color:color-mix(in oklab,var(--down) 88%,black);pointer-events:none}
.pt-hold .pt-hint{font-family:var(--font-num);font-size:9.5px;opacity:.7;margin-left:7px;
  text-transform:uppercase;letter-spacing:var(--track-caps,.07em)}

/* ============================================================================
   2 · MORPH — one surface, three states
   ========================================================================== */
/* This one animates WIDTH on purpose, against the usual rule. The whole point
   of a morph surface is that it is the SAME object at a new size; scaleX would
   distort the pill's radius and stretch the type, which is the exact illusion
   this is meant to avoid. The cost is bounded to one inline-grid element with
   `contain`, and nothing depends on its width for layout. */
.pt-morph{position:relative;display:inline-grid;place-items:center;overflow:hidden;
  contain:layout style;
  min-height:40px;padding:10px 22px;border-radius:var(--rad-pill,999px);border:0;
  cursor:pointer;font-family:var(--font-sans);font-size:13px;font-weight:600;
  color:oklch(0.99 0.002 262);background:color-mix(in oklab,var(--up) 88%,black);
  width:calc(var(--pt-w,80px) + 44px);
  transition:width var(--t-slow,.3s) var(--ease-emph,cubic-bezier(.16,1,.3,1)),
             background var(--t-slow,.3s) var(--ease-out),
             transform var(--t-press,.07s) var(--ease-out)}
body.theme-dark .pt-morph{background:color-mix(in oklab,var(--up) 72%,black)}
.pt-morph:active{transform:scale(.98)}
.pt-morph:focus-visible{outline:none;box-shadow:var(--focus-ring)}
.pt-morph[data-state="busy"]{background:var(--ink)}
body.theme-dark .pt-morph[data-state="busy"]{background:var(--surface-2)}
.pt-morph[data-state="done"]{background:color-mix(in oklab,var(--up) 88%,black)}
.pt-morph .pt-lb{grid-area:1/1;white-space:nowrap;
  transition:opacity 150ms var(--ease-out),filter 150ms var(--ease-out),
             transform 150ms var(--ease-out)}
/* the 2px blur is what stops the eye reading two overlapping words instead of
   one word changing */
.pt-morph .pt-lb.out{opacity:0;filter:blur(2px);transform:translateY(-2px)}
.pt-morph .pt-probe{position:absolute;visibility:hidden;pointer-events:none;white-space:nowrap}
/* the pending beat is kit.css's tick-dot — one pending vocabulary, still */
.pt-morph[data-state="busy"] .pt-lb::after{content:"";display:inline-block;width:5px;height:5px;
  border-radius:50%;background:currentColor;margin-left:8px;vertical-align:1px;
  animation:kitTick 1s var(--ease-inout,ease-in-out) infinite}

/* ============================================================================
   3 · MINIMAP — the window is the control
   ========================================================================== */
.pt-mm{display:flex;flex-direction:column;gap:11px;width:100%}
.pt-mm-main{position:relative;height:clamp(120px,16vw,164px)}
.pt-mm-path{position:absolute;inset:0;width:100%;height:100%;display:block}
.pt-mm-read{position:absolute;left:0;top:0;display:flex;align-items:baseline;gap:9px;
  font-family:var(--font-num);font-variant-numeric:tabular-nums;pointer-events:none}
.pt-mm-read b{font-size:20px;font-weight:600;letter-spacing:-0.025em;color:var(--ink)}
.pt-mm-read i{font-style:normal;font-size:12px;font-weight:600;color:var(--muted)}
.pt-mm-read i.up{color:var(--up)} .pt-mm-read i.down{color:var(--down)}
.pt-mm-strip{position:relative;height:42px;border-radius:var(--rad-1,8px);
  background:var(--bg-sunken);overflow:hidden}
.pt-mm-strip svg{position:absolute;inset:0;width:100%;height:100%;display:block}
.pt-mm-strip .all{stroke:var(--hairline);stroke-width:1.4;vector-effect:non-scaling-stroke}
.pt-mm-win{position:absolute;top:0;bottom:0;cursor:grab;touch-action:none;
  border-radius:var(--rad-1,8px);background:color-mix(in oklab,var(--g3) 15%,transparent);
  border:1px solid color-mix(in oklab,var(--g3) 60%,transparent);
  transition:background var(--t-fast,.15s) var(--ease-out)}
.pt-mm-win:hover{background:color-mix(in oklab,var(--g3) 22%,transparent)}
.pt-mm-win:focus-visible{outline:none;box-shadow:var(--focus-ring)}
/* the two grab bars: the affordance IS the handle, not a tooltip */
.pt-mm-win::before,.pt-mm-win::after{content:"";position:absolute;top:50%;width:2px;height:14px;
  margin-top:-7px;border-radius:1px;background:color-mix(in oklab,var(--g3) 75%,transparent)}
.pt-mm-win::before{left:5px} .pt-mm-win::after{right:5px}
.pt-mm.grabbing .pt-mm-win{cursor:grabbing;background:color-mix(in oklab,var(--g3) 26%,transparent)}

/* ============================================================================
   4 · RANGE — the path travels between series
   ========================================================================== */
.pt-rg{display:flex;flex-direction:column;gap:11px;width:100%}
.pt-rg-head{display:flex;align-items:center;gap:12px}
.pt-rg-head .v{font-family:var(--font-num);font-size:10px;text-transform:uppercase;
  letter-spacing:var(--track-caps,.07em);color:var(--muted)}
.pt-rg-seg{display:inline-flex;margin-left:auto;background:var(--surface-2);
  border:1px solid var(--hairline-2);border-radius:var(--rad-pill,999px);padding:2px}
.pt-rg-seg button{border:0;background:none;font-family:var(--font-num);font-size:11px;
  color:var(--muted);padding:4px 11px;border-radius:var(--rad-pill,999px);cursor:pointer;
  min-height:26px;transition:color var(--t-fast,.15s) var(--ease-out)}
.pt-rg-seg button.on{background:var(--surface);color:var(--ink);box-shadow:var(--crisp)}
.pt-rg-seg button:active{transform:scale(.96);transition-duration:var(--t-press,.07s)}
.pt-rg-svg{width:100%;height:clamp(110px,15vw,150px);display:block}

/* ============================================================================
   5 · STREAM — words, not characters
   ========================================================================== */
.pt-stream{font-size:14px;line-height:1.62;color:var(--ink-2);max-width:58ch;
  margin:0;text-wrap:pretty}
.pt-stream .w span{display:inline;opacity:0;filter:blur(4px);
  transition:opacity 260ms var(--ease-out),filter 260ms var(--ease-out)}
.pt-stream .w span.in{opacity:1;filter:blur(0)}
.pt-stream .caret{display:inline-block;width:2px;height:1em;vertical-align:-2px;
  margin-left:2px;background:var(--g4);border-radius:1px}
/* the caret blinks ONLY once the stream stops — a blinking caret during output
   is two things claiming to be the live edge */
.pt-stream.done .caret{animation:ptCaret 1.1s steps(1) infinite}
@keyframes ptCaret{0%,55%{opacity:1}56%,100%{opacity:0}}

/* ============================================================================
   6 · STRIP — the fade is the only thing saying there's more
   ========================================================================== */
.pt-strip{position:relative;width:100%}
.pt-strip-rail{display:flex;gap:8px;overflow-x:auto;scroll-snap-type:x proximity;
  padding:2px 1px 10px;scrollbar-width:none}
.pt-strip-rail::-webkit-scrollbar{display:none}
.pt-chip{flex:0 0 auto;scroll-snap-align:start;display:inline-flex;align-items:baseline;gap:8px;
  padding:7px 13px;border-radius:var(--rad-pill,999px);border:1px solid var(--hairline-2);
  background:var(--surface);font-family:var(--font-num);font-size:11.5px;
  transition:border-color var(--t-fast,.15s) var(--ease-out),
             transform var(--t-press,.07s) var(--ease-out)}
.pt-chip:hover{border-color:var(--hairline)}
.pt-chip:active{transform:scale(.97)}
.pt-chip b{font-weight:600;color:var(--ink)}
.pt-chip i{font-style:normal;font-variant-numeric:tabular-nums;color:var(--muted)}
.pt-chip i.up{color:var(--up)} .pt-chip i.down{color:var(--down)}
/* the edge fades ride a scroll() timeline on the rail — no listener, so they
   can never disagree with the actual scroll position */
.pt-strip::before,.pt-strip::after{content:"";position:absolute;top:0;bottom:10px;width:44px;
  pointer-events:none;z-index:2;opacity:0}
.pt-strip::before{left:0;background:linear-gradient(90deg,var(--bg),transparent)}
.pt-strip::after{right:0;background:linear-gradient(270deg,var(--bg),transparent)}
@supports (animation-timeline: scroll()){
  .pt-strip{view-timeline-name:none}
  .pt-strip-rail{scroll-timeline:--ptrail x}
  .pt-strip::before{animation-name:ptFadeIn;animation-duration:auto;animation-fill-mode:both;
    animation-timing-function:linear;animation-timeline:--ptrail;animation-range:0% 8%}
  .pt-strip::after{animation-name:ptFadeOut;animation-duration:auto;animation-fill-mode:both;
    animation-timing-function:linear;animation-timeline:--ptrail;animation-range:92% 100%}
}
@keyframes ptFadeIn{from{opacity:0}to{opacity:1}}
@keyframes ptFadeOut{from{opacity:1}to{opacity:0}}

/* ============================================================================
   REDUCED MOTION — curated
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  /* hold: one press confirms (js short-circuits); no travelling fill */
  .pt-hold .pt-fill{transition:none}
  .pt-hold:active,.pt-morph:active,.pt-chip:active,.pt-rg-seg button:active{transform:none}
  /* morph keeps the state colours and the label, loses the travel */
  .pt-morph{transition:background var(--t-med,.24s) linear}
  .pt-morph .pt-lb{transition:none}
  .pt-morph[data-state="busy"] .pt-lb::after{animation:none}
  /* stream and caret: text simply present */
  .pt-stream .w span{transition:none;opacity:1;filter:none}
  .pt-stream.done .caret{animation:none}
  /* the edge fades are INFORMATION, so they stay — they just don't animate */
  .pt-strip::before,.pt-strip::after{animation:none;opacity:1}
}
html.mk-reduce .pt-hold .pt-fill{transition:none}
html.mk-reduce .pt-morph .pt-lb{transition:none}
html.mk-reduce .pt-morph[data-state="busy"] .pt-lb::after{animation:none}
html.mk-reduce .pt-stream .w span{transition:none;opacity:1;filter:none}
html.mk-reduce .pt-stream.done .caret{animation:none}
html.mk-reduce :is(.pt-hold,.pt-morph,.pt-chip):active{transform:none}

/* the DD grab pattern: while any finger-drag in this file's components is
   live, the page must not select or show a text cursor */
html.lv-grabbing, html.lv-grabbing *{user-select:none;-webkit-user-select:none}
html.lv-grabbing{cursor:grabbing}
