/* MomentSearch - type, motion and texture: the parts Tailwind can't express.
 *
 * The design tokens (paper / ink / coral / line, Newsreader + Geologica) are declared
 * in landing.html's <head>. NOTHING here introduces a colour or a typeface that
 * isn't already in that palette - this file only adds movement.
 *
 * Motion budget: entrances are one 60ms cascade, there are no looping
 * animations, and everything collapses under prefers-reduced-motion (bottom).
 */

/* ── Base ──────────────────────────────────────────────────────────────────── */
html,body{background:#FBF7F0;color:#1E1B16;font-family:Geologica,system-ui,sans-serif}
.display{font-family:Newsreader,Georgia,serif}

/* A display line that ends on a single short word reads as a mistake at any
   size, and this headline did it at every breakpoint ("...in any / video.").
   balance evens the line lengths; pretty only guards the last line, which is
   what body copy needs. Both degrade to normal wrapping where unsupported. */
.balance{text-wrap:balance}
.pretty{text-wrap:pretty}
::selection{background:rgba(255,90,60,.22)}
/* Solid ember rather than a translucent coral haze: the translucent version
   composited to ~1.5:1 against the paper background, well under the 3:1 WCAG
   2.2 minimum for a focus indicator. Ember is already a page token (the step
   numerals use it), so this borrows rather than adds a color. */
a:focus-visible,button:focus-visible,input:focus-visible,textarea:focus-visible{
  outline:3px solid #B82C14;outline-offset:3px}

/* A field that failed its own check, in the same ember as the focus ring and
   the error copy - one color for "something here needs attention". */
input.field-invalid,textarea.field-invalid{border-color:#B82C14}

/* Product first, with the maker directly beneath and one clear action right. */
.brand-lockup{display:flex;flex-direction:column;align-items:flex-start;line-height:1}
.brand-main{display:flex;align-items:center;color:#1E1B16;text-decoration:none}
.brand-name{display:inline-flex;align-items:center;font-size:1.1rem;font-weight:700;letter-spacing:-.035em}
.brand-o{width:.78em;height:.78em;margin:0 .015em;overflow:visible;color:#1E1B16;flex:none}
.brand-o-footer{font-size:1em}
.brand-search{color:#FF5A3C}
.brand-powered{display:flex;align-items:center;gap:.3rem;margin-top:.35rem;color:#6B6357;
  font-size:.625rem;font-weight:500;text-decoration:none}
.brand-powered img{width:auto;height:1rem;opacity:.82;transition:opacity .2s ease}
.brand-powered:hover img{opacity:1}

/* ── Paper grain ───────────────────────────────────────────────────────────
 * A single fixed multiply layer so the whole page reads as printed stock
 * rather than a flat #FBF7F0 fill. Purely decorative, never interactive.     */
.grain{position:fixed;inset:0;z-index:60;pointer-events:none;opacity:.04;mix-blend-mode:multiply;
  background-repeat:repeat;background-size:180px 180px;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/></filter><rect width='180' height='180' filter='url(%23n)'/></svg>")}

/* ── Entrance cascade ──────────────────────────────────────────────────────
 * Every above-the-fold element gets .rise and a --d slot; 60ms apart.        */
.rise{opacity:0;animation:rise .62s cubic-bezier(.22,.68,.36,1) forwards;
  animation-delay:calc(var(--d,0)*60ms)}
@keyframes rise{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}

/* The Newsreader headline arrives word by word, with a slight settle. */
.word{display:inline-block;opacity:0;animation:word .7s cubic-bezier(.22,.68,.36,1) forwards;
  animation-delay:calc(200ms + var(--i,0)*70ms)}
@keyframes word{from{opacity:0;transform:translateY(20px) rotate(1.4deg)}to{opacity:1;transform:none}}

/* …then the coral word writes itself. A nib sweeps left to right and the
 * letters appear behind it, so the word reads as being written rather than
 * faded in. The clip and the nib share one easing curve and one duration, which
 * is what keeps the pen tip exactly on the edge of the ink for the whole sweep
 * - they are two properties of the same motion, so they must not drift.
 *
 * The curve is symmetric ease-in-out on purpose: a hand accelerates, holds a
 * steady pace across the word, then settles. A front-loaded ease-out curve
 * makes the pen sprint through the first letters and crawl through the last,
 * which reads as a wipe rather than as writing.
 *
 * The word's .word wrapper still runs the cascade, but its content starts fully
 * clipped, so nothing shows through early: the slot arrives empty and then
 * fills. WRITE_DELAY (.75s) lands just after the cascade has settled.          */
.mark{position:relative;white-space:nowrap}
.mark .ink{display:inline-block;clip-path:inset(0 100% 0 0);
  animation:write 1.05s .75s cubic-bezier(.42,0,.58,1) forwards}
@keyframes write{to{clip-path:inset(0 -.06em 0 0)}}

/* The nib: a short coral stroke at the leading edge that lifts off the page
 * once the word is finished. Its own width is excluded from the travel so it
 * stops flush with the final letter instead of overshooting.                  */
.mark .nib{position:absolute;top:.1em;bottom:.2em;left:0;width:2px;border-radius:2px;
  background:#FF5A3C;opacity:0;transform:translateX(-1px);
  animation:nib 1.05s .75s cubic-bezier(.42,0,.58,1) forwards}
@keyframes nib{
  0%{left:0;opacity:1}
  88%{opacity:1}
  100%{left:100%;opacity:0}
}

/* …and it signs off with the underline, once the writing is done (.75 + 1.05).
 * non-scaling-stroke keeps the line an even weight despite the non-uniform
 * scale that stretching the viewBox to the word's width implies.            */
.mark svg{position:absolute;left:0;width:100%;bottom:-.16em;height:.36em;overflow:visible}
.mark path{fill:none;stroke:#FF5A3C;stroke-width:3.4;stroke-linecap:round;
  vector-effect:non-scaling-stroke;stroke-dasharray:340;stroke-dashoffset:340;
  animation:draw .75s 1.8s cubic-bezier(.4,0,.2,1) forwards}
@keyframes draw{to{stroke-dashoffset:0}}

/* ── Step spine ────────────────────────────────────────────────────────────
 * The hairline through the numerals says "sequence": one rule spanning the whole
 * row with the three dots threaded onto it, so the row reads across before it
 * reads down any single step. It runs edge to edge rather than stopping at the
 * outer two dots - a rule that ends exactly where the first and last dot sit
 * reads as a measured span between them, which is the wrong idea; carrying it
 * past both says the sequence is a continuous line the steps are placed on.
 * Nothing here needs the column geometry any more, so the ends are 0 and the
 * dots (solid coral, z-10) sit over it rather than interrupting it.
 *
 * Vertically the dot is nudged .125rem by .step-num below, so its centre is
 * 1.125rem down, not 1rem; the half-pixel then pulls the 1px rule onto that
 * centre instead of a pixel below it.                                         */
.spine{top:calc(1.125rem - .5px);left:0;right:0}

/* Solid coral badge rather than an outline on paper: at 32px an outline reads
   as a bullet, not a step in a sequence - the filled dot is what the spine
   (above) and the mobile rail (below) both need to visually anchor into. */
.step-num{top:.125rem}

/* Below sm the grid is one column and .spine (a horizontal rule) has nothing
   to span, so the sequence cue would vanish entirely without this: a vertical
   rail down the badge column. Row heights vary with text wrap, so this can't
   be pinned to the last badge's exact centre the way .spine pins to a fixed
   row height - anchoring 1rem short of both ends (roughly the first and last
   badge's centre) reads as "runs through the steps" without needing that. */
.spine-v{top:1rem;left:1rem;bottom:1rem}

/* ── "Try it live" chooser ─────────────────────────────────────────────────
 * Opens in the page rather than over it: nothing to dismiss, and on a slide the
 * choice stays visible next to the thing that prompted it. Grid rows let the
 * content open smoothly without guessing its height; visibility removes the
 * collapsed cards from the tab order.                                       */
.reveal{display:grid;grid-template-rows:0fr;margin-top:0;opacity:0;visibility:hidden;
  transition:grid-template-rows .45s cubic-bezier(.22,.68,.36,1),opacity .3s ease,visibility .45s}
.reveal>div{min-height:0;overflow:hidden}
.reveal.open{grid-template-rows:1fr;margin-top:1.5rem;opacity:1;visibility:visible}
.chev{display:inline-block;transition:transform .3s cubic-bezier(.22,.68,.36,1)}
.chev.up{transform:rotate(180deg)}

/* The two cards land in sequence rather than both at once. Scoped to .open so
 * the cascade plays when the panel is opened - not silently at page load while
 * the panel is still collapsed (and again on every reopen, since toggling .open
 * restarts the animation).                                                    */
.optcard{opacity:0}
.reveal.open .optcard{animation:pop .5s cubic-bezier(.22,.68,.36,1) forwards;
  animation-delay:calc(var(--i,0)*70ms)}
.optcard:hover{transform:translateY(-1px)}
@keyframes pop{from{opacity:0;transform:translateY(12px) scale(.985)}to{opacity:1;transform:none}}

/* ── The proof strip ───────────────────────────────────────────────────────
 * The card under the hero is a diagram of the mechanism, and the mechanism is
 * a SEQUENCE: a question arrives, every frame and every spoken word gets
 * scanned, one second wins. Drawn static it showed only the last step — a lit
 * marker with no account of how it came to be lit — so it read as decoration
 * rather than as the proof it is meant to be.
 *
 * The delays below are tied to the geometry in landing.html: the sweep crosses
 * the axis in 3s starting at 1.6s, and each tick and waveform bar lights at
 * the moment the sweep actually reaches it (--i is set per element there, and
 * the per-step figures are the sweep duration divided by the 13 tick gaps and
 * 23 bar gaps: 3s/13 = .2308s, 3s/23 = .1304s).                              */
.pt-tick{fill:#F3ECE0;stroke:#E7DECF}
.pt-bar{fill:#6B6357;opacity:.35}
.pt-scan{opacity:0}
.pt-hit-bar{opacity:0;transform-box:fill-box;transform-origin:50% 100%;transform:scaleY(0)}
.pt-cap{opacity:0}
.pt-q::after{content:"";display:inline-block;width:2px;height:.92em;background:#FF5A3C;
  margin-left:3px;vertical-align:-.1em;opacity:0}
.proof.typing .pt-q::after{opacity:1;animation:pt-caret 1s steps(1) infinite}

/* The status line, and the pulse that says the thing is working. */
.pt-search,.pt-found{opacity:0}
.pt-dot{width:7px;height:7px;border-radius:50%;background:#FF5A3C;flex:none}

/* The moments it actually returned. Real thumbnails from the real corpus, so
   the timestamps and the relevance words below them are a captured result
   rather than a mock-up — which is also why the strip above carries no
   timestamp of its own any more: three moments from three different videos
   can't honestly sit on one video's timeline. It scans the corpus; these
   cards carry the detail. */
.pt-card{opacity:0}
.pt-shot{position:relative;aspect-ratio:16/9;border-radius:8px;overflow:hidden;
  background:#E7DECF;border:1px solid #E7DECF}
.pt-shot img{width:100%;height:100%;object-fit:cover;display:block}
.pt-ts{position:absolute;bottom:4px;left:4px;padding:1px 5px;border-radius:4px;
  background:rgba(30,27,22,.82);color:#fff;font-size:9px;font-weight:700;font-variant-numeric:tabular-nums}
.pt-rel{display:inline-flex;align-items:center;font-size:10px;font-weight:700;margin-top:5px;
  font-variant-numeric:tabular-nums}
.pt-rel-hi{color:#1f7a43}
.pt-rel-mid{color:#8a6d1a}
.pt-rel-lo{color:#6B6357}
.pt-ct{margin-top:2px;font-size:10px;line-height:1.35;color:#6B6357;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}

.proof.play .pt-search{animation:pt-fade .4s ease 1.6s both, pt-out .3s ease 4.6s both}
.proof.play .pt-dot{animation:pt-pulse 1s ease-in-out 1.6s 3 both}
.proof.play .pt-found{animation:pt-fade .4s ease 4.8s both}
.proof.play .pt-scan{animation:pt-sweep 3s linear 1.6s both}
.proof.play .pt-tick{animation:pt-lit .6s ease both;animation-delay:calc(1.6s + var(--i)*.2308s)}
.proof.play .pt-bar{animation:pt-litbar .6s ease both;animation-delay:calc(1.6s + var(--i)*.1304s)}
/* Two animations, not two rules — a second rule would overwrite the shorthand.
   Both delays are derived from --i, so a marker lights as the sweep reaches it
   and steps aside 150ms later, wherever it sits on the axis. */
.proof.play .pt-hit-dim{animation:pt-lit .6s ease calc(1.6s + var(--i)*.2308s) both,
  pt-out .3s ease calc(1.75s + var(--i)*.2308s) both}
.proof.play .pt-hit-bar{animation:pt-grow .5s cubic-bezier(.2,1.5,.4,1) both;
  animation-delay:calc(1.75s + var(--i)*.2308s)}
.proof.play .pt-card{animation:pt-rise .55s cubic-bezier(.2,1.1,.4,1) both;
  animation-delay:calc(4.8s + var(--i)*.22s)}
.proof.play .pt-cap{animation:pt-fade .5s ease 5.7s both}

@keyframes pt-caret{50%{opacity:0}}
@keyframes pt-pulse{
  0%,100%{transform:scale(1);box-shadow:0 0 0 0 rgba(255,90,60,.55)}
  50%{transform:scale(1.3);box-shadow:0 0 0 7px rgba(255,90,60,0)}
}
@keyframes pt-sweep{
  0%{opacity:0;transform:translateX(0)}
  6%{opacity:1} 94%{opacity:1}
  100%{opacity:0;transform:translateX(440px)}
}
@keyframes pt-lit{0%,100%{fill:#F3ECE0;stroke:#E7DECF}40%{fill:#FFD8CD;stroke:#FFAF9A}}
@keyframes pt-litbar{0%,100%{opacity:.35}40%{opacity:.9}}
@keyframes pt-out{to{opacity:0}}
@keyframes pt-grow{from{opacity:0;transform:scaleY(0)}to{opacity:1;transform:scaleY(1)}}
@keyframes pt-rise{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}
@keyframes pt-fade{to{opacity:1}}

/* ── Reduced motion ────────────────────────────────────────────────────────
 * Everything above is decoration; none of it carries meaning that survives
 * only in the movement. So: land on the final frame immediately.            */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:1ms!important;animation-iteration-count:1!important;
    transition-duration:1ms!important;scroll-behavior:auto!important}
  .word,.rise{opacity:1}
  .reveal.open .optcard{opacity:1;box-shadow:none}
  .mark path{stroke-dashoffset:0}
  /* the written word must end up readable, and the pen has no business
     appearing at all when motion is unwelcome */
  .mark .ink{clip-path:none}
  .mark .nib{display:none}
  /* The 1ms duration above collapses the proof strip's animations but does NOT
     touch their delays, so without this the diagram would sit blank and then
     snap together four seconds later. Land on the finished diagram at once,
     and drop the sweep and the caret entirely — both are pure movement. */
  .proof.play *{animation-delay:0s!important}
  .proof .pt-scan,.proof .pt-search{display:none}
  .proof .pt-q::after{display:none}
  .proof .pt-hit-bar{opacity:1;transform:scaleY(1)}
  .proof .pt-hit-dim{opacity:0}
  .proof .pt-found,.proof .pt-cap{opacity:1}
  .proof .pt-card{opacity:1;transform:none}
}
