/* GifLab · SITE layer — loaded ONLY on the web version (ctlab.app/giflab), AFTER css/style.css.
   OWNED BY THE ctlab-global SITE CHAT. Kept in its own file so GifLab's own index.html / style.css /
   app.js stay a pure tool that the GifLab chat can mirror without ever clobbering the site chrome.
   Pairs with js/giflab-site.js. See CTLAB_PROJECT.md for the ownership split. */

/* transparent body → the shared #bg WebGL shader shows through (GifLab's own opaque body hid it). */
body { background: transparent; }

/* Hide the site-chrome blocks until the reveal runs. Keyed on DIRECT selectors (not .gfx-init) so it
   applies from the FIRST paint — the .gfx-init classes are added by giflab-site.js, which runs after
   first paint, so gating the hide on them would flash the chrome visible-then-hidden. Skipped in the
   packaged desktop app (no site reveal there). */
html:not(.desktop-app) body:not(.gfx-on) nav,
html:not(.desktop-app) body:not(.gfx-on) .stage,
html:not(.desktop-app) body:not(.gfx-on) .preview-col,
html:not(.desktop-app) body:not(.gfx-on) .controls,
html:not(.desktop-app) body:not(.gfx-on) .site-foot { opacity: 0; }
/* the whole tool area fades in as one when the reveal fires (its inner blocks still fly in on top) — so on
   arrival the page is just the shader bg + the loader, then the interface appears over it. */
html:not(.desktop-app) .stage { transition: opacity .5s ease; }

/* Staggered fly-in on load + fly-out on nav-away — same keyframes/easing as the rest of ctlab.app.
   The cascade is CSS animation-delay (not a JS stagger) so it can't be starved by the tool's own
   boot work parsing on the main thread right after paint. */
.gfx-init { will-change: opacity, transform; }
.gfx-init.in { animation: gfxIn .62s cubic-bezier(.16,1,.3,1) backwards; }
nav.gfx-init.in          { animation-delay: 0s; }
.preview-col.gfx-init.in { animation-delay: .09s; }
.controls.gfx-init.in    { animation-delay: .18s; }
.site-foot.gfx-init.in   { animation-delay: .27s; }
.gfx-init.out { animation: gfxOutRise .6s cubic-bezier(.25,.46,.45,.94) forwards,
                           gfxOutFade .32s cubic-bezier(.4,0,1,1) forwards; }
@keyframes gfxIn      { from { opacity:0; transform:translateY(26px) } to { opacity:1; transform:none } }
@keyframes gfxOutRise { from { transform:translateY(0) }              to { transform:translateY(-46px) } }
@keyframes gfxOutFade { from { opacity:1 }                            to { opacity:0 } }
/* Motion is ALWAYS ON — no prefers-reduced-motion gate (owner decision 2026-07-05, WEB_ANIMATIONS.md). */

/* Signed-in nav account menu — mirror of the main site's .acct-* (assets/styles.css); lives here because
   GifLab runs its own bundle without assets/styles.css. Dark values are HARDCODED (not var() tokens) so the
   menu is immune to GifLab's own token set. Keep visually in sync with assets/styles.css .acct-* rules. */
.acct-nav{position:relative;display:inline-flex}
.acct-btn{display:inline-flex;align-items:center;gap:8px;background:#1a1a1a;border:1px solid rgba(255,255,255,0.07);border-radius:100px;padding:4px 11px 4px 4px;cursor:pointer;color:#f0f0f0;font-family:inherit;font-size:13px;line-height:1;transition:border-color .2s,background .2s}
.acct-btn:hover{border-color:rgba(255,255,255,0.16);background:#222}
.acct-av{width:24px;height:24px;border-radius:50%;background:#e8e0d0;color:#0a0a0a;font-size:11px;font-weight:700;display:grid;place-items:center;flex:none}
.acct-nm{max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.acct-cv{width:14px;height:14px;color:#777;transition:transform .2s cubic-bezier(.16,1,.3,1);flex:none}
.acct-nav.open .acct-cv{transform:rotate(180deg)}
.acct-menu{position:absolute;top:calc(100% + 9px);right:0;min-width:210px;background:#111;border:1px solid rgba(255,255,255,0.07);border-radius:12px;padding:6px;box-shadow:0 20px 54px -12px rgba(0,0,0,.6);opacity:0;visibility:hidden;transform:translateY(-6px);transition:opacity .2s cubic-bezier(.16,1,.3,1),transform .2s cubic-bezier(.16,1,.3,1),visibility .2s;z-index:200}
.acct-nav.open .acct-menu{opacity:1;visibility:visible;transform:none}
.acct-uinfo{display:flex;flex-direction:column;gap:2px;padding:8px 10px 10px;margin-bottom:6px;border-bottom:1px solid rgba(255,255,255,0.07)}
.acct-uinfo b{font-size:13px;font-weight:600;color:#f0f0f0}
.acct-uinfo span{font-size:12px;color:#777;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.acct-menu a,.acct-menu .acct-out{display:block;width:100%;text-align:left;padding:8px 10px;border-radius:8px;font-size:13px;color:#777;background:none;border:0;cursor:pointer;font-family:inherit;text-decoration:none;transition:background .15s,color .15s}
.acct-menu a:hover{background:#1a1a1a;color:#f0f0f0}
.acct-menu .acct-out{color:#d98077}
.acct-menu .acct-out:hover{background:rgba(217,128,119,.12);color:#e79b93}

/* Arrival loader — instead of animating a transition across the page boundary (View Transitions kept
   flashing), the GifLab page arrives showing the SAME dark shader background + this spinner, then the
   interface reveals over it (giflab-site.js delays gfx-on by ~0.8s so the loader is seen). Because both
   the departure page and this one show the shader, the background reads as continuous; the loader makes
   the app-boot an intentional moment instead of a jarring flash. Renders from FIRST paint (this CSS is in
   <head>) and fades out when the reveal fires. Not shown in the packaged desktop app. */
html:not(.desktop-app) body::after{
  content:''; position:fixed; top:50%; left:50%; width:34px; height:34px; margin:-17px 0 0 -17px;
  border:3px solid rgba(255,255,255,.14); border-top-color:#c44dff; border-radius:50%;
  z-index:400; pointer-events:none; animation:glSpin .8s linear infinite;
  opacity:1; transition:opacity .45s ease;
}
html:not(.desktop-app) body.gfx-on::after{ opacity:0 }
@keyframes glSpin{ to{ transform:rotate(360deg) } }

/* Arrival black overlay — GifLab arrives fully black (covers the app boot + continues the black the
   departing page faded to → the fade-through-black hop has no flicker), then this fades out to reveal the
   shader bg while the spinner turns (spinner z-index 400 sits ABOVE this at z 398), then the interface
   reveals. giflab-site.js adds .nvf-up to fade it; also cleared on .gfx-on as a failsafe so it can never
   stick. Not in the packaged desktop app. */
html:not(.desktop-app) body::before{
  content:''; position:fixed; inset:0; background:#000; z-index:398; pointer-events:none;
  opacity:1; transition:opacity .55s ease;
}
html:not(.desktop-app) body.nvf-up::before,
html:not(.desktop-app) body.gfx-on::before{ opacity:0 }
/* THE flicker fix: the #bg shader painted for ~2 frames (luma 27) BEFORE the black overlay composited — a
   visible lightening at the seam. Keep the shader hidden until the arrival lift (.nvf-up), so the very first
   painted frame is flat near-black (html #0a0a0a), with nothing bright to flash; then the shader fades in as
   the black lifts. Failsafe: also shown on .gfx-on. */
html:not(.desktop-app) #bg{ transition:opacity .55s ease }
html:not(.desktop-app) body:not(.nvf-up):not(.gfx-on) #bg{ opacity:0 }

/* ===== Empty-landing scroll — SITE-LAYER override of the tool's app-like block =====
   The tool's css/style.css makes the empty landing a FIXED 100dvh−76px stage frame with the controls
   scrolling internally. Problem (owner, screenshot): in EASY mode the controls collapse to a few groups and
   the preview drop-zone is capped ~560px, so both columns are far shorter than the frame — leaving a big dead
   gap (~600px on a tall screen) with the footer parked a whole viewport down. Fix: drop the fixed frame so the
   stage is CONTENT height (the footer follows the content up — short in Easy, tall in Advanced), and PIN the
   preview with position:sticky so the app-like "wheel moves the left tools, preview stays put, footer slides
   up" feel is kept when the controls run tall. Lives HERE (loaded after style.css) rather than editing the
   tool's file, so it wins the cascade and survives GifLab's mirror overwrites. Desktop widths only.
   Scoped to ALL of body.editing (empty AND loaded-clip) on web — pairs with GifLab's style.css unlock
   (also extended from .no-clip to body.editing). Keeping ONE scrollable+sticky layout for both states means
   dropping a clip no longer switches layout modes, so the preview stops "flying" on load (owner bug). Must
   deploy in lockstep with that style.css change. Packaged desktop app (?app=1) still untouched via :not(.desktop-app). */
@media (min-width:981px){
  html:not(.desktop-app) body.editing .stage{ flex:none; height:auto; overflow:visible }
  html:not(.desktop-app) body.editing .edit-view{ height:auto }
  html:not(.desktop-app) body.editing .editor{ height:auto; grid-template-rows:auto; align-items:start }
  html:not(.desktop-app) body.editing .controls{ max-height:none; overflow:visible; padding-right:0; position:static }
  /* top MUST equal the preview's TRUE rest offset = the editor content top (108px — the exact line the left
     controls / the Easy·Advanced pill sit on). Pinning AT rest means the preview is already stuck at scroll 0
     → zero upward drift when scrolling starts, AND its top edge stays level with the toggle pill.
     Do NOT set top larger than the rest: sticky then shoves the preview DOWN to `top`, and that shove differs
     between Easy and Advanced → the preview visibly jumps between modes (owner bug). (An earlier 134 here was a
     misread — the headless preview freezes the gfxIn reveal at translateY(26px), inflating every position read
     by 26; the real rest is 108.) Keep this value == the controls' top offset. */
  html:not(.desktop-app) body.editing .preview-col{ position:sticky; top:108px; align-self:start }
}
/* Now that the landing scrolls as a normal page (above), the native vertical scrollbar appears the moment
   the content grows past the viewport — eating ~15px of width and shoving the centered layout sideways as it
   toggles (owner: "появляется скролбар и прыгает весь интерфейс влево"). Reserve the scrollbar gutter
   PERMANENTLY so the layout width is constant whether or not the scrollbar is shown → zero horizontal jump.
   Applies to the whole web tool now (empty AND loaded-clip — both scroll as one page since the mode no
   longer switches) via :has(body.editing); the packaged desktop app keeps its edge-to-edge shader. */
html:not(.desktop-app):has(body.editing){ scrollbar-gutter: stable }

/* Footer copyright ("© 2026 …") — centre it at the very bottom on EVERY width (owner: it looked crooked;
   the tool's style.css yanks it position:absolute top-right ≥1100px and right/left-aligns it otherwise).
   Undo the absolute yank, keep it a normal-flow centred bottom bar matching the main-site footer. Site
   layer loads after style.css → wins the cascade; single owner for this footer bit lives here. */
.foot-bottom{ justify-content:center }
@media (min-width:1100px){
  .foot-bottom{ position:static; top:auto; right:auto; margin:30px auto 0; max-width:1060px; padding-top:20px; border-top:1px solid var(--border) }
}

/* Hide the footer "Suggest a feature" trigger (owner, confirmed): it becomes a GENERAL cross-product
   feedback button later (its own backend). The GifLab-specific feedback is a separate, PROMINENT
   Export-style button in the tool (built by the GifLab chat). Specific selector beats .foot-col a. */
.foot-col a.foot-feedback{ display:none }

/* Trim In/Out fly-in — animate ONLY the number, never the FIELD (owner: the input boxes themselves
   jerk/move, but only the digits inside should animate). The tool's `.fly-in` translates+fades the whole
   <input> (I'd specced it wrong), so the field box moves. Override it for the Trim `.num` inputs: fade
   just the TEXT COLOUR (transparent → ink) so the digits appear while the box stays dead-still. No
   transform → zero field movement. Higher specificity (.controls input.num.fly-in) + loads after → wins;
   GifLab's stagger (animationDelay In→Out) and animationend cleanup still apply. */
.controls input.num.fly-in{ animation: trimNumIn .34s cubic-bezier(.16,1,.3,1) both }
@keyframes trimNumIn{ from{ color:transparent } to{ color:var(--ink) } }

