fix(familiaros-website): align spinner, metal chrome, and primary button palette
Some checks are pending
Deploy FamiliarOS Website / deploy (push) Waiting to run

This commit is contained in:
OpenPets Dev 2026-06-21 00:52:48 +00:00
parent b3d915c00e
commit 80b968465f
5 changed files with 300 additions and 183 deletions

View file

@ -6,20 +6,57 @@
- Header layout on all public pages: emblem left, wordmark centered in header track, nav/actions right.
- Header shell height: `h-[10rem]` / `md:h-[10.5rem]` / `lg:h-[11rem]`.
- Color contract (from `/website/src/styles/global.css`)
- Core system blue/cyan:
- `--fos-cyan: #00E6FF`
- `--fos-ion-blue: #3A86FF`
- `--fos-deep-blue: #0A66FF`
- Action / activation amber:
- `--fos-amber: #FF9A00`
- `--fos-gold: #FFC43D`
- `--fos-ember: #C96A00`
- Metal hierarchy:
- `--fos-chrome: #E6E8EC`
- `--fos-steel: #A6ADB8`
- `--fos-gunmetal: #3A3F46`
- `--fos-slate: #121821`
- `--fos-deep-space: #0B0F14`
- `--fos-black: #05070A`
- Silver surface language:
- `--brand-silver: #E6E8EC`
- `--brand-silver-soft: #F5F6F7`
- `--brand-silver-ink: #303949`
- Spinner system (suspended load)
- Use the suspense frame in `GeneralPageLoader`.
- Default state uses `NamedAgentSpinner name="cascade"` with `size={32}` and `loading-spinner` class.
- Default color: `--fos-cyan` (blue/cyan).
- Final transition pulse: append `is-completing` class and use `--fos-amber` for one short flash.
- Keep no top progress bar; spinner sits central in one framed shell.
- Silver accent system for public marketing surfaces
- Use the silver tokens for shared effects and surfaces:
- `--brand-silver`
- `--brand-silver-soft`
- `--brand-silver-ink`
- Applied across all `MarketingShell`-based pages (`About`, `Docs`, `Support`, `Help`, `Login`, `Register`, `Pricing`, `Changelog`, `Affiliate*`, and legal marketing wrappers).
- Apply silver treatment to `public-card`, `public-card-raised`, `pricing-glow-border`, and MetalFX frames.
- Use the silver tokens for shared effects and surfaces.
- Apply across `public-card`, `public-card-raised`, `pricing-glow-border`, MetalFX frames, and nav/header pills.
- Pricing controls/faces use silver (`billing tabs`, `toggle`, `plan badge`, `Most Popular` ribbon, `feature check`/icons, FAQ icons, CTA sparkles).
- Subscription frame background glows and borders must remain silver.
- Subscription frame background glows and borders remain silver.
- Typography
- Technical/modern stack remains in global tokens:
- Technical stack remains:
- `--font-ui: Sora`
- `--font-display: Orbitron` (with Sora fallback)
- `--font-display: Orbitron`
- Recommended font direction: technical/modern sans stack (Sora + Orbitron), with `Space Grotesk` as an alternate UI edge option.
- Light/dark mode
- Keep `data-color-mode`/`html.light` style overrides for public cards/surfaces aligned with the silver frame language.
- Keep `data-color-mode`/`html.light` style overrides for public cards/surfaces aligned with silver frame language.
- Keep header/nav/button frames and branding transparent where applicable.
- Background video decision lane
- Preferred default for the homepage/marketing background: `matrix-digital-moewalls-com.mp4`.
- If no full swap is needed, keep no motion background.
- Use the cyan family (`--fos-cyan`, `--fos-ion-blue`) as the final grade tone when needed.
- Reverse-sync for brand/video assets
- Use the shared Windows lane to bring in local asset drops from `C:\FamiliarOS`:
- `powershell -File C:\ScriptoriumAI\ops\ssh\run-vps-remote-dev-reverse-sync.ps1 -Selection FamiliarOS`
- Place local logo/video drop targets under `C:\FamiliarOS\website\public\` equivalents and re-run the above once files are present.

View file

@ -102,15 +102,17 @@ export function MarketingShell({ children, showFooter = true }: MarketingShellPr
const navGlassClassName = isDark
? 'rounded-full border border-white/15 bg-white/5 backdrop-blur-2xl'
: 'rounded-full border border-[#262B33]/25 bg-white/85 backdrop-blur-2xl'
const navMetalFrameClassName = `public-nav-metal-frame ${isDark ? '' : 'bg-white/5 border border-[#262B33]/25'}`
const logoClassName = 'drop-shadow-[0_0_14px_rgba(232,232,232,0.36)]'
const headerFrameClassName = 'h-[10rem] md:h-[10.5rem] lg:h-[11rem]'
const headerInnerClassName = `relative mx-auto flex w-full max-w-7xl items-center gap-2.5 px-4 md:gap-3 md:px-5 lg:gap-4 lg:px-6 xl:gap-5 ${headerFrameClassName}`
const emblemImageClassName = `block h-[3.8rem] w-[3.8rem] shrink-0 bg-transparent object-contain object-center sm:h-[4.2rem] sm:w-[4.2rem] md:h-[4.5rem] md:w-[4.5rem] lg:h-[4.9rem] lg:w-[4.9rem] ${logoClassName}`
const wordmarkImageClassName = `block h-[3.35rem] w-auto max-w-[14rem] shrink-0 bg-transparent object-contain sm:h-[3.8rem] md:h-[4.2rem] md:max-w-[14.5rem] lg:h-[4.65rem] lg:max-w-[15.8rem] ${logoClassName}`
const emblemImageClassName = `block h-[3.8rem] w-[3.8rem] shrink-0 bg-transparent object-contain object-center sm:h-[4.2rem] sm:w-[4.2rem] md:h-[4.6rem] md:w-[4.6rem] lg:h-[5rem] lg:w-[5rem] ${logoClassName}`
const wordmarkImageClassName = `block h-[3.6rem] w-auto max-w-[14.5rem] shrink-0 bg-transparent object-contain sm:h-[4.1rem] md:h-[4.8rem] md:max-w-[15.2rem] lg:h-[5.2rem] lg:max-w-[16.5rem] ${logoClassName}`
const logoClusterClassName = 'flex items-center gap-2.5'
const emblemLinkClassName = 'relative z-10 flex min-w-0 items-center gap-0'
const wordmarkLinkClassName = 'relative hidden md:inline-flex items-center justify-center'
const wordmarkWrapperClassName = 'pointer-events-none absolute left-1/2 top-1/2 z-10 hidden -translate-x-1/2 -translate-y-1/2 flex-col items-center md:flex'
const wordmarkLinkClassName = 'relative inline-flex items-center justify-center pointer-events-auto'
const activePath = useMemo(() => {
if (location.pathname === '/about') return '/about'
@ -165,6 +167,9 @@ export function MarketingShell({ children, showFooter = true }: MarketingShellPr
draggable={false}
/>
</Link>
</div>
<div className={wordmarkWrapperClassName}>
<Link
to="/"
className={wordmarkLinkClassName}
@ -181,63 +186,65 @@ export function MarketingShell({ children, showFooter = true }: MarketingShellPr
</div>
<nav className="hidden items-center justify-center flex-1 lg:flex">
<div className={`flex items-center gap-2 px-3 py-2 ${navGlassClassName}`}>
{navigationItems.map((entry) => {
if (entry.kind === 'dropdown') {
const isChildActive = entry.children.some((c) => activePath === c.to)
<div className={navMetalFrameClassName}>
<div className={`flex items-center gap-2 px-3 py-2 ${navGlassClassName}`}>
{navigationItems.map((entry) => {
if (entry.kind === 'dropdown') {
const isChildActive = entry.children.some((c) => activePath === c.to)
return (
<div key={entry.label} className="relative" ref={resourcesRef}>
<button
type="button"
onClick={() => setResourcesOpen((v) => !v)}
onBlur={(e) => {
if (!resourcesRef.current?.contains(e.relatedTarget as Node)) setResourcesOpen(false)
}}
className={`public-nav-link flex items-center gap-1 ${
isChildActive
? 'public-nav-link-active'
: `${mutedClassName} ${navHoverClassName} hover:text-current`
}`}
>
{entry.label}
<ChevronDown size={12} className={`transition-transform ${resourcesOpen ? 'rotate-180' : ''}`} />
</button>
{resourcesOpen && (
<div className={`absolute left-0 top-full mt-2 min-w-[160px] rounded-2xl border p-1 shadow-xl z-50 ${isDark ? 'bg-[hsl(var(--card))] border-white/15' : 'bg-white border-[hsl(var(--border))]'}`}>
{entry.children.map((child) => (
<Link
key={child.to}
to={child.to}
onClick={() => setResourcesOpen(false)}
className={`block rounded-xl px-3 py-2 text-sm transition-colors ${
activePath === child.to
? 'bg-[hsl(var(--brand-line))] text-[hsl(var(--foreground))]'
: `${mutedClassName} ${navHoverClassName} hover:text-current`
}`}
>
{child.label}
</Link>
))}
</div>
)}
</div>
)
}
const isActive = activePath === entry.to
return (
<div key={entry.label} className="relative" ref={resourcesRef}>
<button
type="button"
onClick={() => setResourcesOpen((v) => !v)}
onBlur={(e) => {
if (!resourcesRef.current?.contains(e.relatedTarget as Node)) setResourcesOpen(false)
}}
className={`public-nav-link flex items-center gap-1 ${
isChildActive
? 'public-nav-link-active'
: `${mutedClassName} ${navHoverClassName} hover:text-current`
}`}
>
{entry.label}
<ChevronDown size={12} className={`transition-transform ${resourcesOpen ? 'rotate-180' : ''}`} />
</button>
{resourcesOpen && (
<div className={`absolute left-0 top-full mt-2 min-w-[160px] rounded-2xl border p-1 shadow-xl z-50 ${isDark ? 'bg-[hsl(var(--card))] border-white/15' : 'bg-white border-[hsl(var(--border))]'}`}>
{entry.children.map((child) => (
<Link
key={child.to}
to={child.to}
onClick={() => setResourcesOpen(false)}
className={`block rounded-xl px-3 py-2 text-sm transition-colors ${
activePath === child.to
? 'bg-[hsl(var(--brand-line))] text-[hsl(var(--foreground))]'
: `${mutedClassName} ${navHoverClassName} hover:text-current`
}`}
>
{child.label}
</Link>
))}
</div>
)}
</div>
<Link
key={entry.to}
to={entry.to}
className={`public-nav-link ${
isActive
? 'public-nav-link-active'
: `${mutedClassName} ${navHoverClassName} hover:text-current`
}`}
>
{entry.label}
</Link>
)
}
const isActive = activePath === entry.to
return (
<Link
key={entry.to}
to={entry.to}
className={`public-nav-link ${
isActive
? 'public-nav-link-active'
: `${mutedClassName} ${navHoverClassName} hover:text-current`
}`}
>
{entry.label}
</Link>
)
})}
})}
</div>
</div>
</nav>

View file

@ -20,6 +20,11 @@ export function PublicPrimaryButton({
const Comp = asChild ? Slot : 'button'
const theme = colorMode === 'light' ? 'light' : 'dark'
const [metalReady, setMetalReady] = useState(false)
const buttonStyle = {
color: 'hsl(var(--brand-silver))',
textShadow: '0 1px 2px rgba(0, 0, 0, 0.35)',
...props.style,
}
useLayoutEffect(() => {
setSharedPreset('silver', theme)
@ -30,6 +35,7 @@ export function PublicPrimaryButton({
<Comp
className={cn('public-btn-primary', className)}
{...(asChild ? {} : { type: type ?? 'button' })}
style={buttonStyle}
{...props}
>
{children}

View file

@ -1,5 +1,6 @@
import React from 'react'
import { LissajousDriftLoader } from './LissajousDriftLoader'
import React from 'react'
import { useEffect, useState } from 'react'
import { NamedAgentSpinner } from './agent-spinners'
type SkeletonBlockProps = {
className?: string
@ -67,10 +68,36 @@ export function EditorLoader() {
)
}
export function GeneralPageLoader({ title = 'Loading' }: { title?: string }) {
return (
<div className="h-full w-full flex flex-col items-center justify-center bg-[#181A1E] text-gray-300 gap-4">
<LissajousDriftLoader text={title} />
</div>
)
}
export function GeneralPageLoader({ title = 'Loading' }: { title?: string }) {
const [isCompleting, setIsCompleting] = useState(false)
useEffect(() => {
const pulseTimer = window.setTimeout(() => {
setIsCompleting(true)
}, 900)
const clearTimer = window.setTimeout(() => {
setIsCompleting(false)
}, 1550)
return () => {
window.clearTimeout(pulseTimer)
window.clearTimeout(clearTimer)
}
}, [])
const spinnerColor = isCompleting ? 'var(--fos-amber)' : 'var(--fos-cyan)'
return (
<div className="general-page-loader-shell" role="status" aria-live="polite" aria-label={title}>
<div className="general-page-loader-frame">
<NamedAgentSpinner
name="cascade"
size={32}
color={spinnerColor}
className={`loading-spinner ${isCompleting ? 'is-completing' : ''}`}
/>
<p className="general-page-loader-text">{title}</p>
</div>
</div>
)
}

View file

@ -6,54 +6,63 @@
@layer base {
:root {
/* FamiliarOS Brand Colors - Monochrome + deep-familar blue */
--background: 220 13% 6%;
--foreground: 43 66% 86%;
--muted: 218 11% 14%;
--muted-foreground: 43 28% 70%;
--popover: 220 13% 6%;
--popover-foreground: 43 50% 85%;
--card: 220 11% 9%;
--card-foreground: 43 66% 86%;
/* Primary: extracted from brand mark (55,106,166) */
--primary: 212 50% 43%;
--primary-foreground: 42 67% 95%;
--primary: 190 100% 50%;
--primary-foreground: 212 45% 97%;
--secondary: 205 75% 20%;
--secondary-foreground: 42 67% 95%;
/* Accent: extracted from icon warm neutral tones */
--accent: 26 59% 59%;
--accent-foreground: 220 20% 13%;
--brand-line: 212 50% 43%;
--fos-cyan: 186 100% 50%;
--fos-ion-blue: 217 100% 61%;
--fos-deep-blue: 217 100% 52%;
--fos-amber: 36 100% 50%;
--fos-gold: 42 100% 62%;
--fos-ember: 32 100% 39%;
--fos-chrome: 220 14% 91%;
--fos-steel: 217 11% 69%;
--fos-gunmetal: 215 9% 25%;
--fos-slate: 216 29% 10%;
--fos-deep-space: 213 29% 6%;
--fos-black: 216 33% 3%;
--brand-line: var(--fos-cyan);
--brand-surface: 220 14% 12%;
--brand-ivory: 40 34% 77%;
--brand-gold: 43 43% 59%;
--brand-gold-soft: 38 24% 54%;
--brand-gold: var(--fos-gold);
--brand-gold-soft: 47 65% 85%;
--brand-muted: 214 7% 55%;
--brand-pale: 209 28% 96%;
--brand-ink: 39 63% 7%;
--brand-deep: 220 13% 6%;
--brand-accent-alt: 258 90% 70%;
--brand-silver: 220 14% 91%;
--brand-silver-soft: 210 11% 96%;
--brand-silver-ink: 218 21% 24%;
--destructive: 0 72% 51%;
--destructive-foreground: 210 40% 98%;
--border: 214 25% 17%;
--input: 214 25% 17%;
--ring: 206 100% 13%;
--radius: 0.5rem;
--font-ui: 'Sora', 'Inter', 'Fira Sans', 'Source Sans 3', 'Segoe UI', sans-serif;
--font-ui: 'Sora', 'Space Grotesk', 'Inter', 'Fira Sans', 'Source Sans 3', 'Segoe UI', sans-serif;
--font-display: 'Orbitron', 'Sora', 'Space Grotesk', 'Inter', 'Segoe UI', sans-serif;
--brand-silver: 0 0% 76%;
--brand-silver-soft: 0 0% 93%;
--brand-silver-ink: 220 7% 16%;
}
* {
@ -91,20 +100,33 @@
--card: 220 11% 9%;
--card-foreground: 43 66% 86%;
--primary: 212 50% 43%;
--primary-foreground: 42 67% 95%;
--primary: 190 100% 50%;
--primary-foreground: 212 45% 97%;
--secondary: 205 75% 20%;
--secondary-foreground: 42 67% 95%;
--accent: 26 59% 59%;
--accent-foreground: 220 20% 13%;
--brand-line: 212 50% 43%;
--fos-cyan: 186 100% 50%;
--fos-ion-blue: 217 100% 61%;
--fos-deep-blue: 217 100% 52%;
--fos-amber: 36 100% 50%;
--fos-gold: 42 100% 62%;
--fos-ember: 32 100% 39%;
--fos-chrome: 220 14% 91%;
--fos-steel: 217 11% 69%;
--fos-gunmetal: 215 9% 25%;
--fos-slate: 216 29% 10%;
--fos-deep-space: 213 29% 6%;
--fos-black: 216 33% 3%;
--brand-line: var(--fos-cyan);
--brand-surface: 220 14% 12%;
--brand-ivory: 40 34% 77%;
--brand-gold: 43 43% 59%;
--brand-gold-soft: 38 24% 54%;
--brand-gold-soft: 47 65% 85%;
--brand-muted: 214 7% 55%;
--brand-pale: 209 28% 96%;
--brand-ink: 39 63% 7%;
@ -113,6 +135,10 @@
--destructive: 0 72% 51%;
--destructive-foreground: 210 40% 98%;
--brand-silver: 220 14% 91%;
--brand-silver-soft: 210 11% 96%;
--brand-silver-ink: 218 21% 24%;
--border: 214 25% 17%;
--input: 214 25% 17%;
@ -134,16 +160,36 @@
--card: 42 66% 96%;
--card-foreground: 220 20% 16%;
/* Primary: keep brand gold, darker shade for contrast on light bg */
--primary: 212 50% 43%; /* brand blue on light */
/* Primary: keep brand blue, cooler tone on light bg */
--primary: 190 100% 38%;
--primary-foreground: 0 0% 100%;
--secondary: 42 52% 84%; /* warm light tint */
--secondary-foreground: 220 20% 16%;
--accent: 26 59% 59%; /* warm neutral */
--accent: 217 100% 45%; /* cool neutral accent */
--accent-foreground: 0 0% 100%;
--fos-cyan: 186 100% 50%;
--fos-ion-blue: 217 100% 61%;
--fos-deep-blue: 217 100% 52%;
--fos-amber: 36 100% 50%;
--fos-gold: 42 100% 62%;
--fos-ember: 32 100% 39%;
--fos-chrome: 220 14% 91%;
--fos-steel: 217 11% 69%;
--fos-gunmetal: 215 9% 25%;
--fos-slate: 216 29% 10%;
--fos-deep-space: 213 29% 6%;
--fos-black: 216 33% 3%;
--brand-line: var(--fos-cyan);
--brand-gold: var(--fos-gold);
--brand-gold-soft: 47 65% 85%;
--brand-silver: 220 14% 91%;
--brand-silver-soft: 210 11% 96%;
--brand-silver-ink: 218 21% 24%;
--destructive: 0 72% 44%;
--destructive-foreground: 0 0% 100%;
@ -982,11 +1028,29 @@
}
/* ── CTA pill-box buttons — primary uses metal-fx wrapper on public pages ── */
.public-nav-metal-frame {
display: inline-flex;
vertical-align: middle;
border-radius: 9999px;
border: 1px solid hsl(var(--brand-silver) / 0.2);
background: color-mix(in srgb, hsl(var(--brand-silver) / 0.05) 78%, transparent);
transition: transform 0.18s ease, filter 0.18s ease;
}
.public-nav-metal-frame:hover {
transform: translateY(-1px);
}
.public-nav-metal-frame:active {
transform: translateY(0);
}
.public-silver-metal-frame,
.public-gold-metal-frame {
display: inline-flex;
vertical-align: middle;
border-radius: 9999px;
background: transparent !important;
filter: drop-shadow(0 12px 24px hsla(var(--brand-silver) / 0.15)) drop-shadow(0 0 16px hsla(var(--brand-silver) / 0.35));
transition: transform 0.18s ease, filter 0.18s ease;
}
@ -1015,16 +1079,15 @@
line-height: 1.2;
transition: color 0.18s ease, opacity 0.18s ease;
background: transparent;
color: hsl(var(--foreground));
color: hsl(var(--brand-silver)) !important;
border: 1px solid hsl(var(--brand-silver) / 0.7);
box-shadow: 0 1px 4px rgba(0,0,0,0.35), 0 0 0 1px hsla(var(--brand-silver) / 0.25), 0 0 32px hsla(var(--brand-silver) / 0.2);
cursor: pointer;
white-space: nowrap;
text-shadow: 0 1px 2px rgba(0,0,0,0.35);
}
.public-btn-primary:hover {
color: hsl(var(--foreground));
text-shadow: 0 1px 2px rgba(0,0,0,0.45);
.public-btn-primary:hover {
color: hsl(var(--brand-silver-soft));
}
.public-btn-primary:disabled {
cursor: not-allowed;
@ -1041,13 +1104,9 @@
filter: drop-shadow(0 18px 34px rgba(90,90,90,0.2)) drop-shadow(0 0 20px rgba(220,220,220,0.18));
}
[data-color-mode='light'] .public-btn-primary {
color: #1f2937;
text-shadow: none;
}
[data-color-mode='light'] .public-btn-primary,
[data-color-mode='dark'] .public-btn-primary {
color: #E2E8F0;
text-shadow: 0 1px 2px rgba(0,0,0,0.45);
color: hsl(var(--brand-silver));
}
/* Secondary: ghost border */
@ -1084,14 +1143,14 @@
}
/* Nav active background — visible in both dark and light */
.public-nav-link-active {
background: rgba(216,203,175,0.18);
color: #D8CBAF;
}
[data-color-mode='light'] .public-nav-link-active {
background: rgba(166,146,110,0.14);
color: #7A6A50;
}
.public-nav-link-active {
background: hsl(var(--brand-silver) / 0.18);
color: hsl(var(--brand-silver-soft));
}
[data-color-mode='light'] .public-nav-link-active {
background: hsl(var(--brand-silver) / 0.18);
color: hsl(var(--brand-silver));
}
}
@layer utilities {
@ -1141,75 +1200,56 @@
}
/* Quantum Pulse Loader */
.generating-loader-wrapper {
.general-page-loader-shell {
position: relative;
display: flex;
min-height: calc(100vh - 10rem);
display: grid;
place-items: center;
overflow: hidden;
padding: 1.5rem;
background: transparent;
}
.general-page-loader-frame {
position: relative;
display: inline-flex;
min-width: min(24rem, 82vw);
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 1rem;
gap: 0.9rem;
border: 1px solid hsl(var(--brand-silver) / 0.32);
border-radius: 1.2rem;
padding: 1.35rem 1.2rem 1.1rem;
background: hsl(var(--brand-deep) / 0.35);
box-shadow:
inset 0 0 0 1px hsl(var(--brand-silver) / 0.08),
0 0 28px hsl(var(--brand-silver) / 0.2);
backdrop-filter: blur(14px);
}
.generating-loader-text {
display: flex;
.general-page-loader-text {
display: inline-flex;
font-size: 0.81rem;
font-weight: 600;
font-size: 1.25rem;
letter-spacing: 0.1em;
color: hsl(var(--primary) / 0.8);
letter-spacing: 0.12em;
text-transform: uppercase;
color: hsl(var(--brand-muted));
}
.generating-loader-letter {
display: inline-block;
animation: generating-loader-letter-anim 2s infinite;
.loading-spinner {
color: hsl(var(--fos-cyan));
text-shadow:
0 0 6px hsl(var(--fos-cyan) / 0.9),
0 0 18px hsl(var(--fos-ion-blue) / 0.55),
0 0 32px hsl(var(--fos-cyan) / 0.28);
}
.generating-loader-bar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, hsl(var(--primary)), transparent);
animation: generating-loader-bar-anim 2s linear infinite;
}
@keyframes generating-loader-letter-anim {
0% {
opacity: 0;
transform: translateY(20px) rotateX(-90deg);
text-shadow: 0 0 0px rgba(255, 255, 255, 0);
}
10% {
opacity: 1;
transform: translateY(0) rotateX(0deg);
text-shadow: 0 0 10px rgba(255, 255, 255, 0.8);
}
30% {
opacity: 1;
transform: translateY(0) rotateX(0deg);
text-shadow: 0 0 10px rgba(255, 255, 255, 0.8);
}
50% {
opacity: 0;
transform: translateY(-20px) rotateX(90deg);
text-shadow: 0 0 0px rgba(255, 255, 255, 0);
}
100% {
opacity: 0;
}
}
@keyframes generating-loader-bar-anim {
0% {
left: -100%;
}
50% {
left: 100%;
}
100% {
left: -100%;
}
.loading-spinner.is-completing {
color: hsl(var(--fos-amber));
text-shadow:
0 0 6px hsl(var(--fos-amber) / 0.95),
0 0 18px hsl(var(--fos-amber) / 0.55),
0 0 32px hsl(var(--fos-gold) / 0.28);
}
/* ── Collaborative cursor decorations (Monaco remote user cursors) ─────────── */