65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
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',
|
|
},
|
|
},
|
|
});
|