/* ═══════════════════════════════════════════════════════════════════════════
   Umlook — landing page motion layer  (/)

   Loaded AFTER landing.css and only by public/landing.html. It adds motion and
   nothing else: every selector here either animates an element landing.css
   already styles, or styles a state class that only landing-anim.js sets.
   landing.css is never edited, so "/" is unaffected by anything in this file.

   House rule for this layer: motion is short (180–700ms), eased out, and moves
   things a few pixels at most. The page sells calm, expensive photography — it
   should not bounce.

   Every effect is switched off by the `prefers-reduced-motion: reduce` block at
   the bottom, which resolves reveals to their finished state and stops loops.
   Don't add an animation here that that block can't turn off.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);   /* long tail, no overshoot */
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  --dur: 700ms;
}

/* ── Scroll reveal ───────────────────────────────────────────────────────────
   landing-anim.js puts `js-anim` on <html> and observes every [data-reveal]. The
   hidden state is scoped to .js-anim so that with JS disabled or broken the
   page renders exactly like "/" instead of a column of invisible sections. */

.js-anim [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity var(--dur) var(--ease-out),
    transform var(--dur) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
.js-anim [data-reveal="left"]  { transform: translateX(-22px); }
.js-anim [data-reveal="right"] { transform: translateX(22px); }
.js-anim [data-reveal="scale"] { transform: scale(0.965); }

.js-anim [data-reveal].is-in {
  opacity: 1;
  transform: none;
  will-change: auto;   /* released once the element has landed */
}

/* ── Scroll progress ─────────────────────────────────────────────────────── */

.progress {
  position: fixed;
  top: 0; left: 0;
  height: 2px;
  width: 100%;
  z-index: 200;
  background: transparent;
  pointer-events: none;
}
.progress__bar {
  height: 100%;
  width: 100%;
  background: var(--sienna);
  transform: scaleX(0);
  transform-origin: 0 50%;
  /* driven from rAF in landing-anim.js — a transition here would lag the scroll */
}

/* ── Nav ─────────────────────────────────────────────────────────────────── */

.nav { transition: box-shadow .3s var(--ease-soft); }
.nav__inner { transition: height .3s var(--ease-soft); }
.nav.is-stuck .nav__inner { height: 54px; }
.nav.is-stuck { box-shadow: 0 1px 0 color-mix(in oklch, var(--ink) 8%, transparent); }

/* Sienna rule that grows out from the left on hover */
.nav__links a:not(.btn) { position: relative; }
.nav__links a:not(.btn)::after {
  content: "";
  position: absolute;
  left: 0; bottom: -5px;
  height: 1px; width: 100%;
  background: var(--sienna);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .32s var(--ease-out);
}
.nav__links a:not(.btn):hover::after { transform: scaleX(1); }

/* The logo's brow hover lives in landing.css -- it's a shared component. */

/* ── Buttons ─────────────────────────────────────────────────────────────── */

/* Ink sweeps in from the left instead of cross-fading the background. The
   label is wrapped in a span by landing-anim.js so it can sit above the sweep. */
.btn { position: relative; overflow: hidden; z-index: 0; }
.btn > .btn__label { position: relative; z-index: 1; display: inline-block; }
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .4s var(--ease-out);
}
.btn--outline::before { background: var(--ink); }
.btn--primary::before { background: oklch(0.34 0.014 60); }
.btn--invert::before  { background: oklch(0.90 0.014 75); }
.btn:hover::before { transform: scaleX(1); }
/* landing.css swaps the background on hover; here the sweep does that job, so
   the base colour has to stay put underneath it. */
.btn--outline:hover { background: transparent; color: var(--paper); }
.btn--primary:hover { background: var(--ink); }
.btn--invert:hover  { background: var(--paper); }
.btn:active { transform: translateY(1px); }

/* Trailing arrow slides on hover, in its own span so the label never shifts */
.btn__arrow { display: inline-block; transition: transform .32s var(--ease-out); }
.btn:hover .btn__arrow { transform: translateX(5px); }

/* ── Hero entrance ───────────────────────────────────────────────────────── */

.js-anim .hero__line,
.js-anim .hero__sub,
.js-anim .hero__cta,
.js-anim .hero__note,
.js-anim .hero__visual {
  opacity: 0;
  transform: translateY(20px);
  animation: rise .9s var(--ease-out) forwards;
}
.js-anim .hero__line { display: block; }
.js-anim .hero__line:nth-child(1) { animation-delay: .06s; }
.js-anim .hero__line:nth-child(2) { animation-delay: .16s; }
.js-anim .hero__sub    { animation-delay: .28s; }
.js-anim .hero__cta    { animation-delay: .38s; }
.js-anim .hero__note   { animation-delay: .46s; }
.js-anim .hero__visual { animation-delay: .30s; }

@keyframes rise { to { opacity: 1; transform: none; } }

/* A hairline draws itself under the headline once the copy has landed — a
   small "the page has finished arriving" full stop. */
.hero h1 { position: relative; }
.js-anim .hero h1::after {
  content: "";
  position: absolute;
  left: 0; bottom: -14px;
  height: 1px; width: 96px;
  background: var(--sienna);
  transform: scaleX(0);
  transform-origin: 0 50%;
  animation: draw .7s var(--ease-out) .7s forwards;
}
@keyframes draw { to { transform: scaleX(1); } }

/* ── Proof strip: continuous marquee ─────────────────────────────────────── */

/* landing-anim.js duplicates the <ul> once so the track loops seamlessly; the copy
   is aria-hidden. .proof__track only exists if that ran, so with no JS the
   strip stays the original centred flex list. */
.proof { overflow: hidden; }
.proof__track {
  display: flex;
  width: max-content;
  animation: marquee 34s linear infinite;
}
.proof:hover .proof__track { animation-play-state: paused; }
.proof__track .proof__list { flex: none; justify-content: flex-start; padding-right: 26px; }
@keyframes marquee { to { transform: translateX(-50%); } }

/* ── Step numbers ────────────────────────────────────────────────────────── */

.step { position: relative; }
.step__n { transition: transform .5s var(--ease-out); }
.step:hover .step__n { transform: translateY(-3px); }
.js-anim .step::before {
  content: "";
  position: absolute;
  top: 0; left: -18px;
  width: 1px; height: 100%;
  background: var(--oat);
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition: transform .8s var(--ease-out) var(--reveal-delay, 0ms);
}
.js-anim .step.is-in::before { transform: scaleY(1); }
@media (max-width: 900px) { .js-anim .step::before { display: none; } }

/* ── Catalog cards ───────────────────────────────────────────────────────── */

.card-item__frame { display: block; overflow: hidden; }
.card-item img { transition: transform .6s var(--ease-out), filter .4s var(--ease-soft); }
.card-item:hover img { transform: scale(1.04); }

/* Locked items sit desaturated until hovered, so the paywall reads before the
   badge does. */
.locked img { filter: grayscale(0.55) contrast(0.96); }
.locked:hover img { filter: none; }

/* The plan badge fills on hover. It used to scale and rotate, which suited the
   padlock it replaced and not a word. Keyed to .card-item, not .locked, so
   hovering the name or the caption lights the badge too — .locked wraps only
   the photo. The Starter badge fills in its own grey: same gesture, and sienna
   stays the paid tiers' colour even in motion. */
.card-item__plan {
  transition:
    background .3s var(--ease-soft),
    border-color .3s var(--ease-soft),
    color .3s var(--ease-soft);
}
.card-item:hover .card-item__plan { background: var(--sienna); color: var(--paper); }
.card-item:hover .card-item__plan--free {
  background: var(--graphite-lt);
  border-color: var(--graphite-lt);
  color: var(--paper);
}

.scroller { scroll-behavior: smooth; }

/* ── Comparison table ────────────────────────────────────────────────────── */

.compare-table tbody tr { transition: background .3s var(--ease-soft); }
.compare-table tbody tr:hover { background: color-mix(in oklch, var(--oat) 22%, transparent); }
.js-anim .compare-table tbody tr {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity .55s var(--ease-out), transform .55s var(--ease-out), background .3s;
}
.js-anim .compare-table.is-in tbody tr { opacity: 1; transform: none; }
.js-anim .compare-table.is-in tbody tr:nth-child(1) { transition-delay: .05s; }
.js-anim .compare-table.is-in tbody tr:nth-child(2) { transition-delay: .12s; }
.js-anim .compare-table.is-in tbody tr:nth-child(3) { transition-delay: .19s; }
.js-anim .compare-table.is-in tbody tr:nth-child(4) { transition-delay: .26s; }
.js-anim .compare-table.is-in tbody tr:nth-child(5) { transition-delay: .33s; }

/* ── Pricing ─────────────────────────────────────────────────────────────── */

.plan { transition: transform .45s var(--ease-out), border-color .35s var(--ease-soft); }
.plan:hover { transform: translateY(-6px); border-color: var(--graphite-lt); }
.plan--featured:hover { border-color: var(--sienna); }

/* Three passes of light across the "Most popular" ribbon, then it stops. A
   permanent loop next to a price is a nag. */
.plan__ribbon { overflow: hidden; }
.js-anim .plan--featured.is-in .plan__ribbon::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 35%, color-mix(in oklch, white 55%, transparent) 50%, transparent 65%);
  transform: translateX(-100%);
  animation: shine 2.4s var(--ease-soft) 3;
}
@keyframes shine { to { transform: translateX(100%); } }

.plan__perks li { transition: color .25s var(--ease-soft); }
.plan:hover .plan__perks li { color: var(--ink); }

/* ── FAQ ─────────────────────────────────────────────────────────────────── */

/* <details> can't animate its own open/close, so landing-anim.js drives the height
   and this only styles the transition. The +/– becomes one rotating glyph. */
.faq details { overflow: hidden; }
.faq summary::after { transition: transform .35s var(--ease-out); }
.faq details[open] summary::after { content: "+"; transform: translateY(-50%) rotate(45deg); }
.faq summary { transition: color .25s var(--ease-soft), padding-left .3s var(--ease-out); }
.faq summary:hover { color: var(--sienna); padding-left: 6px; }
.faq__body { overflow: hidden; }

/* ── Closing band ────────────────────────────────────────────────────────── */

.closer { position: relative; overflow: hidden; }
.closer::before {
  content: "";
  position: absolute;
  inset: -40%;
  background: radial-gradient(circle at 50% 50%, color-mix(in oklch, var(--sienna) 26%, transparent) 0%, transparent 62%);
  opacity: 0;
  transition: opacity 1.4s var(--ease-soft);
}
.js-anim .closer.is-in::before { opacity: 1; animation: drift 18s ease-in-out infinite alternate; }
.closer .wrap { position: relative; z-index: 1; }
@keyframes drift {
  from { transform: translate3d(-4%, -3%, 0) scale(1); }
  to   { transform: translate3d(4%, 3%, 0) scale(1.12); }
}

/* ── Back to top ─────────────────────────────────────────────────────────── */

.totop {
  position: fixed;
  right: 22px; bottom: 22px;
  z-index: 150;
  width: 42px; height: 42px;
  display: grid;
  place-items: center;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--ink);
  font-size: 14px;
  cursor: pointer;
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition: opacity .35s var(--ease-out), transform .35s var(--ease-out), background .2s, color .2s;
}
.totop.is-on { opacity: 1; transform: none; pointer-events: auto; }
.totop:hover { background: var(--ink); color: var(--paper); }

/* ── Focus, and the motion opt-out ───────────────────────────────────────── */

a:focus-visible, button:focus-visible, summary:focus-visible {
  outline: 2px solid var(--sienna);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  /* Resolve every reveal to its finished state and stop every loop. The page
     must be complete and fully styled with all motion removed. */
  html { scroll-behavior: auto; }
  .js-anim [data-reveal],
  .js-anim [data-reveal].is-in,
  .js-anim .hero__line,
  .js-anim .hero__sub,
  .js-anim .hero__cta,
  .js-anim .hero__note,
  .js-anim .hero__visual,
  .js-anim .compare-table tbody tr {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
    transition: none !important;
  }
  .js-anim .hero h1::after { transform: scaleX(1); animation: none; }
  .proof__track { animation: none; }
  .js-anim .step::before { transform: scaleY(1); transition: none; }
  .js-anim .plan--featured.is-in .plan__ribbon::after { display: none; animation: none; }
  .js-anim .closer.is-in::before { animation: none; opacity: .6; }
  .card-item:hover img, .plan:hover { transform: none; }
  .btn::before { transition: none; }
  .totop { transition: opacity .01s; }
}
