/* ===========================================================================
   Old Town Barbers — Motion Engine (CSS)   ·  Kai (motion/interaction)  ·  2026-07-07
   Round 2, parallel with Cass's visual pass. Self-contained, vanilla, no deps.
   Targets ONLY the shared hook-point contract. Companion: otb-motion.js + the
   head-arm snippet (otb-motion-arm.head.html). See README-integration.md.

   Progressive-enhancement contract (load-bearing):
     - The base / no-JS / SSR state is the FINAL VISIBLE state. Nothing here hides
       content on its own. The initial-hidden reveal state applies ONLY under the
       `.otb-anim` class, which the head snippet adds pre-paint AND only when JS is
       on and prefers-reduced-motion is NOT set. So GPTBot/ClaudeBot, no-JS, and
       reduced-motion users always see fully-painted content.
     - Everything animates on transform + opacity only (compositor-friendly, 60fps).
   Tokens are read from Cass's :root vars with hard fallbacks, so this file never
   introduces a color the brand sheet didn't already define.
   =========================================================================== */

/* ---- local token fallbacks (inherit Cass's :root if present) --------------- */
:root {
  --otb-brass:  var(--brass,  #C29A54);
  --otb-brass2: var(--brass2, #E2C687);
  --otb-coal:   var(--coal,   #1C1813);
  --otb-line-dark: var(--line-dark, rgba(255, 255, 255, .10));
  --otb-ease-reveal: cubic-bezier(.2, .7, .2, 1);
}

/* ===========================================================================
   1 · SCROLL REVEAL   —   [data-reveal] (+ optional data-reveal-delay)
   Hidden state is scoped under .otb-anim (JS-armed, pre-paint). Without that
   class the element is simply visible => safe for no-JS / bots / reduced-motion.
   =========================================================================== */
.otb-anim [data-reveal]:not(.is-in) {
  opacity: 0;
  transform: translateY(16px);
}
.otb-anim [data-reveal] {
  transition:
    opacity   620ms var(--otb-ease-reveal),
    transform 620ms var(--otb-ease-reveal);
  transition-delay: var(--otb-reveal-delay, 0ms);
}
.otb-anim [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* Safety: if the module fails to load/init, the head snippet flips this class on
   after 3s so nothing stays stuck hidden. */
.otb-anim-fallback [data-reveal] {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}

/* ===========================================================================
   2 · NAV CONDENSE   —   [data-nav][data-nav-state="condensed"]
   JS toggles data-nav-state ("top" <-> "condensed"). This block supplies the
   240ms transition + the condensed appearance (solid coal + hairline). If your
   nav component already paints its condensed state, keep yours and drop the
   appearance rule below — the load-bearing piece is the transition timing + the
   attribute toggle (which lives in the JS).
   =========================================================================== */
[data-nav] {
  transition:
    background-color 240ms ease,
    box-shadow       240ms ease,
    border-color     240ms ease,
    backdrop-filter  240ms ease;
}
[data-nav][data-nav-state="condensed"] {
  background-color: var(--otb-coal);
  border-bottom: 1px solid var(--otb-line-dark);
  backdrop-filter: saturate(120%) blur(6px);
}

/* ===========================================================================
   3 · IMAGE HOVER   —   [data-gallery-tile]
   Slow scale to 1.05 on the tile's <img>, plus a brass vignette + "View" pill.
   Pure CSS (hover). The one structural expectation: a gallery tile contains an
   <img> (semantic for a photo gallery). Pill + vignette are pseudo-elements, so
   they never enter the DOM/a11y tree and are invisible to bots.
   =========================================================================== */
[data-gallery-tile] {
  position: relative;
  overflow: hidden;          /* clips the zoom-within-frame; shadow lives outside */
  isolation: isolate;
}
[data-gallery-tile] img {
  display: block;
  transition: transform 520ms ease-out;
  transform: scale(1);
  backface-visibility: hidden;   /* crisper scale on the compositor */
}
@media (hover: hover) {
  [data-gallery-tile]:hover img,
  [data-gallery-tile]:focus-within img {
    transform: scale(1.05);
  }
}
/* brass vignette (inset glow + gentle darken at the edges) */
[data-gallery-tile]::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  opacity: 0;
  transition: opacity 520ms ease-out;
  background:
    radial-gradient(120% 90% at 50% 45%, transparent 55%, rgba(20, 18, 16, .42) 100%);
  box-shadow: inset 0 0 0 1px rgba(194, 154, 84, .0);
}
@media (hover: hover) {
  [data-gallery-tile]:hover::before,
  [data-gallery-tile]:focus-within::before {
    opacity: 1;
    box-shadow: inset 0 0 0 1px rgba(194, 154, 84, .55);
  }
}
/* "View" pill */
[data-gallery-tile]::after {
  content: "View";
  position: absolute;
  left: 50%;
  bottom: 14px;
  z-index: 2;
  transform: translate(-50%, 8px);
  padding: 6px 16px;
  font: 600 11px/1 "Inter", system-ui, sans-serif;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--otb-coal);
  background: var(--otb-brass);
  border-radius: 999px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 360ms ease-out, transform 360ms ease-out;
}
@media (hover: hover) {
  [data-gallery-tile]:hover::after,
  [data-gallery-tile]:focus-within::after {
    opacity: 1;
    transform: translate(-50%, 0);
  }
}

/* ===========================================================================
   4 · LIGHTBOX OPEN / ZOOM   —   [data-lightbox]
   Cass owns the functional lightbox (open/close/keyboard/swipe/Esc). Kai owns
   ONLY the open animation: backdrop fade + image zoom-from-grid-position.
   The `.otb-lb--anim` class is added by JS at open-time and removed at close, so
   these overrides are scoped to the animation window and never fight Cass's
   own show/hide state. Structural expectation: [data-lightbox] contains an <img>.
   =========================================================================== */
[data-lightbox].otb-lb--anim {
  opacity: 0;
  transition: opacity 360ms ease-out;
}
[data-lightbox].otb-lb--anim img {
  opacity: 0;
  transform: scale(.92);
  transition: opacity 360ms ease-out, transform 360ms ease-out;
  /* transform-origin is set inline by JS to the clicked tile's position */
}
[data-lightbox].otb-lb--anim.otb-lb--in {
  opacity: 1;
}
[data-lightbox].otb-lb--anim.otb-lb--in img {
  opacity: 1;
  transform: none;
}

/* ===========================================================================
   5 · LIVE-IG MARQUEE   —   [data-ig-marquee]
   Soren's server component renders the posts inside; Kai animates the track
   only. JS clones the row once (clones are aria-hidden + data-otb-clone, and
   JS-only => never seen by bots) so translateX(-50%) loops seamlessly.
   The marquee element must sit inside an overflow-hidden wrapper (Cass/Soren).
   =========================================================================== */
[data-ig-marquee].otb-marquee--on {
  display: flex;
  flex-wrap: nowrap;
  width: max-content;
  will-change: transform;
  animation: otb-ig-marquee 40s linear infinite;
}
[data-ig-marquee].otb-marquee--on:hover,
[data-ig-marquee].otb-marquee--on.otb-marquee--paused {
  animation-play-state: paused;   /* pause on hover + when tab is hidden */
}
@keyframes otb-ig-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* ===========================================================================
   7 · REDUCED MOTION   —   resolve EVERYTHING to instant final state.
   Belt-and-suspenders: the head snippet already refuses to arm .otb-anim under
   reduced-motion; this media query is the hard backstop for hover/lightbox/
   marquee too.
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .otb-anim [data-reveal],
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  [data-nav] { transition: none !important; }
  [data-gallery-tile] img { transition: none !important; transform: none !important; }
  [data-gallery-tile]::before,
  [data-gallery-tile]::after { transition: none !important; }
  [data-lightbox],
  [data-lightbox] img {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  [data-ig-marquee].otb-marquee--on {
    animation: none !important;
    transform: none !important;
  }
}
