/* =============================================================================
   STUDIO ELEVATE — the emotional layer  (studio-elevate.css)
   Fable, elevation pass. Loads LAST in studio-live.html / studio-live-demo.html
   so it composes on top of senova-ui → senova-ds → studio-live → studio-shell.

   Charter: first impression (ambient life) → exploration (onboarding, micro-
   delight) → creation (the bottom-docked composer as the studio's "bench") →
   reward (the develop-in reveal). Tokens only (--sn-*), both themes, every
   ambient/looping motion lives inside prefers-reduced-motion:no-preference,
   and everything long-running pauses under html[data-hidden] (set by JS on
   visibilitychange). Only transform / opacity / filter are animated.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   §0 MOTION VOCABULARY — three authored easings on top of the DS set.
   el-swift  : confident deceleration (iOS-sheet family) — panels, rises.
   el-spring : one polite overshoot — chips, menus, toasts. Never wobbles twice.
   el-reveal : long luxurious tail — the generation bloom only.
   -------------------------------------------------------------------------- */
:root {
  --el-swift:  cubic-bezier(.32,.72,0,1);
  --el-spring: cubic-bezier(.34,1.45,.4,1);
  --el-reveal: cubic-bezier(.22,.9,.24,1);
}

/* global finish: selection + thin themed scrollbars — the "expensive object"
   details you only notice when they're missing. */
::selection { background: color-mix(in srgb, var(--sn-accent) 24%, transparent); }
* { scrollbar-width: thin; scrollbar-color: var(--sn-hairline-strong) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--sn-hairline-strong); border-radius: var(--sn-r-input); border: 2.5px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background-color: var(--sn-ink-3); border: 2.5px solid transparent; background-clip: padding-box; }

/* ---------------------------------------------------------------------------
   §1 AMBIENT BACKGROUND LIFE
   Markup (first child of <body>, before .app):
     <div id="studio-ambient" aria-hidden="true"></div>
   Two vast, soft light-fields drift past each other on 52s/76s alternating
   loops — far below attention threshold, but the room never feels dead.
   GPU-cheap: two composited pseudos animating transform only; softness is
   baked into gradient stops (no filter). Paused when the tab hides.
   -------------------------------------------------------------------------- */
#studio-ambient {
  position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
  contain: strict;
  /* the still base: a faint ember crown + the quietest vignette */
  background:
    radial-gradient(46% 30% at 50% 0%, var(--sn-accent-glow), transparent 76%),
    radial-gradient(130% 90% at 50% 108%, color-mix(in srgb, var(--sn-bg-deep) 14%, transparent), transparent 62%);
}
#studio-ambient::before, #studio-ambient::after {
  content: ""; position: absolute; border-radius: 50%; will-change: transform;
}
/* the ember field — warmth, upper left, breathing across the canvas */
#studio-ambient::before {
  left: -8%; top: -16%; width: 58vmax; height: 58vmax;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--sn-accent) 9%, transparent), transparent 70%);
}
/* the counter-light — in studio theme this is a cool ivory lift, in light
   theme a soft shade: the same rule reads correctly in both worlds. */
#studio-ambient::after {
  right: -12%; bottom: -20%; width: 72vmax; height: 72vmax;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--sn-ink) 4%, transparent), transparent 72%);
}
[data-theme="light"] #studio-ambient { opacity: .55; }

@media (prefers-reduced-motion: no-preference) {
  #studio-ambient::before { animation: el-drift-a 52s ease-in-out infinite alternate; }
  #studio-ambient::after  { animation: el-drift-b 76s ease-in-out infinite alternate; }
}
@keyframes el-drift-a { from { transform: translate3d(-4%,-2%,0) scale(1); }    to { transform: translate3d(5%,4%,0)  scale(1.12); } }
@keyframes el-drift-b { from { transform: translate3d(3%,3%,0)  scale(1.08); }  to { transform: translate3d(-4%,-3%,0) scale(1); } }

/* the app sits above the ambient; transparent regions (the stage) let it
   breathe through. */
.app { position: relative; z-index: 1; }

/* the rail becomes translucent glass so the ambient gives it a living edge */
.side {
  background: color-mix(in srgb, var(--sn-sidebar-bg) 86%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}

/* the create hero band lets a measured amount of ambient through, and gets
   its own slow-breathing ember behind the composer — the hearth. */
#stage .gen:not(.gen-chat) { position: relative; overflow: hidden;
  background: color-mix(in srgb, var(--sn-bg-deep) 86%, transparent); }
[data-theme="light"] #stage .gen:not(.gen-chat) {
  background: linear-gradient(180deg, color-mix(in srgb, #F4F2EC 78%, transparent), color-mix(in srgb, var(--sn-bg) 30%, transparent)); }
#stage .gen:not(.gen-chat)::before {
  content: ""; position: absolute; left: 50%; top: -34%; width: min(820px, 86%);
  aspect-ratio: 1.55; translate: -50% 0; border-radius: 50%; pointer-events: none;
  background: radial-gradient(closest-side, var(--sn-accent-glow), transparent 72%);
}
#stage .gen:not(.gen-chat) > .gen-inner { position: relative; z-index: 1; }
@media (prefers-reduced-motion: no-preference) {
  #stage .gen:not(.gen-chat)::before { animation: el-breathe 9s ease-in-out infinite alternate; will-change: transform, opacity; }
}
@keyframes el-breathe { from { opacity: .68; transform: scale(1); } to { opacity: 1; transform: scale(1.07); } }

/* ---------------------------------------------------------------------------
   §2 ROUTE-ENTER CHOREOGRAPHY — a view doesn't "load", it settles into place:
   headline, then subline, then the instrument, then the shelf. 40–110ms steps,
   one easing family — reads as intention, not as animation.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  #stage .gen :is(h1, .gen-head h1)          { animation: el-rise .5s  var(--el-swift) both; }
  #stage .gen :is(.sub, .gen-head .sub)      { animation: el-rise .5s  var(--el-swift) 50ms both; }
  /* NOTE: never animate #sl-composer itself — rerenderComposerBar() replaces
     that node on every control change and an entrance would replay. The dock
     wrapper (stable across re-renders) carries the entrance instead. */
  #stage .gen-chat .gen-dock                 { animation: el-rise .55s var(--el-swift) 100ms both; }
  #stage > .wrap .view-head,
  #stage .wrap > .sec-head                   { animation: el-rise .45s var(--el-swift) both; }

  /* shelf grids that render ONCE per visit (never the live #sl-results reel —
     those nodes are re-rendered by the poll and must not re-enter). */
  :is(#sl-recent, #sl-lib, #sl-rec) > figure.card,
  :is(#sl-recent, #sl-lib, #sl-rec) > .recipe-card,
  .onboard > .onboard-card {
    animation: el-rise .5s var(--el-swift) both;
  }
  :is(#sl-recent, #sl-lib, #sl-rec, .onboard) > :nth-child(1) { animation-delay: 40ms; }
  :is(#sl-recent, #sl-lib, #sl-rec, .onboard) > :nth-child(2) { animation-delay: 80ms; }
  :is(#sl-recent, #sl-lib, #sl-rec, .onboard) > :nth-child(3) { animation-delay: 120ms; }
  :is(#sl-recent, #sl-lib, #sl-rec, .onboard) > :nth-child(4) { animation-delay: 160ms; }
  :is(#sl-recent, #sl-lib, #sl-rec) > :nth-child(5) { animation-delay: 200ms; }
  :is(#sl-recent, #sl-lib, #sl-rec) > :nth-child(6) { animation-delay: 235ms; }
  :is(#sl-recent, #sl-lib, #sl-rec) > :nth-child(7) { animation-delay: 265ms; }
  :is(#sl-recent, #sl-lib, #sl-rec) > :nth-child(n+8) { animation-delay: 290ms; }
}
@keyframes el-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ---------------------------------------------------------------------------
   §3 THE REVEAL — the moment of creation. JS adds .is-revealing to the fresh
   figure.card when its job resolves (and removes it after ~1.4s).
   Three voices, one chord: the card settles (700ms), the image develops from
   blur like a print in the bath (1050ms), and a single ember-tinted light
   sweep passes once (900ms, delayed 260ms). Never loops, never repeats.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  figure.card.is-revealing {
    animation: el-reveal-card .7s var(--el-swift) both;
  }
  figure.card.is-revealing .media > img,
  figure.card.is-revealing .media > video {
    animation: el-reveal-media 1.05s var(--el-reveal) both;
    will-change: filter, transform;
  }
  figure.card.is-revealing .media { position: relative; }
  figure.card.is-revealing .media::after {
    content: ""; position: absolute; top: -12%; bottom: -12%; left: 0; width: 55%;
    background: linear-gradient(100deg, transparent 8%,
      color-mix(in srgb, #FFFFFF 13%, transparent) 45%,
      var(--sn-accent-glow) 58%, transparent 92%);
    animation: el-reveal-sweep .9s var(--el-swift) 260ms both;
    pointer-events: none;
  }
}
@keyframes el-reveal-card {
  0%   { opacity: 0; transform: scale(.965) translateY(5px); }
  55%  { opacity: 1; }
  100% { opacity: 1; transform: none; }
}
@keyframes el-reveal-media {
  0%   { filter: blur(18px) saturate(1.35); transform: scale(1.06); }
  55%  { filter: blur(5px)  saturate(1.15); }
  100% { filter: blur(0)    saturate(1);    transform: scale(1); }
}
@keyframes el-reveal-sweep {
  0%   { transform: translateX(-130%) skewX(-14deg); opacity: 0; }
  16%  { opacity: 1; }
  100% { transform: translateX(230%) skewX(-14deg); opacity: 0; }
}

/* pending, refined: the working card holds a soft ember heartbeat under its
   percent label and a quiet glow on the ring — patient, not anxious. */
figure.card.is-pending .spin { box-shadow: 0 0 22px var(--sn-accent-glow); }
@media (prefers-reduced-motion: no-preference) {
  figure.card.is-pending .pending-in .lbl { animation: el-pending-breathe 2.4s ease-in-out infinite; }
}
@keyframes el-pending-breathe { 0%,100% { opacity: .55; } 50% { opacity: 1; } }

/* ---------------------------------------------------------------------------
   §4 THE BOTTOM-DOCKED COMPOSER — .gen.gen-chat (home / Generate surface).
   The instrument moves to where the hands rest: pinned low, content flows
   above it, a gradient scrim keeps scrolling work legible beneath it, and a
   faint ember under-glow marks it as the live surface. Composer INTERNALS are
   untouched — #sl-composer / #cm-* keep their exact markup and bindings.
   -------------------------------------------------------------------------- */
#stage .gen.gen-chat {
  display: flex; flex-direction: column;
  background: transparent; border-bottom: none;
  min-height: calc(100vh - 57px);
  min-height: calc(100dvh - 57px);   /* topbar 56px + hairline */
}
.gen-chat .gen-scroll { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; }
.gen-chat .gen-head { padding: 46px 28px 0; text-align: center; }
/* §63: "What will you make?" — the studio's biggest headline, now in the studio voice. */
.gen-chat .gen-head h1 { font: 600 clamp(1.6rem, 1.2rem + 1.4vw, 2.125rem)/1.15 var(--sn-sans); letter-spacing: -.022em; margin: 0 0 6px; color: var(--sn-ink); }
.gen-chat .gen-head .sub { color: var(--sn-ink-2); font-size: .9375rem; margin: 0; }
/* reserve exactly the sticky dock's height so no media ever hides behind the composer (JS sets --gen-dock-h) */
.gen-chat .gen-feed { width: 100%; padding-bottom: calc(var(--gen-dock-h, 200px) + 28px); }

.gen-chat .gen-dock {
  position: sticky; bottom: 0; z-index: 60; margin-top: auto;
  padding: 6px 28px calc(18px + env(safe-area-inset-bottom, 0px));
}
/* the scrim: work scrolls up INTO light, never under a hard edge */
.gen-chat .gen-dock::before {
  content: ""; position: absolute; left: 0; right: 0; top: -52px; bottom: 0; z-index: -1;
  background: linear-gradient(180deg, transparent,
    color-mix(in srgb, var(--sn-bg) 72%, transparent) 40%,
    var(--sn-bg) 86%);
  pointer-events: none;
}
.gen-chat .gen-dock-inner { position: relative; max-width: 760px; margin: 0 auto; }
/* the hearth under the bench */
.gen-chat .gen-dock-inner::after {
  content: ""; position: absolute; left: 8%; right: 8%; bottom: -26px; height: 60px;
  border-radius: 50%; background: radial-gradient(closest-side, var(--sn-accent-glow), transparent 78%);
  pointer-events: none; z-index: -1;
}
@media (prefers-reduced-motion: no-preference) {
  .gen-chat .gen-dock-inner::after { animation: el-breathe 8s ease-in-out infinite alternate; }
}
/* the docked composer floats a full tier above the page */
.gen-chat #sl-composer { border-radius: var(--sn-r-panel); box-shadow: var(--sn-elev-3); }
.gen-chat #sl-composer:focus-within {
  box-shadow: var(--sn-elev-3), 0 0 0 3px var(--sn-accent-glow);
}
/* seeded flash — JS adds .is-seeded when an onboarding card fills the prompt */
@media (prefers-reduced-motion: no-preference) {
  #sl-composer.is-seeded { animation: el-seed .8s var(--el-swift); }
}
@keyframes el-seed {
  0%   { box-shadow: var(--sn-elev-3), 0 0 0 0    color-mix(in srgb, var(--sn-accent) 45%, transparent); }
  100% { box-shadow: var(--sn-elev-3), 0 0 0 16px transparent; }
}
@media (max-width: 640px) {
  .gen-chat .gen-dock { padding-left: 12px; padding-right: 12px; }
  .gen-chat .gen-head { padding-top: 30px; }
}

/* ---------------------------------------------------------------------------
   §5 ONBOARDING — "here's what you can make." A quiet 4-up rail of real work,
   each card seeding the composer on click. Editorial, not tutorial: media
   leads, one line of how, an arrow that answers the hover.
   Markup (see implementation spec):
     <div class="onboard" id="sl-onboard">
       <button class="onboard-card" data-ob="0">
         <span class="ob-media"><img src="…" alt=""><span class="ob-kind">Image</span>
           <span class="ob-cta">Try this <span class="sn-ic-slot" data-ic="chevronRight"></span></span></span>
         <span class="ob-tx"><span class="ob-title">Product shot</span>
           <span class="ob-hint">Name the object, the surface, the light.</span></span>
       </button> ×4
     </div>
   -------------------------------------------------------------------------- */
.onboard {
  display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px;
  width: min(1040px, calc(100% - 56px)); margin: 22px auto 6px;
}
.onboard-card {
  display: block; text-align: left; cursor: pointer; padding: 7px 7px 11px;
  background: var(--sn-surface); border: 1px solid var(--sn-hairline); border-radius: var(--sn-r-card);
  box-shadow: var(--sn-elev-1); -webkit-user-select: none; user-select: none;
  transition: transform .32s var(--el-swift), box-shadow .32s var(--el-swift), border-color .2s var(--sn-ease);
}
.onboard-card:hover { transform: translateY(-3px); box-shadow: var(--sn-elev-2); border-color: var(--sn-hairline-strong); }
.onboard-card:active { transform: translateY(-1px) scale(.99); }
.onboard-card:focus-visible { outline: none; box-shadow: var(--sn-elev-2), var(--sn-ring); }

.ob-media { display: block; position: relative; aspect-ratio: 16/10; border-radius: var(--sn-r-input); overflow: hidden;
  background: linear-gradient(140deg, var(--sn-bg-raised), var(--sn-chip-bg)); }
.ob-media > img, .ob-media > video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .6s var(--el-swift);
}
.onboard-card:hover .ob-media > img, .onboard-card:hover .ob-media > video { transform: scale(1.05); }
/* glass kind-chip, same dialect as .badge-type */
.ob-kind { position: absolute; top: 7px; left: 7px; height: 18px; padding: 0 7px; border-radius: 999px;
  background: rgba(10,9,8,.55); backdrop-filter: blur(6px); color: #F5F4EF;
  font: 600 .5625rem/18px var(--sn-mono); letter-spacing: .06em; text-transform: uppercase; }
/* the invitation appears only when asked for */
.ob-cta { position: absolute; right: 7px; bottom: 7px; display: inline-flex; align-items: center; gap: 4px;
  height: 22px; padding: 0 9px; border-radius: 999px;
  background: rgba(10,9,8,.6); backdrop-filter: blur(6px); color: #F5F4EF;
  font: 600 .6875rem/1 var(--sn-sans);
  opacity: 0; transform: translateY(4px);
  transition: opacity .22s var(--sn-ease), transform .3s var(--el-swift); }
.ob-cta .sn-ic, .ob-cta .sn-ic-slot { width: 11px; height: 11px; }
.onboard-card:hover .ob-cta, .onboard-card:focus-visible .ob-cta { opacity: 1; transform: none; }

.ob-tx { display: block; padding: 10px 6px 0; }
.ob-title { display: block; font: 600 .875rem/1.25 var(--sn-sans); color: var(--sn-ink); }
.ob-hint { display: block; margin-top: 3px; font: .75rem/1.45 var(--sn-sans); color: var(--sn-ink-3); }

@media (max-width: 1000px) { .onboard { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 640px) {
  .onboard { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; gap: 10px;
    width: auto; margin: 18px 0 6px; padding: 0 12px 6px; scrollbar-width: none; }
  .onboard::-webkit-scrollbar { display: none; }
  .onboard-card { flex: 0 0 70%; scroll-snap-align: center; }
}

/* ---------------------------------------------------------------------------
   §6 MICRO-INTERACTIONS — the pervasive pass. One rule: motion answers the
   hand. Nothing moves that the user didn't cause (ambient §1 excepted).
   -------------------------------------------------------------------------- */

/* 6.1 accent buttons — hover lifts and warms; press lands. The glow is a
   shadow of the ember, not a neon ring. */
.sn-btn-accent, .cmp-gen.sn-btn-accent {
  transition: background var(--sn-t-hover) var(--sn-ease), border-color var(--sn-t-hover) var(--sn-ease),
    color var(--sn-t-hover) var(--sn-ease), transform .24s var(--el-swift), box-shadow .24s var(--el-swift);
}
.sn-btn-accent:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 20px -6px color-mix(in srgb, var(--sn-accent) 55%, transparent);
}
.sn-btn-accent:active { transform: translateY(0) scale(.98); box-shadow: 0 2px 8px -4px color-mix(in srgb, var(--sn-accent) 45%, transparent); }
.cmp-bar .cmp-gen:hover { box-shadow: 0 8px 26px -8px color-mix(in srgb, var(--sn-accent) 62%, transparent); }
/* primaries and secondaries get the settle without the glow */
.sn-btn-primary, .sn-btn-secondary { transition: background var(--sn-t-hover) var(--sn-ease), border-color var(--sn-t-hover) var(--sn-ease),
  color var(--sn-t-hover) var(--sn-ease), opacity var(--sn-t-hover) var(--sn-ease), transform .2s var(--el-swift), box-shadow .2s var(--el-swift); }
.sn-btn-primary:hover, .sn-btn-secondary:hover { transform: translateY(-.5px); }
.sn-btn-primary:active, .sn-btn-secondary:active { transform: scale(.98); }

/* 6.2 nav — the active mark blooms in (and visibly hands off between items,
   because both transitions run); icons get a soft spring on hover. */
.sn-nav-item { position: relative; transition: background var(--sn-t-hover) var(--sn-ease),
  color var(--sn-t-hover) var(--sn-ease), transform .18s var(--el-swift); }
.sn-nav-item:active { transform: scale(.985); }
.sn-nav-item::before {
  content: ""; position: absolute; left: -3px; top: 50%; translate: 0 -50%;
  width: 2.5px; height: 15px; border-radius: 2px; background: var(--sn-accent);
  transform: scaleY(0); transition: transform .34s var(--el-spring);
}
.sn-nav-item.is-active::before { transform: scaleY(1); }
.sn-nav-item .sn-ic-slot { transition: transform .3s var(--el-spring); }
.sn-nav-item:hover .sn-ic-slot { transform: translateY(-1px) scale(1.07); }
.sn-nav-item.is-active .sn-ic-slot { transform: none; }

/* 6.3 segmented controls — the active pill fades/settles across (background +
   shadow transition reads as a thumb-slide), the press is tactile. */
.sn-seg button {
  transition: color var(--sn-t-hover) var(--sn-ease), background .2s var(--el-swift),
    box-shadow .2s var(--el-swift), transform .12s var(--el-swift);
}
.sn-seg button:active { transform: scale(.94); }

/* 6.4 menus — spring-in from the anchor edge, rows arriving in a 20ms cascade
   (capped at 8 so long galleries never feel slow). */
.sn-menu { transform-origin: top left; }
@media (prefers-reduced-motion: no-preference) {
  .sn-menu { animation: el-menu-in .3s var(--el-spring); }
  .sn-menu.model-menu .sn-menu-item { animation: el-menu-item .3s var(--el-swift) 150ms backwards; }
  .sn-menu.model-menu .sn-menu-item:nth-child(1) { animation-delay: 20ms; }
  .sn-menu.model-menu .sn-menu-item:nth-child(2) { animation-delay: 40ms; }
  .sn-menu.model-menu .sn-menu-item:nth-child(3) { animation-delay: 60ms; }
  .sn-menu.model-menu .sn-menu-item:nth-child(4) { animation-delay: 80ms; }
  .sn-menu.model-menu .sn-menu-item:nth-child(5) { animation-delay: 100ms; }
  .sn-menu.model-menu .sn-menu-item:nth-child(6) { animation-delay: 115ms; }
  .sn-menu.model-menu .sn-menu-item:nth-child(7) { animation-delay: 130ms; }
  .sn-menu.model-menu .sn-menu-item:nth-child(8) { animation-delay: 145ms; }
}
@keyframes el-menu-in   { from { opacity: 0; transform: translateY(-5px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes el-menu-item { from { opacity: 0; transform: translateY(4px); }            to { opacity: 1; transform: none; } }

/* 6.5 result cards — a single glass sheen that answers the hover (the lift and
   image-scale live in studio-live §4; this only adds light). */
figure.card:not(.is-pending):not(.is-error):not(.is-skel):not(.is-audio)::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  background: linear-gradient(115deg, transparent 42%, rgba(255,255,255,.055) 50%, transparent 58%);
  opacity: 0; transition: opacity .35s var(--sn-ease);
}
figure.card:not(.is-pending):not(.is-error):not(.is-skel):not(.is-audio):hover::after { opacity: 1; }

/* 6.6 the credit chip — JS re-adds .is-tick when the balance changes; the
   count acknowledges the spend with one pulse and an ember ring. */
#sl-credits { transition: transform .2s var(--el-swift); }
@media (prefers-reduced-motion: no-preference) {
  #sl-credits.is-tick { animation: el-credit .65s var(--el-spring); }
}
@keyframes el-credit {
  0%   { transform: scale(1);    box-shadow: 0 0 0 0 color-mix(in srgb, var(--sn-accent) 40%, transparent); }
  30%  { transform: scale(1.12); }
  100% { transform: scale(1);    box-shadow: 0 0 0 10px transparent; }
}

/* 6.7 overlays — the toast and modal join the spring family. */
@media (prefers-reduced-motion: no-preference) {
  .sn-toast { animation: el-toast-in .48s var(--el-spring); }
  .sn-modal { animation: el-modal-in .36s var(--el-spring); }
  .pv-lb .box { animation: el-lb-in .42s var(--el-swift); }
}
@keyframes el-toast-in { from { opacity: 0; transform: translateY(16px) scale(.96); } to { opacity: 1; transform: none; } }
@keyframes el-modal-in { from { opacity: 0; transform: translateY(12px) scale(.965); } to { opacity: 1; transform: none; } }
@keyframes el-lb-in    { from { opacity: 0; transform: scale(.965); } to { opacity: 1; transform: none; } }

/* 6.8 the rail toggle + avatar — small acknowledgements. */
.rail-toggle .rail-ic-wrap { transition: transform .3s var(--el-spring); }
.rail-toggle:hover .rail-ic-wrap { transform: scale(1.08); }
.sl-avatar { transition: border-color var(--sn-t-hover) var(--sn-ease), transform .2s var(--el-spring); }
.sl-avatar:hover { transform: scale(1.05); }
.sl-avatar:active { transform: scale(.96); }
/* collapsed labels drift out, not just fade */
.side .nav-lb { transition: opacity .2s var(--sn-ease), max-width .28s var(--sn-ease), transform .28s var(--el-swift); }
html[data-rail="collapsed"] .side .nav-lb { transform: translateX(-6px); }

/* 6.9 focus — rings bloom instead of snapping (box-shadow is already in each
   element's transition list; this adds the soft ramp where it was missing).
   No outlines are removed here — existing focus affordances stay intact. */
.sn-input, .sn-textarea, .sn-select { transition: border-color var(--sn-t-hover) var(--sn-ease), box-shadow .22s var(--el-swift); }

/* ---------------------------------------------------------------------------
   §7 TAB HIDDEN — the app sets html[data-hidden] on visibilitychange; every
   long-running loop sleeps so a background tab costs nothing.
   -------------------------------------------------------------------------- */
html[data-hidden] #studio-ambient::before,
html[data-hidden] #studio-ambient::after,
html[data-hidden] #stage .gen:not(.gen-chat)::before,
html[data-hidden] .gen-chat .gen-dock-inner::after,
html[data-hidden] figure.card.is-pending .media::before,
html[data-hidden] figure.card.is-pending .pending-in .lbl,
html[data-hidden] figure.card.is-skel .media,
html[data-hidden] .card .spin,
html[data-hidden] .loading-full .spin {
  animation-play-state: paused !important;
}

/* ---------------------------------------------------------------------------
   §8 REDUCED MOTION — belt + braces. Everything keyframed above already lives
   inside no-preference blocks; this kills the transition-driven transforms so
   reduce means STILL, not slower.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  #studio-ambient::before, #studio-ambient::after,
  #stage .gen:not(.gen-chat)::before, .gen-chat .gen-dock-inner::after { animation: none !important; }
  figure.card.is-revealing, figure.card.is-revealing .media > img,
  figure.card.is-revealing .media > video { animation: none !important; filter: none !important; }
  figure.card.is-revealing .media::after { content: none !important; }
  .sn-btn-accent:hover, .sn-btn-primary:hover, .sn-btn-secondary:hover,
  .onboard-card:hover, .sl-avatar:hover, .sn-nav-item:hover .sn-ic-slot,
  .rail-toggle:hover .rail-ic-wrap { transform: none; }
  .onboard-card, .onboard-card .ob-media > img, .onboard-card .ob-media > video,
  .ob-cta, .sn-nav-item, .sn-nav-item::before, .sn-nav-item .sn-ic-slot,
  .sn-seg button, .side .nav-lb, .sl-avatar, .rail-toggle .rail-ic-wrap,
  #sl-credits { transition: none; }
  .onboard-card:hover .ob-media > img, .onboard-card:hover .ob-media > video { transform: none; }
  .ob-cta { opacity: 1; transform: none; }   /* the affordance must not vanish */
  html[data-rail="collapsed"] .side .nav-lb { transform: none; }
}
