RASAD-HU / frontend-src /src /index.css
aboodhaymouni
feat: full RASAD platform deploy — latest version
ed3a9a0
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* RASAD Premium Dark Design System */
--background: 220 20% 5%; /* #0A0C0F INK */
--foreground: 40 25% 92%;
--surface: 215 30% 9%; /* #0F1722 deep navy */
--surface-2: 215 25% 12%;
--paper: 42 27% 92%; /* #F0EDE6 */
--card: 215 30% 9%;
--card-foreground: 40 25% 92%;
--popover: 215 30% 9%;
--popover-foreground: 40 25% 92%;
--primary: 5 65% 48%; /* signal red #C8392D */
--primary-foreground: 0 0% 100%;
--primary-glow: 5 80% 58%;
--secondary: 215 25% 14%;
--secondary-foreground: 40 25% 92%;
--muted: 215 20% 14%;
--muted-foreground: 0 0% 60%;
--accent: 5 65% 48%;
--accent-foreground: 0 0% 100%;
--verified: 152 68% 32%; /* #1A8A5A */
--verified-foreground: 0 0% 100%;
--warning: 36 80% 52%; /* #E8A020 */
--warning-foreground: 0 0% 8%;
--info: 212 50% 63%; /* #6B9BD4 */
--info-foreground: 0 0% 8%;
--destructive: 5 65% 48%;
--destructive-foreground: 0 0% 100%;
--border: 0 0% 100% / 0.08;
--input: 215 25% 14%;
--ring: 5 65% 48%;
--radius: 0.75rem;
--gradient-hero: radial-gradient(ellipse at top right, hsl(5 65% 48% / 0.15), transparent 60%),
radial-gradient(ellipse at bottom left, hsl(212 50% 30% / 0.18), transparent 55%);
--gradient-signal: linear-gradient(135deg, hsl(5 65% 48%), hsl(5 80% 58%));
--gradient-card: linear-gradient(180deg, hsl(215 30% 11% / 0.9), hsl(215 30% 7% / 0.95));
--shadow-glow: 0 0 40px hsl(5 65% 48% / 0.25);
--shadow-elev: 0 12px 40px -12px hsl(0 0% 0% / 0.6);
--sidebar-background: 215 30% 9%;
--sidebar-foreground: 40 25% 92%;
--sidebar-primary: 5 65% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 215 25% 14%;
--sidebar-accent-foreground: 40 25% 92%;
--sidebar-border: 0 0% 100% / 0.06;
--sidebar-ring: 5 65% 48%;
/* Surface tokens used by themed UI primitives */
--surface-elev: 215 30% 11%;
--surface-elev-2: 215 30% 14%;
--hairline: 0 0% 100% / 0.08;
--hairline-strong: 0 0% 100% / 0.14;
--hover-overlay: 0 0% 100% / 0.04;
--scroll-track: 0 0% 100% / 0.06;
--scroll-thumb: 0 0% 100% / 0.12;
}
/* ─── LIGHT THEME — modern clean palette (Linear/Stripe/Notion-feel) ─── */
html.light {
--background: 220 20% 98%; /* #F8FAFB — clean neutral */
--foreground: 222 47% 11%; /* slate-900 */
--surface: 0 0% 100%; /* pure white card */
--surface-2: 220 14% 96%; /* slate-100 */
--paper: 220 20% 98%;
--card: 0 0% 100%;
--card-foreground: 222 47% 11%;
--popover: 0 0% 100%;
--popover-foreground: 222 47% 11%;
--primary: 5 78% 46%; /* brand red, slightly desaturated */
--primary-foreground: 0 0% 100%;
--primary-glow: 5 84% 56%;
--secondary: 220 14% 95%;
--secondary-foreground: 222 47% 11%;
--muted: 220 14% 96%;
--muted-foreground: 215 16% 47%; /* slate-600 — readable but soft */
--accent: 5 78% 46%;
--accent-foreground: 0 0% 100%;
--verified: 152 60% 32%;
--verified-foreground: 0 0% 100%;
--warning: 32 95% 44%;
--warning-foreground: 0 0% 100%;
--info: 212 70% 42%;
--info-foreground: 0 0% 100%;
--destructive: 5 78% 46%;
--destructive-foreground: 0 0% 100%;
--border: 220 13% 91%; /* slate-200 — crisp hairline */
--input: 220 14% 95%;
--ring: 5 78% 46%;
--gradient-hero: radial-gradient(ellipse at top right, hsl(5 84% 56% / 0.10), transparent 65%),
radial-gradient(ellipse at bottom left, hsl(212 70% 56% / 0.10), transparent 65%);
--gradient-signal: linear-gradient(135deg, hsl(5 78% 46%), hsl(5 84% 56%));
--gradient-card: linear-gradient(180deg, #ffffff, #fdfdfd);
--shadow-glow: 0 8px 24px -10px hsl(5 78% 46% / 0.25);
--shadow-elev: 0 1px 2px hsl(222 47% 11% / 0.04),
0 4px 12px -4px hsl(222 47% 11% / 0.06),
0 12px 32px -16px hsl(222 47% 11% / 0.06);
--sidebar-background: 0 0% 100%;
--sidebar-foreground: 222 47% 11%;
--sidebar-primary: 5 78% 46%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 220 14% 96%;
--sidebar-accent-foreground: 222 47% 11%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 5 78% 46%;
--surface-elev: 0 0% 100%; /* pure white */
--surface-elev-2: 220 14% 96%; /* slate-100 */
--hairline: 220 13% 91%; /* solid, no alpha — predictable */
--hairline-strong: 220 13% 82%;
--hover-overlay: 220 14% 94%;
--scroll-track: 220 14% 94%;
--scroll-thumb: 220 10% 70%;
}
}
@layer base {
* { @apply border-border; }
html {
font-family: 'Noto Kufi Arabic', system-ui, sans-serif;
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Anchor offsets so headers don't get hidden under the sticky navbar */
:target,
[id^="verify-"], [id^="section-"] {
scroll-margin-top: 96px;
}
/* Better focus rings */
*:focus-visible {
outline: 2px solid hsl(var(--primary) / 0.6);
outline-offset: 2px;
border-radius: 6px;
}
/* Custom scrollbar (subtle, theme-aware) */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: hsl(var(--scroll-track)); }
::-webkit-scrollbar-thumb {
background: hsl(var(--scroll-thumb));
border-radius: 999px;
border: 2px solid transparent;
background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: hsl(var(--primary) / 0.4); background-clip: padding-box; }
body {
@apply bg-background text-foreground antialiased;
background-image:
radial-gradient(ellipse at 20% -10%, hsl(var(--primary) / 0.08), transparent 50%),
radial-gradient(ellipse at 90% 10%, hsl(var(--info) / 0.06), transparent 50%),
linear-gradient(hsl(var(--foreground) / 0.015) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--foreground) / 0.015) 1px, transparent 1px);
background-size: auto, auto, 56px 56px, 56px 56px;
transition: background-color 280ms ease, color 280ms ease;
}
html.light body {
background-image:
radial-gradient(ellipse 100% 60% at 20% -20%, hsl(5 84% 60% / 0.06), transparent 60%),
radial-gradient(ellipse 100% 60% at 90% 5%, hsl(212 70% 56% / 0.05), transparent 60%);
background-attachment: fixed;
}
/* Light-mode: rewrite hardcoded white-opacity tailwind classes that exist
throughout the legacy pages so they don't render as pure-white overlays
on the warm-paper background. We re-map them to a dark-ink overlay tuned
to feel similar to the original dark-mode contrast ratio. */
html.light .bg-white\/\[0\.02\] { background-color: hsl(220 15% 20% / 0.025) !important; }
html.light .bg-white\/\[0\.03\] { background-color: hsl(220 15% 20% / 0.035) !important; }
html.light .bg-white\/\[0\.04\] { background-color: hsl(220 15% 20% / 0.045) !important; }
html.light .bg-white\/\[0\.05\] { background-color: hsl(220 15% 20% / 0.05) !important; }
html.light .bg-white\/\[0\.06\] { background-color: hsl(220 15% 20% / 0.06) !important; }
html.light .bg-white\/\[0\.08\] { background-color: hsl(220 15% 20% / 0.07) !important; }
html.light .bg-white\/\[0\.10\] { background-color: hsl(220 15% 20% / 0.09) !important; }
html.light .bg-white\/\[0\.12\] { background-color: hsl(220 15% 20% / 0.11) !important; }
html.light .border-white\/\[0\.04\] { border-color: hsl(220 15% 40% / 0.18) !important; }
html.light .border-white\/\[0\.05\] { border-color: hsl(220 15% 40% / 0.20) !important; }
html.light .border-white\/\[0\.06\] { border-color: hsl(220 15% 40% / 0.22) !important; }
html.light .border-white\/\[0\.08\] { border-color: hsl(220 15% 40% / 0.24) !important; }
html.light .border-white\/\[0\.10\] { border-color: hsl(220 15% 40% / 0.28) !important; }
html.light .border-white\/\[0\.12\] { border-color: hsl(220 15% 40% / 0.32) !important; }
html.light .border-white\/\[0\.14\] { border-color: hsl(220 15% 40% / 0.36) !important; }
html.light .ring-white\/10 { --tw-ring-color: hsl(220 15% 40% / 0.22) !important; }
html.light .ring-white\/20 { --tw-ring-color: hsl(220 15% 40% / 0.30) !important; }
/* Shadow blocks tuned with rgba(0,0,0,...) become invisible on paper; lift them */
html.light .shadow-\[0_24px_60px_-12px_rgba\(0\,0\,0\,0\.55\)\],
html.light .shadow-\[0_8px_32px_-8px_rgba\(0\,0\,0\,0\.5\)\] {
box-shadow: 0 18px 40px -16px hsl(220 30% 30% / 0.22) !important;
}
/* Dark navy [hsl(215 30% 8%)] / [hsl(215 30% 10%)] inline backgrounds → warm card */
html.light [class*="bg-\\[hsl\\(215_30\\%_8\\%\\)\\]"],
html.light [class*="bg-\\[hsl\\(215_30\\%_10\\%\\)\\]"],
html.light [class*="bg-\\[hsl\\(215_30\\%_11\\%\\)\\]"],
html.light [class*="bg-\\[hsl\\(215_30\\%_12\\%\\)\\]"],
html.light [class*="bg-\\[hsl\\(215_30\\%_13\\%\\)\\]"] {
background-color: hsl(var(--surface)) !important;
}
/* ─── Light mode polish — tame glows, soften shadows, reduce signal noise.
Dark-on-bright glows look amateurish; in light mode we replace red/blue
bloom with subtle warm drop-shadows that read as "elevation" instead
of "neon". */
/* Tone down ALL primary/info halo glow shadows in light. The legacy code
uses `signal-glow`, hand-rolled `0 0 N hsl(--primary)` shadows, and big
blurry "ambient" map shadows — they all need to feel like a printed
atlas, not a tradeshow LED wall. */
html.light .signal-glow {
box-shadow: 0 6px 18px -8px hsl(5 70% 30% / 0.32),
0 2px 6px -2px hsl(5 70% 30% / 0.18) !important;
}
/* cta-glow keyframe (light variant) — keep the gentle expansion, drop the
colored halo. */
html.light .cta-glow {
animation: cta-glow-light 2.6s ease-in-out infinite !important;
}
@keyframes cta-glow-light {
0%, 100% { box-shadow: 0 6px 18px -8px hsl(5 70% 30% / 0.32),
0 1px 3px -1px hsl(5 70% 30% / 0.15); }
50% { box-shadow: 0 10px 24px -10px hsl(5 70% 30% / 0.40),
0 2px 6px -2px hsl(5 70% 30% / 0.22); }
}
/* pulse-ring keyframe — switch to a smaller, lower-opacity ring. */
html.light .pulse-ring { animation: pulse-ring-light 2.2s infinite !important; }
@keyframes pulse-ring-light {
0% { box-shadow: 0 0 0 0 hsl(5 70% 40% / 0.28); }
70% { box-shadow: 0 0 0 12px hsl(5 70% 40% / 0); }
100% { box-shadow: 0 0 0 0 hsl(5 70% 40% / 0); }
}
/* Hard-coded blurry chunky shadows used by some panels — replace with a
restrained ink-tinted drop shadow. */
html.light .shadow-\[0_24px_60px_-12px_rgba\(0\,0\,0\,0\.55\)\],
html.light .shadow-\[0_8px_32px_-8px_rgba\(0\,0\,0\,0\.5\)\],
html.light .shadow-\[0_8px_32px_-8px_rgba\(0\,0\,0\,0\.4\)\] {
box-shadow: 0 12px 28px -16px hsl(220 30% 25% / 0.18),
0 4px 10px -6px hsl(220 30% 25% / 0.12) !important;
}
/* The world-map container has a giant primary-tinted box-shadow that
becomes a red bloom on paper. Soften the *element with surface-card*
plus the 100px shadow — calmer drop shadow. */
html.light .surface-card[style*="0 40px 100px"] {
box-shadow: 0 18px 40px -20px hsl(220 30% 25% / 0.18) !important;
}
/* Reduce ring opacity on glowing buttons (`ring-white/10` etc) — already
covered above, but `ring-1 ring-white/15` and `ring-white/20` need it
too. */
html.light .ring-white\/15 { --tw-ring-color: hsl(220 15% 40% / 0.28) !important; }
/* Lighter card-glow border highlight — the rotating gradient frame */
html.light .card-glow::before {
background: linear-gradient(
135deg,
transparent 30%,
hsl(var(--primary) / 0.0) 50%,
hsl(var(--primary) / 0.28) 75%,
transparent 100%
) !important;
}
/* radar-bg & grid-bg — make a touch more visible on paper without going
red. */
html.light .grid-bg {
background-image:
linear-gradient(hsl(220 15% 25% / 0.05) 1px, transparent 1px),
linear-gradient(90deg, hsl(220 15% 25% / 0.05) 1px, transparent 1px) !important;
}
html.light .radar-bg {
background:
radial-gradient(circle at center, transparent 0, transparent 30%, hsl(5 65% 48% / 0.04) 31%, transparent 32%),
radial-gradient(circle at center, transparent 0, transparent 50%, hsl(5 65% 48% / 0.035) 51%, transparent 52%),
radial-gradient(circle at center, transparent 0, transparent 70%, hsl(5 65% 48% / 0.03) 71%, transparent 72%) !important;
}
/* SVG feGaussianBlur glow filters wash out on paper. Soften by composing
with brightness adjustment. We target the WorldNewsMap node-glow id. */
html.light filter#node-glow feGaussianBlur { /* spec hint, browsers may ignore */ }
/* Cheaper approach: drop the marker box-shadow halos by overriding tone */
html.light .surface-card svg circle[filter="url(#node-glow)"] {
filter: drop-shadow(0 0 4px currentColor) !important;
}
/* Buttons using `bg-gradient-to-b from-primary to-primary/80` keep their
vibrant red — but the `ring-1 ring-white/10` makes them look like a
dropped-in sticker. Replace with a hairline darker rim. */
html.light .ring-1.ring-white\/10 { --tw-ring-color: hsl(5 70% 30% / 0.18) !important; }
/* Map container backdrop-blur on dark glass-card looks frosted on paper —
remove backdrop-blur in light to keep typography crisp. */
html.light .glass-panel { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }
/* CSS image-veil (used on hero/live cards) is tuned for dark; soften the
stronger black gradient. */
html.light .image-veil::after {
background: linear-gradient(180deg,
hsl(var(--surface) / 0.0) 0%,
hsl(var(--surface) / 0.28) 55%,
hsl(var(--surface) / 0.85) 100%) !important;
}
/* Final touch: subtle scrollbar tweak so it isn't aggressive in light */
html.light ::-webkit-scrollbar-thumb { background: hsl(220 15% 45% / 0.32) !important; }
html.light ::-webkit-scrollbar-thumb:hover { background: hsl(var(--primary) / 0.55) !important; }
/* "Soft blob" decoration overlay (used on stat cards) — too saturated on
paper background. Drop opacity to a hint. */
html.light .light-mode-soft-blob { opacity: 0.10 !important; filter: blur(36px) !important; }
/* The hero `text-shimmer` heading uses a fast primary→glow→primary
gradient. On paper the bright orange/red shimmer looks unprofessional —
freeze it to a solid primary tone. */
html.light .text-shimmer {
background: none !important;
color: hsl(var(--primary)) !important;
-webkit-text-fill-color: hsl(var(--primary)) !important;
animation: none !important;
}
/* Live page hero gradient — slightly dial it down. */
html.light section .grid-bg { opacity: 0.6 !important; }
h1, h2, h3, h4 { font-family: 'Noto Kufi Arabic', sans-serif; font-weight: 800; letter-spacing: -0.005em; }
/* Latin display (numbers, codes, EN labels) — Bebas for impact */
.display, .font-display { font-family: 'Bebas Neue', 'Noto Kufi Arabic', sans-serif; letter-spacing: 0.02em; }
.font-mono, code, .mono { font-family: 'IBM Plex Mono', ui-monospace, monospace; }
}
@layer components {
.glass-panel {
@apply rounded-2xl border backdrop-blur-xl;
border-color: hsl(var(--hairline));
background: var(--gradient-card);
box-shadow: var(--shadow-elev);
}
.signal-glow { box-shadow: var(--shadow-glow); }
.hairline { border: 1px solid hsl(var(--hairline)); }
.chip {
@apply inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs text-foreground/80;
border-color: hsl(var(--hairline));
background: hsl(var(--hover-overlay));
}
.surface-card {
background: hsl(var(--surface-elev));
border: 1px solid hsl(var(--hairline));
}
.surface-card-2 {
background: hsl(var(--surface-elev-2));
border: 1px solid hsl(var(--hairline));
}
.ring-border { box-shadow: inset 0 0 0 1px hsl(var(--hairline)); }
.grid-bg {
background-image:
linear-gradient(hsl(0 0% 100% / 0.03) 1px, transparent 1px),
linear-gradient(90deg, hsl(0 0% 100% / 0.03) 1px, transparent 1px);
background-size: 32px 32px;
}
.radar-bg {
background:
radial-gradient(circle at center, transparent 0, transparent 30%, hsl(5 65% 48% / 0.06) 31%, transparent 32%),
radial-gradient(circle at center, transparent 0, transparent 50%, hsl(5 65% 48% / 0.05) 51%, transparent 52%),
radial-gradient(circle at center, transparent 0, transparent 70%, hsl(5 65% 48% / 0.04) 71%, transparent 72%);
}
}
@keyframes pulse-ring {
0% { box-shadow: 0 0 0 0 hsl(5 65% 48% / 0.55); }
70% { box-shadow: 0 0 0 18px hsl(5 65% 48% / 0); }
100% { box-shadow: 0 0 0 0 hsl(5 65% 48% / 0); }
}
.pulse-ring { animation: pulse-ring 2.2s infinite; }
@keyframes radar-sweep {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.radar-sweep { animation: radar-sweep 6s linear infinite; transform-origin: center; }
@keyframes spin-slow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.animate-spin-slow { animation: spin-slow 3s linear infinite; }
@keyframes wire-scroll {
from { transform: translateX(0); }
to { transform: translateX(50%); }
}
.animate-wire-scroll {
animation: wire-scroll 60s linear infinite;
}
.animate-wire-scroll:hover { animation-play-state: paused; }
/* Editorial rule — newspaper-style horizontal divider with end caps */
.editorial-rule {
display: flex;
align-items: center;
gap: 0.75rem;
color: hsl(var(--muted-foreground));
}
.editorial-rule::before,
.editorial-rule::after {
content: "";
flex: 1;
height: 1px;
background: hsl(var(--hairline));
}
/* ─── Live Page Visualizations ─── */
@keyframes scan-line {
0% { transform: translateY(-100%); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateY(100%); opacity: 0; }
}
.scan-line::after {
content: "";
position: absolute;
inset-inline: 0;
height: 30%;
top: 0;
background: linear-gradient(180deg, transparent, hsl(var(--primary) / 0.35), transparent);
pointer-events: none;
animation: scan-line 3.6s ease-in-out infinite;
}
@keyframes glow-pan {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.glow-pan {
background-size: 200% 200%;
animation: glow-pan 6s ease-in-out infinite;
}
@keyframes meter-fill {
from { width: 0%; }
to { width: var(--fill, 50%); }
}
.meter-bar { animation: meter-fill 900ms cubic-bezier(0.22, 1, 0.36, 1) both; }
@keyframes pulse-dot {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.4); opacity: 0.6; }
}
.pulse-dot { animation: pulse-dot 1.8s ease-in-out infinite; }
@keyframes ticker-marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
.ticker-marquee { animation: ticker-marquee 45s linear infinite; }
.ticker-marquee:hover { animation-play-state: paused; }
/* Hero image gradient overlay for live cards */
.image-veil::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg, hsl(var(--surface) / 0.05) 0%, hsl(var(--surface) / 0.55) 60%, hsl(var(--surface) / 0.95) 100%);
pointer-events: none;
}
html.light .image-veil::after {
background: linear-gradient(180deg, hsl(var(--surface) / 0.0) 0%, hsl(var(--surface) / 0.35) 55%, hsl(var(--surface) / 0.88) 100%);
}
/* Inverted "paper" surface for the contrast section. In light mode this would
* be the same color as the page background, so we flip it to a dark ink panel. */
.paper-surface {
background: hsl(var(--paper));
color: hsl(220 20% 12%);
}
.paper-surface .muted {
color: hsl(220 10% 38%);
}
.paper-surface a { color: hsl(5 65% 38%); }
html.light .paper-surface {
background: hsl(220 30% 12%);
color: hsl(40 25% 92%);
}
html.light .paper-surface .muted { color: hsl(40 12% 70%); }
html.light .paper-surface a { color: hsl(5 80% 65%); }
/* Subtle hover lift used across cards */
.hover-lift {
transition: transform 240ms cubic-bezier(0.22, 1, 0.36, 1),
border-color 240ms ease,
box-shadow 240ms ease;
}
.hover-lift:hover {
transform: translateY(-2px);
border-color: hsl(var(--primary) / 0.3);
box-shadow: 0 12px 32px -8px hsl(var(--primary) / 0.18);
}
/* Shimmer effect for highlighted text on hero */
@keyframes shimmer {
0% { background-position: -100% 50%; }
100% { background-position: 200% 50%; }
}
.text-shimmer {
background: linear-gradient(
90deg,
hsl(var(--primary)) 0%,
hsl(var(--primary-glow)) 35%,
hsl(var(--primary)) 65%,
hsl(var(--primary-glow)) 100%
);
background-size: 200% 100%;
background-clip: text;
-webkit-background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
animation: shimmer 4.5s linear infinite;
}
/* Soft floating motion */
@keyframes float-y {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
.float-y { animation: float-y 4s ease-in-out infinite; }
/* ─── Mount entrance animations (stagger via inline animation-delay) ─── */
@keyframes enter-up {
from { opacity: 0; transform: translateY(22px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes enter-down {
from { opacity: 0; transform: translateY(-14px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes enter-fade {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes enter-left {
/* In RTL the visual "left" column slides in from the left edge */
from { opacity: 0; transform: translateX(-32px) scale(0.985); }
to { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes enter-right {
from { opacity: 0; transform: translateX(32px) scale(0.985); }
to { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes enter-pop {
0% { opacity: 0; transform: scale(0.9); }
60% { opacity: 1; transform: scale(1.04); }
100% { opacity: 1; transform: scale(1); }
}
@keyframes hero-glow-fade {
from { opacity: 0; }
to { opacity: 1; }
}
.enter-up { animation: enter-up 720ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.enter-down { animation: enter-down 600ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.enter-fade { animation: enter-fade 900ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.enter-left { animation: enter-left 900ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.enter-right { animation: enter-right 900ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.enter-pop { animation: enter-pop 620ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }
/* Animated underline that draws in once on mount */
@keyframes underline-draw {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.underline-draw {
position: relative;
display: inline-block;
}
.underline-draw::after {
content: "";
position: absolute;
inset-inline: 0;
bottom: -4px;
height: 3px;
border-radius: 2px;
background: linear-gradient(90deg, hsl(var(--primary)), hsl(var(--primary-glow)));
transform-origin: right;
animation: underline-draw 800ms cubic-bezier(0.22, 1, 0.36, 1) 900ms both;
}
@media (prefers-reduced-motion: reduce) {
.enter-up, .enter-down, .enter-fade, .enter-left, .enter-right, .enter-pop,
.underline-draw::after, .float-y, .text-shimmer, .cta-glow, .pulse-ring,
.radar-sweep, .animate-spin-slow {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
}
/* Glow pulse for primary CTA */
@keyframes cta-glow {
0%, 100% { box-shadow: 0 0 0 0 hsl(var(--primary) / 0.4), 0 12px 28px -8px hsl(var(--primary) / 0.35); }
50% { box-shadow: 0 0 0 8px hsl(var(--primary) / 0), 0 16px 40px -10px hsl(var(--primary) / 0.55); }
}
.cta-glow { animation: cta-glow 2.6s ease-in-out infinite; }
/* Card edge gradient on hover (overlays the border subtly) */
.card-glow { position: relative; }
.card-glow::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(
135deg,
transparent 30%,
hsl(var(--primary) / 0.0) 50%,
hsl(var(--primary) / 0.5) 75%,
transparent 100%
);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
opacity: 0;
transition: opacity 280ms ease;
pointer-events: none;
}
.card-glow:hover::before { opacity: 1; }