/* ============================================================================
   סיפורון · site-motion.css
   Motion polish: initial states, progressive enhancement, reduced-motion rules.
   Pairs with site-motion.js. Adding either one alone must never break a page.

   THE ONE RULE THIS FILE OBEYS
   Nothing here hides content. There is no `opacity:0` waiting for JavaScript to
   undo it. The reveal's starting state is applied by site-motion.js, at runtime,
   only to elements that are currently BELOW the fold — so if the script never
   runs, if the Motion CDN is blocked, or if the browser is too old for modules,
   every page renders exactly as it does today. It also means no flash of content
   appearing and then being hidden again, which is what a CSS-first hidden state
   would cause on a deferred script.

   Everything below is either decoration that degrades to nothing, or a transition
   on a state that is already correct without it.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   1. Cards — hover on pointer devices, press on touch
   Done in CSS rather than JavaScript on purpose: these fire on every card on
   every page, and the compositor can run transform/opacity without ever waking
   the main thread. Motion is reserved for what CSS genuinely cannot express.
   --------------------------------------------------------------------------- */

/* The selectors are written out here rather than applied as a class by
   JavaScript. Story cards, show cards, plan cards and review cards are all
   rendered AFTER page load — from Supabase, or by the homepage's own render() —
   so a class applied once at boot missed every one of them. CSS has no such
   timing problem: a card gains this the moment it is inserted, and skeletons are
   excluded by selector rather than by a check that has to run at the right time. */
.mo-card,
.story-card:not(.sk-card),
.show:not(.sk-card),
.plan-card:not(.sk-card),
.voice-card:not(.sk-card),
#shelfCards .card:not(.sk-card),
#rails .card:not(.sk-card),
#searchGrid .card:not(.sk-card) {
  touch-action: manipulation;
}

/* hover:hover excludes touch screens, where :hover sticks after a tap and leaves
   a card looking permanently lifted. */
@media (hover: hover) and (pointer: fine) {
  .mo-card, .story-card:not(.sk-card), .show:not(.sk-card), .plan-card:not(.sk-card),
  .voice-card:not(.sk-card), #shelfCards .card:not(.sk-card),
  #rails .card:not(.sk-card), #searchGrid .card:not(.sk-card) {
    transition: transform .26s cubic-bezier(.22,1,.36,1),
                box-shadow .26s cubic-bezier(.22,1,.36,1),
                border-color .26s cubic-bezier(.22,1,.36,1);
    will-change: transform;
  }
  /* Rise 5px, scale 1.015 — under the 4-6px / 1.02 ceiling asked for. The gold
     ring is an extra shadow layer, never a border-color change, so no card loses
     the edge its own page gave it and nothing reflows. */
  .mo-card:hover, .story-card:not(.sk-card):hover, .show:not(.sk-card):hover,
  .plan-card:not(.sk-card):hover, .voice-card:not(.sk-card):hover,
  #shelfCards .card:not(.sk-card):hover, #rails .card:not(.sk-card):hover,
  #searchGrid .card:not(.sk-card):hover {
    transform: translateY(-5px) scale(1.015);
    box-shadow: 0 26px 52px -30px rgba(33,26,48,.62), 0 0 0 1px rgba(245,197,90,.34);
  }
}

/* Touch: a brief press-in. :active is released by the browser on touchend, so a
   card physically cannot get stuck pressed the way a JS-applied class can. */
@media (hover: none), (pointer: coarse) {
  .mo-card, .story-card:not(.sk-card), .show:not(.sk-card), .plan-card:not(.sk-card),
  .voice-card:not(.sk-card), #shelfCards .card:not(.sk-card),
  #rails .card:not(.sk-card), #searchGrid .card:not(.sk-card) {
    transition: transform .16s cubic-bezier(.22,1,.36,1);
  }
  .mo-card:active, .story-card:not(.sk-card):active, .show:not(.sk-card):active,
  .plan-card:not(.sk-card):active, .voice-card:not(.sk-card):active,
  #shelfCards .card:not(.sk-card):active, #rails .card:not(.sk-card):active,
  #searchGrid .card:not(.sk-card):active {
    transform: scale(.978);
  }
  .mo-press:active { transform: scale(.97); }
}

/* :where() has ZERO specificity, which is the whole point.
   This was previously `.mo-press { transition: ... }`, and `transition` is a
   shorthand — so it REPLACED each button's existing transition entirely. The
   homepage CTA declares `transition: transform .18s, border-color .18s,
   background .18s`; the old rule cut that down to transform alone, silently
   killing the background and border-color fades on hover.

   Wrapped in :where(), any component rule wins outright, so buttons that already
   describe their own transitions keep every property. Buttons that declare none
   still get a transform transition from here, which is all this ever needed to do. */
:where(.mo-press) { transition: transform .16s cubic-bezier(.22,1,.36,1); }

/* ---------------------------------------------------------------------------
   2. Audio player
   --------------------------------------------------------------------------- */

/* Play/pause glyph swap. The button's innerHTML is replaced by the page's own
   code; this softens the swap without touching that code. */
.mo-playbtn svg { transition: transform .18s cubic-bezier(.22,1,.36,1), opacity .18s linear; }
.mo-playbtn.mo-swap svg { transform: scale(.82); opacity: .55; }

/* ONE attention pulse, then stillness.

   index.html gives .daily-play an infinite animation in TWO separate rules —
   `bookGlow 3.5s ease-in-out infinite` and `pulseGlow 2.4s infinite`. Adding a
   one-shot class on top did not stop those: as soon as the class came off, the
   infinite animation underneath simply resumed, so the button pulsed forever.

   Both are cancelled here. !important is required because the page's own rules are
   equally specific and later in the cascade for the second one; the one-shot then
   wins over this because .daily-play.mo-pulse-once is more specific than
   .daily-play, and among !important declarations specificity still decides.

   Result: computed animation-name is `none` at rest, `moPulseOnce` for 1.15s the
   first time the button is seen, and `none` again afterwards — including after
   scrolling away and back, because site-motion.js disconnects its observer. */
.daily-play,
#dPlay {
  animation: none !important;
}

@keyframes moPulseOnce {
  0%   { transform: scale(1);    box-shadow: 0 0 0 0 rgba(245,197,90,.55); }
  55%  { transform: scale(1.06); box-shadow: 0 0 0 14px rgba(245,197,90,0); }
  100% { transform: scale(1);    box-shadow: 0 0 0 0 rgba(245,197,90,0); }
}
.daily-play.mo-pulse-once,
#dPlay.mo-pulse-once,
.mo-pulse-once {
  animation: moPulseOnce 1.15s cubic-bezier(.22,1,.36,1) 1 !important;
}

/* ---------------------------------------------------------------------------
   3. Smart Helper
   The sheet is anchored to the corner its button sits in (right on both LTR and
   RTL here, because .ai-help-fab is pinned right in ai-help.css), so it grows
   from that corner instead of from its own middle.
   --------------------------------------------------------------------------- */
.ai-help-sheet { transform-origin: 100% 100%; }

/* Closing. .ai-help-modal goes to display:none the instant `open` is removed, so
   an exit animation is only possible where `transition-behavior: allow-discrete`
   lets display be transitioned. Where it is not supported the panel simply closes
   at once, exactly as it does today — which is why this is inside @supports and
   why nothing depends on it.
   Deliberately NOT converted to visibility/opacity hiding: display:none is what
   currently keeps the closed panel out of the keyboard order, and an earlier
   accessibility fix depends on that. */
@supports (transition-behavior: allow-discrete) {
  @media (prefers-reduced-motion: no-preference) {
    .ai-help-modal {
      transition: opacity .17s ease, display .17s allow-discrete;
      opacity: 1;
    }
    .ai-help-modal:not(.open) { opacity: 0; }
    .ai-help-modal:not(.open) .ai-help-sheet { transform: scale(.97) translateY(6px); }
    .ai-help-sheet { transition: transform .17s cubic-bezier(.22,1,.36,1); }
  }
}

/* Crossfade between the helper's screens (help / report / idea / quick fix).
   Opacity only — the panes are toggled with an `on` class by ai-help.js and this
   never changes which one is shown, only how it arrives. */
.ai-help-pane, #aiHelpMenu, #aiHelpCompose, #aiHelpChat {
  transition: opacity .16s ease;
}

/* ---------------------------------------------------------------------------
   4. Cross-document page transitions
   @view-transition is the declarative form: the browser applies it to same-origin
   navigations only, so external links, Stripe and Supabase redirects, downloads
   and new-tab clicks are excluded by the platform rather than by a click handler
   of mine that could get the exclusions wrong. Back and forward keep working.
   Browsers without support ignore the whole at-rule.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  ::view-transition-old(root) { animation: moVtOut .18s cubic-bezier(.22,1,.36,1) both; }
  ::view-transition-new(root) { animation: moVtIn  .18s cubic-bezier(.22,1,.36,1) both; }
  @keyframes moVtOut { to { opacity: 0 } }
  @keyframes moVtIn  { from { opacity: 0 } }
}

/* ---------------------------------------------------------------------------
   5. Reduced motion
   The site already has a global reset that flattens CSS animations and
   transitions. That reset cannot reach inline styles written by JavaScript, so
   site-motion.js checks the same media query and skips animating entirely. What
   is left here removes the decorative pieces the reset does not cover.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .mo-card:hover { transform: none; }
  .mo-card:active, .mo-press:active { transform: none; }
  /* !important, because the one-shot itself is now !important (it has to outrank
     the page's two infinite .daily-play animations). site-motion.js also never
     adds the class for these visitors — this is the second line of defence. */
  .mo-pulse-once, .daily-play.mo-pulse-once, #dPlay.mo-pulse-once { animation: none !important; }
  .ai-help-sheet { transform: none !important; }
  /* No view transitions at all: the at-rule above is already inside a
     no-preference query, so nothing to undo. */
}

/* Decorative layers must never be announced or focusable. The hero's clouds,
   stars and glow are presentational; site-motion.js also sets aria-hidden on the
   ones it touches. */
.mo-decor { pointer-events: none; }

/* Hero off screen, or tab in the background: park its CSS loops rather than let
   the compositor keep drawing a starfield nobody can see. The class is added and
   removed by site-motion.js; without that file the hero animates exactly as it
   does today, because nothing here applies unless the class is present. */
.hero.mo-hero-idle .motion-cloud,
.hero.mo-hero-idle .cloud,
.hero.mo-hero-idle .twinkle,
.hero.mo-hero-idle .star,
.hero.mo-hero-idle .gold-dust,
.hero.mo-hero-idle .book-aura,
.hero.mo-hero-idle .moon {
  animation-play-state: paused;
}

/* Keyboard focus must stay obvious even mid-animation. Motion animates transform
   and opacity only, so an outline is never animated away — this just guarantees a
   ring on anything the polish touches. */
.mo-card:focus-visible, .mo-press:focus-visible, .mo-playbtn:focus-visible {
  outline: 3px solid #f5c55a;
  outline-offset: 3px;
}

/* Skeletons must never be treated as revealable content. site-motion.js checks
   this too; the rule is here so a skeleton can never inherit a stuck opacity. */
.sk, .sk-card, [aria-busy="true"] .sk, [aria-busy="true"] .sk-card {
  opacity: 1 !important;
  transform: none !important;
}
