/* ABCC — shared scroll effects (pairs with scroll-fx.js).
   Every hidden state is gated behind .fx-on, which the script only adds when
   motion is allowed and IntersectionObserver exists — so content is never
   hidden without JavaScript, and never for reduced-motion visitors. */

/* ---------- Reading progress ---------- */
.fx-progress {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 150; /* above the header (100), below the mobile menu (200) */
  height: 3px;
  pointer-events: none;
}
.fx-progress i {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--blue), var(--blue-bright));
  transform: scaleX(0);
  transform-origin: 0 50%;
  will-change: transform;
}

/* ---------- Back to top ---------- */
.fx-top {
  position: fixed;
  right: max(1rem, env(safe-area-inset-right));
  bottom: max(1rem, env(safe-area-inset-bottom));
  z-index: 90; /* under the header, menu and photo viewer */
  display: grid;
  place-items: center;
  width: 2.875rem;
  height: 2.875rem;
  border: 1px solid rgba(243, 236, 221, 0.28);
  border-radius: 50%;
  background: var(--dark);
  color: var(--cream-on-dark);
  box-shadow: 0 10px 28px rgba(8, 27, 43, 0.28);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(0.75rem);
  transition: opacity 0.35s, transform 0.45s var(--ease-out), visibility 0s 0.35s, background 0.25s;
}
.fx-top.is-shown {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 0.35s, transform 0.45s var(--ease-out), visibility 0s, background 0.25s;
}
.fx-top svg { width: 1.1rem; height: 1.1rem; }
.fx-top:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 3px; }
@media (hover: hover) {
  .fx-top:hover { background: var(--blue); }
}

/* ---------- Entrances ----------
   Same recipe as the Events / Weekly pages: hidden until seen, then a keyframe
   run with `backwards` fill, so an element's own hover transitions and
   transforms are untouched once it has arrived. */
.fx-on .fx-rise:not(.is-in) { opacity: 0; transform: translate3d(0, var(--reveal-dist), 0); }
.fx-on .fx-rise.is-in { animation: fx-rise var(--reveal-dur) var(--ease-out) var(--fx-delay, 0ms) backwards; }
.fx-on .fx-rise--soft:not(.is-in) { transform: translate3d(0, 14px, 0); }
.fx-on .fx-rise--soft.is-in { animation-name: fx-rise-soft; animation-duration: 0.7s; }
.fx-on .fx-rise--fade:not(.is-in) { transform: none; }
.fx-on .fx-rise--fade.is-in { animation-name: fx-fade; }
@keyframes fx-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes fx-rise {
  from { opacity: 0; transform: translate3d(0, var(--reveal-dist), 0); }
  to { opacity: 1; transform: none; }
}
@keyframes fx-rise-soft {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
  to { opacity: 1; transform: none; }
}

/* ---------- Framed photos drift a few pixels as they cross the screen ----------
   Only inside frames that already clip (overflow: hidden); the slight scale
   keeps the frame covered while the image moves. */
.fx-on .fx-drift {
  transform: translate3d(0, var(--fx-y, 0px), 0) scale(1.07);
  will-change: transform;
}

@media (max-width: 640px) {
  .fx-top { width: 2.75rem; height: 2.75rem; }
}

@media print {
  .fx-progress, .fx-top { display: none; }
  .fx-on .fx-rise:not(.is-in) { opacity: 1; transform: none; }
}

/* ---------------------------------------------------------------------
   Page heroes — the home hero's sequence, shared by every inner page
   (scroll-fx.js heroEntrance): photo settles → headline rises word by word →
   the italic accent writes itself in → a blue underline draws → lede, button
   and section links follow. Only runs with .fx-on (motion allowed).
   --------------------------------------------------------------------- */
.fx-title .fx-em { position: relative; display: inline-block; }
.fx-title .fx-ink { display: inline-block; }
.fx-flourish {
  position: absolute; left: .02em; bottom: -.14em;
  width: 92%; height: .1em;
  overflow: visible; pointer-events: none;
  stroke-dasharray: 330; stroke-dashoffset: 0;
}
.fx-on .fx-hero .fx-hero-photo { animation: fx-photo-in 1s var(--ease-out) var(--hx, 0ms) backwards; }
.fx-on .fx-hero .fx-hero-photo :is(.s-photo__frame, .b-window__frame, .vision-window__frame) {
  animation: fx-photo-rise 1.45s cubic-bezier(.65, 0, .25, 1) var(--hx, 0ms) backwards;
}
.fx-on .fx-title .fx-w { display: inline-block; animation: fx-word .8s var(--ease-out) var(--hx, 0ms) backwards; }
.fx-on .fx-title .fx-ink { animation: fx-write 1s cubic-bezier(.55, .1, .35, 1) var(--hx, 0ms) backwards; }
.fx-on .fx-title .fx-flourish { animation: fx-draw 1.1s var(--ease-out) var(--hx, 0ms) backwards; }
.fx-on .fx-hero-part { animation: fx-rise var(--reveal-dur) var(--ease-out) var(--hx, 0ms) backwards; }
.fx-on .fx-hero-part--soft { animation-name: fx-rise-soft; animation-duration: .7s; }
/* Phones (photo under the headline): a touch quicker so the whole header lands in ~2s. */
.fx-on .fx-hero--stacked .fx-title .fx-w { animation-duration: .65s; }
.fx-on .fx-hero--stacked .fx-hero-photo { animation-duration: .82s; }
.fx-on .fx-hero--stacked .fx-hero-photo :is(.s-photo__frame, .b-window__frame, .vision-window__frame) { animation-duration: 1s; }
/* Sections already on screen below a landing hero wait unseen (scroll-fx.js holdBelow);
   .fx-replay briefly resets their entrances so they can play again once shown. */
.fx-on .fx-held { opacity: 0; visibility: hidden; }
/* scroll-fx.js is deferred, so the browser paints the finished hero (and whatever sits
   below it) before the entrance can claim it — a full flash, then a blank, then the
   animation. Until the script adds .fx-ready, start from the entrance's opening frame
   instead. Failsafe: if the script never arrives, everything shows after 2.5s. */
@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
  html:not(.fx-ready) main > :is(.s-hero, .b-hero, .vision-intro) :is(:has(> h1) > *, .s-photo, .b-window, .vision-window, .s-index a, .b-index a, .vision-index a),
  html:not(.fx-ready) main > :is(.s-hero, .b-hero, .vision-intro) ~ *,
  html:not(.fx-ready) body:has(main > :is(.s-hero, .b-hero, .vision-intro)) .footer {
    opacity: 0;
    animation: fx-failsafe 0s linear 2.5s forwards;
  }
}
@keyframes fx-failsafe { to { opacity: 1; } }
.fx-replay, .fx-replay * { transition: none !important; animation: none !important; }

@keyframes fx-photo-in { from { opacity: 0; transform: translate3d(0, 28px, 0); } }
/* Grows up from the bottom edge; the rounded top (see --fx-round in scroll-fx.js) leads. */
@keyframes fx-photo-rise {
  from { clip-path: inset(100% 0 0 0 round var(--fx-round, 0)); }
  to { clip-path: inset(0 0 0 0 round var(--fx-round, 0)); }
}
@keyframes fx-photo-open { from { clip-path: inset(9% 9% 9% 9%); } to { clip-path: inset(0); } }
@keyframes fx-word { from { opacity: 0; transform: translateY(.45em) rotate(5deg); filter: blur(6px); } }
@keyframes fx-write { from { clip-path: inset(-30% 100% -40% -10%); } to { clip-path: inset(-30% -10% -40% -10%); } }
@keyframes fx-draw { from { stroke-dashoffset: 330; } to { stroke-dashoffset: 0; } }

/* Section headings and content photos (scroll-fx.js) — lighter than the heroes. */
.fx-head .fx-w, .fx-head .fx-ink { display: inline-block; }
.fx-on .fx-head:not(.is-in) .fx-w { opacity: 0; transform: translateY(.4em); }
.fx-on .fx-head.is-in .fx-w { animation: fx-word-soft .7s var(--ease-out) var(--hx, 0ms) backwards; }
.fx-on .fx-head:not(.is-in) .fx-ink { clip-path: inset(-30% 100% -40% -10%); }
.fx-on .fx-head.is-in .fx-ink { animation: fx-write .9s cubic-bezier(.55, .1, .35, 1) var(--hx, 0ms) backwards; }
.fx-on .fx-photo:not(.is-in) { clip-path: inset(7% 7% 7% 7%); }
.fx-on .fx-photo.is-in { animation: fx-photo-open 1.25s var(--ease-out) backwards; }
@keyframes fx-word-soft { from { opacity: 0; transform: translateY(.4em); filter: blur(4px); } }
