import { keyframes, style } from '@vanilla-extract/css'; export const FrameMount = style({ width: '100%', height: '100%', }); // Loading bar overlay shown while the widget iframe boots and capability // handshake completes. Mirrors `SyncIndicator` (sweep + radial fade + glow) // but pinned to the TOP of the iframe frame (under the BotShell hero) and // painted in the brand fleet-violet `Primary.Main` (#9580ff) instead of the // sync-state green. Resolves the «empty page on bot click» complaint: // before this, the iframe took ~200-800ms to first paint and the user saw // only the hero on a flat dark surface. Hex is hardcoded for the same // reason `BotShell.HeroAvatar` hardcodes it: keep the bot accent stable // across folds palette swaps. const slide = keyframes({ '0%': { transform: 'translateX(-100%)' }, '100%': { transform: 'translateX(100%)' }, }); // Container clips horizontally so the slide-keyframe wraps invisibly off- // screen at the edges. Vertically the upper drop-shadow halo (-6px..0) IS // intentionally clipped — the bar sits flush against the BotShell hero's // bottom border and a halo glowing UP would visually bleed into the hero // chrome. Only the lower halo (0..8px) renders, glowing DOWN into the // iframe — mirror of SyncIndicator's bottom-anchored bar where the halo // renders UP into the page. Don't bump `top` to surface the upper halo; // that would float the bar away from the hero edge and break the seam. export const LoadingBarRoot = style({ position: 'absolute', top: 0, left: 0, right: 0, height: '14px', pointerEvents: 'none', // Above the iframe, but no need for the global Z400 — local stacking // context inside Frame is enough. zIndex: 2, overflow: 'hidden', }); export const LoadingBar = style({ position: 'absolute', top: 0, left: 0, width: '100%', height: '2px', background: 'radial-gradient(ellipse 50% 100% at center, #9580ff 0%, rgba(149, 128, 255, 0.45) 35%, rgba(149, 128, 255, 0) 70%)', filter: 'drop-shadow(0 0 6px rgba(149, 128, 255, 0.75))', animation: `${slide} 1.6s linear infinite`, willChange: 'transform', // 250ms opacity transition matches SyncIndicator's crossfade so the bar // doesn't pop in/out abruptly when ready flips. animationPlayState is // toggled inline at the call site so the compositor doesn't keep // sweeping while the bar is invisible (opacity:0 alone doesn't pause // CSS animations — verified across browsers). transition: 'opacity 250ms ease', '@media': { '(prefers-reduced-motion: reduce)': { animation: 'none', }, }, });