@import "tailwindcss"; /* ───────────────────────────────────────────────────────── VYNDR Design System Bloomberg terminal + ESPN broadcast + glitch-in-the-matrix. "The terminal at sportsbook prices." ───────────────────────────────────────────────────────── */ :root { /* Surfaces — Design package exact ramp (HANDOFF): void/card/elevated + deep-panel + row-hairline. (2026-07-16 token alignment — Design wins.) */ --bg-0: #06060B; /* void */ --bg-1: #0E0E14; /* card (was #0E0E16) */ --bg-2: #14141E; /* elevated (was #15151F) */ --bg-3: #1A1A26; --bg-deep: #0A0A10; /* deep panel */ --hairline: #101018; /* row hairline */ /* Borders — Design exact */ --border: #1E1E2A; /* was #1E1E2E */ --border-hi: #2A2A38; /* was #2A2A3E */ /* Text — Design exact ramp F0F0F0 / B8BCC8 / 707080 (+ 4a4a58 micro) */ --text-0: #F0F0F0; /* was #E8E8F0 */ --text-1: #B8BCC8; /* was #7A7A8E — Design's secondary is far brighter */ --text-2: #707080; /* was #4A4A5E */ --text-3: #4a4a58; /* micro-labels */ /* Brand accent — deep forest, button green, glow */ --acc-0: #0F3D2E; --acc-1: #1A5A42; --acc-glow: rgba(15, 61, 46, 0.25); /* Grade tiers — the most important colors. Design package (HANDOFF): A-tier green+glow · B neutral-bright WHITE · C muted GREY · D/F red. The old B=blue / C=amber violated DESIGN-SPEC v2's own "B neutral-bright, C muted" — aligned to Design 2026-07-16. Amber stays its own token (--amber). */ --grade-aplus: #00FFB8; --grade-a: #00D4A0; --grade-b: #F0F0F0; /* neutral-bright white (was blue) */ --grade-c: #B8BCC8; /* was #FFB347 (amber) */ --grade-d: #FF4757; /* was #FF5252 */ /* Sport tints */ --nba: #E94B3C; --mlb: #1E90FF; --wnba: #F7944A; /* Special — Vendetta crimson; kill conditions only */ --crimson: #8B0000; /* Signals */ --danger: var(--grade-d); --warning: var(--amber); /* decoupled from grade-C (now grey) — warning is amber */ --success: var(--grade-a); /* Geometry */ --diagonal-angle: -2deg; --radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-xl: 24px; /* Glitch intensity — 0..1, can be tuned per-section. VYNDR 2.0 (§2) sets this to 1 — full intensity is the design baseline. */ --glitch: 1; --scan-opacity: calc(0.04 + 0.06 * var(--glitch)); --grain-opacity: calc(0.02 + 0.02 * var(--glitch)); --rgb-shift: calc(0.5px + 1.5px * var(--glitch)); --slash-opacity: calc(0.06 + 0.08 * var(--glitch)); --sweep-opacity: calc(0.08 + 0.12 * var(--glitch)); /* ───────────────────────────────────────────────────────── VYNDR 2.0 DESIGN TOKENS (§2 — exact values from vyndr.css). The new canonical short-name token set. Legacy aliases below keep every existing component resolving during the migration. ───────────────────────────────────────────────────────── */ /* Grade tokens (the product's core color language) */ --g-ap: #00ffb8; /* A+ rarest, max glow */ --g-a: #00d4a0; /* A workhorse green / primary CTA */ --g-b: #F0F0F0; /* B neutral-bright white (Design) */ --g-c: #B8BCC8; /* C muted grey (Design; was amber) */ --g-d: #FF4757; /* D red (Design; was #ff5252) */ /* Sport tokens */ --s-nba: #e94b3c; --s-mlb: #1e90ff; --s-wnba: #f7944a; --s-soccer: #3ddc84; /* Amber / system glow (LOADING, LIVE, 404, warnings) */ --amber: #ffb347; --amber-glow: 0 0 12px rgba(255, 179, 71, 0.6); /* Semantic */ --live: #ff3b3b; --hit: #00d4a0; --miss: #FF4757; /* Design red (was #ff5252) */ /* Scanline base opacity + hero grade color */ --scan-op: 0.04; --grade-hero: var(--g-a); /* Motion tokens (DS5 · DESIGN-SPEC Part 4 — ALIVE IS PUNCTUATED STILLNESS). Every duration in the living layer resolves from one of these — no one-off ms literals. IDLE = the single proof-of-life pulse; TRANSITION is sub-200ms (perceived instantaneity); DATA is the change-only green pulse; TICKER-HOLD is how long the ticker RESTS on each item so it can be read (≥4s). */ --motion-idle: 1.4s; --motion-transition: 180ms; --motion-data: 300ms; --ticker-hold: 4200ms; /* Type — Inter for chrome/UI, JetBrains Mono for ALL data. Session 41: --font-* come from next/font (self-hosted, set on ). Literal family names kept as fallbacks for any non-next/font context. */ --sans: var(--font-sans), 'Inter', system-ui, sans-serif; --mono: var(--font-mono), 'JetBrains Mono', 'SF Mono', ui-monospace, monospace; --ibm-mono: var(--font-ibm), var(--font-mono), 'IBM Plex Mono', 'JetBrains Mono', 'SF Mono', ui-monospace, monospace; /* ── Legacy aliases — every existing component still resolves ── */ --bg-primary: var(--bg-0); --bg-surface: var(--bg-1); --bg-surface-hover: var(--bg-2); --bg-elevated: var(--bg-3); --border-focus: var(--border-hi); --text-primary: var(--text-0); --text-secondary: var(--text-1); --text-tertiary: var(--text-2); --accent: var(--acc-0); --accent-light: var(--acc-1); --accent-glow: var(--acc-glow); --bg: var(--bg-0); --card: var(--bg-1); --card-hover: var(--bg-2); --border-light: var(--border-hi); --text: var(--text-0); --text-muted: var(--text-1); --text-dim: var(--text-2); --cyan: var(--grade-a); --cyan-hover: var(--grade-aplus); --cyan-dim: rgba(0, 212, 160, 0.10); --kill: var(--crimson); --forest: var(--acc-0); --forest-dark: #0A2A20; --forest-light: var(--acc-1); } /* ───────────────────────────────────────────────────────── Base ───────────────────────────────────────────────────────── */ * { box-sizing: border-box; } html { background: var(--bg-primary); color-scheme: dark; } body { background: var(--bg-0); color: var(--text-0); font-family: var(--sans); font-weight: 400; line-height: 1.6; letter-spacing: -0.01em; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; min-height: 100vh; } h1, h2, h3, h4, h5, h6 { font-family: var(--sans); font-weight: 700; letter-spacing: -0.02em; line-height: 1.15; color: var(--text-0); } .font-mono, .mono { font-family: var(--mono); font-feature-settings: 'tnum' 1; font-variant-numeric: tabular-nums; } .num { font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: 700; } .lbl { font-family: var(--mono); font-size: 10px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-2); } ::selection { background: var(--accent); color: var(--text-primary); } ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: var(--bg-primary); } ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: var(--border-focus); } /* ───────────────────────────────────────────────────────── The Diagonal Cut — signature visual motif ───────────────────────────────────────────────────────── */ .diagonal-cut { position: relative; isolation: isolate; } .diagonal-cut::before { content: ''; position: absolute; inset: 0; background: linear-gradient(var(--diagonal-angle), transparent 0%, var(--accent-glow) 100%); pointer-events: none; z-index: 0; border-radius: inherit; } .diagonal-cut > * { position: relative; z-index: 1; } .diagonal-cut-strong::before { background: linear-gradient(var(--diagonal-angle), transparent 0%, rgba(26, 74, 58, 0.20) 100%); } /* Radial accent glow — for hero backgrounds */ .radial-glow { background: radial-gradient(ellipse 60% 50% at 50% 30%, var(--accent-glow), transparent 70%), var(--bg-primary); } /* ───────────────────────────────────────────────────────── Surfaces ───────────────────────────────────────────────────────── */ .surface { background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius-lg); transition: transform 200ms ease, border-color 200ms ease, background 200ms ease; } .surface-hover:hover { background: var(--bg-surface-hover); border-color: var(--border-focus); transform: translateY(-2px); } .surface-elevated { background: var(--bg-elevated); border: 1px solid var(--border-focus); border-radius: var(--radius-lg); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); } /* ───────────────────────────────────────────────────────── Buttons ───────────────────────────────────────────────────────── */ .btn-primary { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 24px; background: var(--accent); color: var(--text-primary); border: 1px solid var(--accent-light); border-radius: var(--radius-md); font-weight: 600; font-size: 14px; letter-spacing: 0.01em; cursor: pointer; transition: background 200ms ease, transform 100ms ease, box-shadow 200ms ease; text-decoration: none; } .btn-primary:hover { background: var(--accent-light); box-shadow: 0 4px 16px var(--accent-glow); } .btn-primary:active { transform: scale(0.98); } .btn-primary:disabled { opacity: 0.4; cursor: not-allowed; background: var(--accent); box-shadow: none; } .btn-ghost { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 24px; background: transparent; color: var(--text-primary); border: 1px solid var(--border); border-radius: var(--radius-md); font-weight: 500; font-size: 14px; cursor: pointer; transition: border-color 200ms ease, background 200ms ease; text-decoration: none; } .btn-ghost:hover { border-color: var(--border-focus); background: var(--bg-surface); } /* ───────────────────────────────────────────────────────── Inputs ───────────────────────────────────────────────────────── */ .input-field { width: 100%; padding: 12px 16px; background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius-md); color: var(--text-primary); font-family: inherit; font-size: 14px; transition: border-color 200ms ease, background 200ms ease; } .input-field::placeholder { color: var(--text-tertiary); } .input-field:focus { outline: none; border-color: var(--accent-light); background: var(--bg-elevated); } /* ───────────────────────────────────────────────────────── Grade tier helpers ───────────────────────────────────────────────────────── */ .grade-a-tier { color: var(--grade-a); } .grade-b-tier { color: var(--grade-b); } .grade-c-tier { color: var(--grade-c); } .grade-d-tier { color: var(--grade-d); } .grade-a-bg { background: rgba(0, 200, 150, 0.10); border-color: rgba(0, 200, 150, 0.30); } .grade-b-bg { background: rgba(74, 158, 255, 0.10); border-color: rgba(74, 158, 255, 0.30); } .grade-c-bg { background: rgba(255, 179, 71, 0.10); border-color: rgba(255, 179, 71, 0.30); } .grade-d-bg { background: rgba(255, 107, 107, 0.10); border-color: rgba(255, 107, 107, 0.30); } /* ───────────────────────────────────────────────────────── Animations ───────────────────────────────────────────────────────── */ @keyframes grade-reveal { 0% { transform: scale(0.85); opacity: 0; } 60% { transform: scale(1.04); opacity: 1; } 100% { transform: scale(1.00); opacity: 1; } } .animate-grade { animation: grade-reveal 400ms cubic-bezier(0.34, 1.56, 0.64, 1) both; } @keyframes blur-pulse { 0%, 100% { opacity: 0.65; } 50% { opacity: 0.85; } } .animate-blur-pulse { animation: blur-pulse 3s ease-in-out 1; } @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } .shimmer-loading { background: linear-gradient( 90deg, var(--accent) 0%, var(--accent-light) 50%, var(--accent) 100% ); background-size: 200% 100%; animation: shimmer 1.5s linear infinite; } /* DS1 (§4 — kill loading walls) — the ONE skeleton primitive. Layout-matched dark shimmer blocks replace every text-wall loader. Tokenized (no one-off CSS); the global prefers-reduced-motion rule freezes the sweep to a static, still-visible surface — arrives-then-rests, honored globally. */ .vyndr-skeleton { background: linear-gradient( 90deg, var(--bg-surface) 0%, var(--bg-surface-hover) 50%, var(--bg-surface) 100% ); background-size: 200% 100%; animation: shimmer 1.5s linear infinite; border-radius: 8px; } /* S6 (A1 board, LCP) — floored at a VISIBLE state (.6), matching the S33 entrance-keyframe rule (a paused frame is never invisible). Starting at opacity 0 also delayed the hero heading's first contentful paint — an invisible element isn't an LCP candidate until it becomes visible, so the old 0-start pushed the landing LCP by the animation delay + duration. */ @keyframes fade-up { 0% { opacity: 0.6; transform: translateY(8px); } 100% { opacity: 1; transform: translateY(0); } } @keyframes skeleton-pulse { 0%, 100% { opacity: 0.4; } 50% { opacity: 0.7; } } @keyframes phosphor-pulse { 0%, 100% { opacity: 0.6; transform: scaleX(0.6); } 50% { opacity: 1; transform: scaleX(1); } } .animate-fade-up { animation: fade-up 400ms ease-out both; } .stagger-1 { animation-delay: 50ms; } .stagger-2 { animation-delay: 100ms; } .stagger-3 { animation-delay: 150ms; } .stagger-4 { animation-delay: 200ms; } .stagger-5 { animation-delay: 250ms; } .stagger-6 { animation-delay: 300ms; } @keyframes ticker-scroll { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } } .animate-ticker { animation: ticker-scroll 60s linear infinite; } .animate-ticker:hover { animation-play-state: paused; } /* ───────────────────────────────────────────────────────── Tier blur (the conversion engine) ───────────────────────────────────────────────────────── */ .tier-locked { position: relative; filter: blur(8px); user-select: none; pointer-events: none; animation: blur-pulse 3s ease-in-out 1; } .tier-locked-overlay { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background: linear-gradient(180deg, transparent 0%, rgba(10, 10, 15, 0.6) 60%, rgba(10, 10, 15, 0.85) 100%); border-radius: inherit; z-index: 2; padding: 24px; text-align: center; } /* ───────────────────────────────────────────────────────── Utilities ───────────────────────────────────────────────────────── */ .text-balance { text-wrap: balance; } .text-pretty { text-wrap: pretty; } .divider { height: 1px; background: var(--border); margin: 16px 0; } /* ───────────────────────────────────────────────────────── Texture layers — seasoning, not the meal ───────────────────────────────────────────────────────── */ .tex-scan { position: relative; isolation: isolate; } .tex-scan::after { content: ""; position: absolute; inset: 0; pointer-events: none; background-image: repeating-linear-gradient( to bottom, rgba(255,255,255,var(--scan-opacity)) 0px, rgba(255,255,255,var(--scan-opacity)) 1px, transparent 1px, transparent 3px ); z-index: 2; mix-blend-mode: screen; } .tex-sweep { position: relative; overflow: hidden; isolation: isolate; } .tex-sweep::before { content: ""; position: absolute; left: 0; right: 0; height: 140px; top: -140px; pointer-events: none; background: linear-gradient( 180deg, transparent, rgba(0, 255, 184, var(--sweep-opacity)) 50%, transparent ); z-index: 1; animation: crt-sweep 7s ease-in-out infinite; mix-blend-mode: screen; } @keyframes crt-sweep { 0% { transform: translateY(0); opacity: 0; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { transform: translateY(120vh); opacity: 0; } } .tex-vignette { position: relative; } .tex-vignette::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: radial-gradient(ellipse at center, transparent 60%, rgba(0,0,0,0.45) 100%); z-index: 3; } .tex-slash { position: relative; } .tex-slash::before { content: ""; position: absolute; inset: 0; pointer-events: none; background: linear-gradient( -2deg, transparent 0%, rgba(0, 212, 160, var(--slash-opacity)) 50%, transparent 100% ); z-index: 1; } .tex-grain::after { content: ""; position: fixed; inset: 0; pointer-events: none; background-image: url("data:image/svg+xml;utf8,"); opacity: var(--grain-opacity); z-index: 999; mix-blend-mode: overlay; } /* Page-level CRT vignette, scales with --glitch */ body.tex-grain::before { content: ""; position: fixed; inset: 0; pointer-events: none; background: radial-gradient(ellipse 110% 90% at 50% 50%, transparent 50%, rgba(0,0,0,calc(0.15 + 0.20 * var(--glitch))) 100%); z-index: 998; } /* ───────────────────────────────────────────────────────── RGB split — broadcast misalignment effect ───────────────────────────────────────────────────────── */ .rgb-split { position: relative; display: inline-block; } .rgb-split::before, .rgb-split::after { content: attr(data-text); position: absolute; inset: 0; pointer-events: none; } .rgb-split::before { color: rgba(255, 60, 60, 0.55); transform: translateX(calc(-1 * var(--rgb-shift))); mix-blend-mode: screen; } .rgb-split::after { color: rgba(60, 120, 255, 0.55); transform: translateX(var(--rgb-shift)); mix-blend-mode: screen; } /* ───────────────────────────────────────────────────────── Wordmark — VYNDR with RGB-split letters, green-glow R, blinking cursor ───────────────────────────────────────────────────────── */ .wordmark { font-family: var(--ibm-mono); font-weight: 800; letter-spacing: 0.10em; display: inline-flex; align-items: center; line-height: 1; position: relative; } .wm-letter { position: relative; display: inline-block; } .wm-letter::before, .wm-letter::after { content: attr(data-text); position: absolute; inset: 0; pointer-events: none; display: inline-block; } .wm-letter::before { color: rgba(255, 60, 60, 0.55); transform: translateX(calc(-1px - var(--rgb-shift))); mix-blend-mode: screen; } .wm-letter::after { color: rgba(60, 120, 255, 0.55); transform: translateX(calc(1px + var(--rgb-shift))); mix-blend-mode: screen; } .wordmark .vynd { color: var(--text-0); } .wordmark .r { color: var(--grade-a); text-shadow: 0 0 calc(var(--wm-size, 32px) * 0.25) rgba(0, 212, 160, 0.6); position: relative; } .wordmark .r::before { color: rgba(255, 60, 60, 0.45); } .wordmark .r::after { color: rgba(0, 255, 184, 0.60); } .wm-cursor { display: inline-block; width: 0.16em; height: 0.9em; margin-left: 0.18em; background: var(--grade-a); box-shadow: 0 0 calc(var(--wm-size, 32px) * 0.3) rgba(0, 212, 160, 0.8); animation: cursor-blink 1.1s steps(2) infinite; } @keyframes cursor-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } .wm-anim { animation: wm-tear 5.5s ease-in-out infinite; } @keyframes wm-tear { 0%, 4%, 100% { transform: translateX(0); } 5% { transform: translateX(2px) skewX(-2deg); filter: hue-rotate(20deg); } 6% { transform: translateX(-1px); } 7% { transform: translateX(0); filter: none; } 47% { transform: translateX(0); } 48% { transform: translateX(-2px) skewX(1deg); } 49% { transform: translateX(1px); } 50% { transform: translateX(0); } } /* ───────────────────────────────────────────────────────── Glitch text — occasional horizontal tear, broadcast tinted ghosts ───────────────────────────────────────────────────────── */ .glitch-text { position: relative; display: inline-block; } .glitch-text::before, .glitch-text::after { content: attr(data-text); position: absolute; inset: 0; pointer-events: none; } .glitch-text::before { color: rgba(255, 60, 60, 0.7); transform: translateX(calc(-1px - var(--rgb-shift))); mix-blend-mode: screen; animation: glitch-shift-r 4s steps(40) infinite; } .glitch-text::after { color: rgba(60, 180, 255, 0.7); transform: translateX(calc(1px + var(--rgb-shift))); mix-blend-mode: screen; animation: glitch-shift-b 4s steps(40) infinite reverse; } @keyframes glitch-shift-r { 0%, 100% { clip-path: inset(0 0 0 0); transform: translateX(calc(-1px - var(--rgb-shift))); } 92% { clip-path: inset(20% 0 60% 0); transform: translateX(-3px); } 94% { clip-path: inset(60% 0 10% 0); transform: translateX(2px); } 96% { clip-path: inset(0 0 0 0); } } @keyframes glitch-shift-b { 0%, 100% { clip-path: inset(0 0 0 0); } 93% { clip-path: inset(45% 0 35% 0); transform: translateX(3px); } 95% { clip-path: inset(10% 0 70% 0); transform: translateX(-2px); } 97% { clip-path: inset(0 0 0 0); } } /* ───────────────────────────────────────────────────────── Transmission badge, frequency badge, phosphor underline ───────────────────────────────────────────────────────── */ .transmission { display: inline-flex; align-items: center; gap: 8px; padding: 4px 10px 4px 4px; font-family: var(--ibm-mono); font-size: 10px; font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase; color: var(--grade-a); background: rgba(0, 212, 160, 0.06); border: 1px solid rgba(0, 212, 160, 0.30); border-radius: 3px; } .transmission .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--grade-a); box-shadow: 0 0 8px var(--grade-a); animation: pulse-live 1.4s ease-in-out infinite; } @keyframes pulse-live { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.4; transform: scale(1.3); } } .freq-badge { display: inline-flex; align-items: center; gap: 10px; padding: 6px 10px; background: var(--bg-1); border: 1px solid var(--border); font-family: var(--ibm-mono); font-size: 10px; letter-spacing: 0.15em; text-transform: uppercase; color: var(--text-1); } .freq-badge .ch { color: var(--grade-a); font-weight: 700; } .phosphor-line { height: 2px; background: linear-gradient(90deg, transparent, var(--grade-a), transparent); box-shadow: 0 0 8px rgba(0, 212, 160, 0.60); width: 100%; } /* The V watermark — Vendetta nod, hero background */ .v-watermark { position: absolute; font-family: var(--ibm-mono); font-weight: 800; font-size: 70vmin; line-height: 0.8; color: var(--acc-0); opacity: 0.06; letter-spacing: -0.05em; user-select: none; pointer-events: none; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 0; } /* CRT phosphor glow utility for grade letters */ .grade-glow-aplus { color: var(--grade-aplus); text-shadow: 0 0 18px var(--grade-aplus), 0 0 36px rgba(0, 255, 184, 0.40); } .grade-glow-a { color: var(--grade-a); text-shadow: 0 0 14px rgba(0, 212, 160, 0.70), 0 0 30px rgba(0, 212, 160, 0.30); } .grade-glow-b { color: var(--grade-b); text-shadow: 0 0 12px rgba(74, 158, 255, 0.60), 0 0 24px rgba(74, 158, 255, 0.20); } .grade-glow-c { color: var(--grade-c); text-shadow: 0 0 12px rgba(255, 179, 71, 0.55); } .grade-glow-d { color: var(--grade-d); text-shadow: 0 0 12px rgba(255, 82, 82, 0.55); } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } /* ───────────────────────────────────────────────────────── RTL support (Session 12 — Arabic). Toggled by . The root layout server- resolves the locale and sets the attribute; these rules flip the few directional surfaces (nav flex direction, sidebar drawers, list markers) without inverting the whole grid (the Bloomberg-style data tables stay LTR even in RTL mode — numbers read left-to-right regardless). ───────────────────────────────────────────────────────── */ [dir="rtl"] body { text-align: right; } [dir="rtl"] .nav-links, [dir="rtl"] .nav-desktop { flex-direction: row-reverse; } [dir="rtl"] .mono, [dir="rtl"] [class*="font-mono"] { /* Monospace numeric blocks stay LTR — financial data reads left-to-right in every locale by convention. */ direction: ltr; text-align: right; unicode-bidi: isolate; } [dir="rtl"] .pricing-grid { direction: rtl; } [dir="rtl"] .btn-primary, [dir="rtl"] .btn-ghost { /* Buttons stay LTR so chevrons / arrows render predictably. */ unicode-bidi: isolate; } /* Session 14 — shimmer keyframe used by Slate skeleton placeholders and any other loading surface that wants the same Bloomberg-style subtle motion. The animation runs over a 200%-wide gradient background; advancing -100% → 100% slides the highlight band across the element. */ @keyframes vyndr-shimmer { 0% { background-position: -100% 0; } 100% { background-position: 100% 0; } } /* ============================================================ VYNDR 2.0 DESIGN SYSTEM (§4 — ported verbatim from vyndr.css) Glitch + animation system. Applied ONLY to chrome (wordmark, headers, dividers, loaders, the living layer). DATA NEVER GLITCHES. Where keyframe/class names collide with the legacy block above, these definitions are authoritative (later wins). ============================================================ */ /* ===================== SCANLINE TEXTURE ===================== */ .scanlines, .scanlines-bg { position: relative; } .scanlines::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: repeating-linear-gradient(0deg, transparent 50%, rgba(0,0,0,1) 50%); background-size: 100% 2px; opacity: var(--scan-op); z-index: 1; } /* Intelligence surface (Level 3) — green + drifting scanlines: "VYNDR is speaking" */ .intel-surface { position: relative; background: linear-gradient(160deg, var(--acc-0), var(--acc-1)); overflow: hidden; } .intel-surface::after { content: ""; position: absolute; inset: -4px 0 -4px 0; pointer-events: none; background: repeating-linear-gradient(0deg, transparent 0 2px, rgba(0,0,0,0.85) 2px 4px); opacity: calc(var(--scan-op) * 1.25 + 0.01); animation: scan-drift 4s linear infinite; z-index: 1; } @keyframes scan-drift { from { transform: translateY(0); } to { transform: translateY(4px); } } /* ===================== WORDMARK GLITCH ===================== */ .wm { position: relative; display: inline-block; flex: none; white-space: nowrap; font-family: var(--sans); font-weight: 800; letter-spacing: 0.04em; color: var(--text-0); text-shadow: 0 0 1px rgba(232,232,240,.25); animation: wm-tear 5s steps(1, end) infinite; } .wm::before, .wm::after { content: attr(data-text); position: absolute; top: 0; left: 0; width: 100%; pointer-events: none; opacity: calc(0.92 * var(--glitch)); } .wm::before { color: #ff2d6f; transform: translateX(calc(-1.9px * var(--glitch))); animation: glitch-shift-r 5s steps(1, end) infinite; mix-blend-mode: screen; } .wm::after { color: #2dffd5; transform: translateX(calc(1.9px * var(--glitch))); animation: glitch-shift-b 5s steps(1, end) infinite; mix-blend-mode: screen; } /* the locked green R — permanent phosphor highlight */ .wm-r { position: relative; z-index: 4; color: var(--g-a); text-shadow: 0 0 9px rgba(0,212,160,.7), 0 0 22px rgba(0,212,160,.38); } /* live terminal caret after the R — alive, mid-type flicker */ .wm-cursor { position: relative; z-index: 4; display: inline-block; width: 0.11em; height: 0.82em; margin-left: 0.14em; transform: translateY(0.04em); background: var(--g-a); box-shadow: 0 0 9px rgba(0,212,160,.9), 0 0 18px rgba(0,212,160,.4); animation: wm-caret 1.15s steps(1, end) infinite; } @keyframes wm-caret { 0%, 44% { opacity: 1; } 45%, 55% { opacity: 0.12; } 56%, 70% { opacity: 1; } 71%, 100% { opacity: 0.12; } } /* BETA clearance pill */ .wm-beta { font-family: var(--mono); font-size: 11px; font-weight: 700; letter-spacing: 0.2em; color: var(--g-a); border: 1px solid color-mix(in srgb, var(--g-a) 50%, transparent); background: rgba(0,212,160,.08); padding: 3px 7px 3px 9px; border-radius: 5px; line-height: 1; box-shadow: 0 0 16px -5px rgba(0,212,160,.6); } @keyframes wm-tear { 0%, 4%, 100% { transform: translate(0); } 5% { filter: hue-rotate(40deg) brightness(1.15); transform: translate(calc(2.4px*var(--glitch))) skew(calc(-3deg*var(--glitch))); } 6% { filter: brightness(1.05); transform: translate(calc(-1.4px*var(--glitch))) skew(calc(1deg*var(--glitch))); } 7% { filter: none; transform: translate(0); } 47% { transform: translate(0); } 48% { filter: hue-rotate(-30deg) brightness(1.12); transform: translate(calc(-2.4px*var(--glitch))) skew(calc(1.5deg*var(--glitch))); } 49% { transform: translate(calc(1.4px*var(--glitch))); } 50% { filter: none; transform: translate(0); } } @keyframes glitch-shift-r { 0%,4%,8%,46%,52%,100% { clip-path: inset(0 0 0 0); transform: translateX(calc(-1.5px*var(--glitch))); } 5% { clip-path: inset(10% 0 60% 0); transform: translateX(calc(-4px*var(--glitch))); } 6% { clip-path: inset(40% 0 20% 0); transform: translateX(calc(3px*var(--glitch))); } 48% { clip-path: inset(20% 0 55% 0); transform: translateX(calc(-3px*var(--glitch))); } 49% { clip-path: inset(60% 0 10% 0); transform: translateX(calc(2px*var(--glitch))); } } @keyframes glitch-shift-b { 0%,4%,8%,46%,52%,100% { clip-path: inset(0 0 0 0); transform: translateX(calc(1.5px*var(--glitch))); } 5% { clip-path: inset(55% 0 15% 0); transform: translateX(calc(4px*var(--glitch))); } 6% { clip-path: inset(15% 0 50% 0); transform: translateX(calc(-3px*var(--glitch))); } 48% { clip-path: inset(50% 0 25% 0); transform: translateX(calc(3px*var(--glitch))); } 49% { clip-path: inset(5% 0 70% 0); transform: translateX(calc(-2px*var(--glitch))); } } /* Section-header glitch on hover */ .glitch-hover { position: relative; display: inline-block; } .glitch-hover:hover { animation: head-tear 0.32s steps(1,end) 1; } @keyframes head-tear { 0% { transform: translate(0); text-shadow: none; } 25% { transform: translate(calc(2px*var(--glitch))) skew(calc(-3deg*var(--glitch))); text-shadow: calc(-2px*var(--glitch)) 0 #ff2d6f, calc(2px*var(--glitch)) 0 #2dffd5; } 50% { transform: translate(calc(-1px*var(--glitch))); } 75% { transform: translate(calc(1px*var(--glitch))); text-shadow: calc(1px*var(--glitch)) 0 #2dffd5; } 100% { transform: translate(0); text-shadow: none; } } /* ===================== CRT SWEEP ===================== */ .crt-sweep { position: fixed; left: 0; right: 0; top: 0; height: 38vh; pointer-events: none; z-index: 9999; background: linear-gradient(180deg, transparent 0%, rgba(0,212,160,0.04) 40%, rgba(0,255,184,0.14) 50%, rgba(255,255,255,0.10) 51%, rgba(0,212,160,0.04) 60%, transparent 100%); animation: crt-sweep 0.7s ease-in 1; } @keyframes crt-sweep { 0% { opacity: 0; transform: translateY(-40vh); } 10% { opacity: 1; } 90% { opacity: 1; } 100% { opacity: 0; transform: translateY(120vh); } } /* Local sweep within a card (grade-card reveal) */ .crt-sweep-local { position: absolute; left: 0; right: 0; top: 0; height: 60%; pointer-events: none; z-index: 6; background: linear-gradient(180deg, transparent, rgba(0,255,184,0.12) 48%, rgba(255,255,255,0.10) 50%, rgba(0,255,184,0.08) 52%, transparent); animation: crt-sweep-local 0.65s ease-in 1; } @keyframes crt-sweep-local { 0% { opacity: 0; transform: translateY(-60%); } 15% { opacity: 1; } 100% { opacity: 0; transform: translateY(220%); } } /* ===================== PHOSPHOR PULSE ===================== */ .phosphor-cursor { display: inline-block; width: 9px; height: 1.05em; background: var(--g-a); margin-left: 2px; vertical-align: text-bottom; box-shadow: 0 0 8px rgba(0,212,160,0.8); animation: phosphor-pulse 1.05s ease-in-out infinite; } .phosphor-cursor.amber { background: var(--amber); box-shadow: var(--amber-glow); } @keyframes phosphor-pulse { 0%,100% { opacity: 0.5; transform: scaleX(0.6); } 50% { opacity: 1; transform: scaleX(1); } } /* ===================== GRADE REVEAL ===================== */ @keyframes grade-reveal { 0% { transform: scale(0.82); } 60% { transform: scale(1.05); } 100% { transform: scale(1); } } .grade-reveal { animation: grade-reveal 0.42s cubic-bezier(.2,1.3,.4,1) both; } /* ===================== TICKER (DS5 — punctuated stillness) ===================== The ticker RESTS. It no longer marquee-scrolls (constant motion reads cheap, Part 0 law #1). It holds each item for --ticker-hold (≥4s) so it can be READ, and the ONLY motion is a change-only cross-fade + a brief data-update pulse when the item advances. The legacy .ticker-track marquee is retired. */ @keyframes ticker-scroll { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } } .ticker-track { display: inline-flex; white-space: nowrap; animation: ticker-scroll 38s linear infinite; } /* The resting strip — a static ranked row, no idle animation. */ .ticker-rest { display: flex; align-items: center; min-width: 0; overflow: hidden; white-space: nowrap; } /* Change-only cross-fade as the held item advances (sub-200ms, weighted). */ @keyframes ticker-swap { from { opacity: 0.55; } to { opacity: 1; } } .ticker-item-enter { animation: ticker-swap var(--motion-transition) ease-out both; } /* Change-only green pulse — the terminal reacting to a fresh head event. */ @keyframes ticker-pulse { 0% { background: color-mix(in srgb, var(--g-a) 22%, transparent); } 100% { background: transparent; } } .ticker-pulse { animation: ticker-pulse var(--motion-data) ease-out 1; } /* ===================== LIVE PULSE ===================== */ .live-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--live); box-shadow: 0 0 0 0 rgba(255,59,59,0.7); animation: live-pulse 1.4s ease-out infinite; } @keyframes live-pulse { 0% { box-shadow: 0 0 0 0 rgba(255,59,59,0.6); } 70% { box-shadow: 0 0 0 7px rgba(255,59,59,0); } 100% { box-shadow: 0 0 0 0 rgba(255,59,59,0); } } /* ===================== DESK SHOWCASE (DS5 pricing hero) ===================== */ /* Balanced two-column at desktop (pitch | real feature visuals), single column on mobile — kills the dead right-half of the old pricing page (#8). */ .desk-showcase { grid-template-columns: 1fr; } @media (min-width: 900px) { .desk-showcase { grid-template-columns: 1.05fr 1fr; } } /* ===================== LINE FLASH (odds cell changes) ===================== */ @keyframes flash-up { 0% { background: rgba(0,212,160,0.45); } 100% { background: transparent; } } @keyframes flash-down { 0% { background: rgba(255,82,82,0.45); } 100% { background: transparent; } } .flash-up { animation: flash-up 0.6s ease-out 1; } .flash-down { animation: flash-down 0.6s ease-out 1; } /* ===================== UTILITY ===================== */ .mono { font-family: var(--mono); } .label { font-family: var(--mono); text-transform: uppercase; font-size: 11px; letter-spacing: 0.12em; color: var(--text-1); } .amber-glow { color: var(--amber); text-shadow: var(--amber-glow); } /* ===================== LIVING LAYER (the brain) ===================== */ /* EKG heartbeat strip */ @keyframes ekg-scroll { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } } /* DS5 — the EKG is now a STATIC readout trace (no idle scroll). The header's ONE idle proof-of-life is the single SIGNAL-LIVE live-dot; the heartbeat waveform is a frozen instrument face, not constant motion (Part 4). */ .ekg-track { display: inline-flex; } /* Neural node pulse */ @keyframes node-pulse { 0%, 100% { opacity: 0.35; r: 2.4; } 50% { opacity: 1; r: 3.4; } } .brain-node { animation: node-pulse 2.6s ease-in-out infinite; transform-box: fill-box; transform-origin: center; } /* Synapse traveling pulse along connections */ @keyframes synapse-travel { from { stroke-dashoffset: 26; } to { stroke-dashoffset: 0; } } .brain-link { stroke-dasharray: 3 23; animation: synapse-travel 2.2s linear infinite; } /* Number tick pop when a counter increments */ @keyframes count-tick { 0% { transform: translateY(0); color: var(--text-0); } 30% { transform: translateY(-2px); color: var(--g-a); text-shadow: 0 0 10px rgba(0,212,160,.6); } 100% { transform: translateY(0); } } .count-tick { animation: count-tick 0.5s ease-out 1; } /* Factor ignite — sequential light-up during grade processing */ @keyframes factor-ignite { 0% { opacity: 0; transform: translateX(-6px); } 100% { opacity: 1; transform: translateX(0); } } .factor-ignite { animation: factor-ignite 0.34s ease-out both; } /* Signal feed item entering */ @keyframes signal-in { 0% { opacity: 0; transform: translateY(-8px); background: rgba(0,212,160,.16); } 100% { opacity: 1; transform: translateY(0); background: transparent; } } .signal-in { animation: signal-in 0.6s ease-out 1; } /* Processing scan bar (grade thinking) */ @keyframes proc-scan { 0% { transform: translateY(-100%); opacity: 0; } 20% { opacity: 1; } 80% { opacity: 1; } 100% { transform: translateY(280%); opacity: 0; } } .proc-scan { animation: proc-scan 1.1s ease-in-out infinite; } /* Soft data-stream blink */ @keyframes data-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } } .data-blink { animation: data-blink 1.6s ease-in-out infinite; } /* ── Entrance keyframes — each FLOORS at the visible state so a paused/ backgrounded frame never renders invisible (learned the hard way, §4). ── */ @keyframes toast-in { 0% { transform: translateX(24px); } 100% { transform: translateX(0); } } .toast-in { animation: toast-in 0.26s cubic-bezier(.2,1,.4,1) both; } @keyframes sheet-up { 0% { transform: translateY(40px); } 100% { transform: translateY(0); } } .sheet-up { animation: sheet-up 0.24s ease-out both; } @keyframes cmd-in { 0% { transform: translateY(-10px); } 100% { transform: translateY(0); } } .cmd-in { animation: cmd-in 0.18s ease-out both; } @keyframes fade-in { from { opacity: 0.6; } to { opacity: 1; } } .fade-in { animation: fade-in 0.2s ease-out both; } /* Reduced-motion: kill the living layer + glitch chrome outright */ @media (prefers-reduced-motion: reduce) { .wm, .wm::before, .wm::after, .ticker-track, .live-dot, .ticker-item-enter, .ticker-pulse, .intel-surface::after, .phosphor-cursor, .ekg-track, .brain-node, .brain-link, .proc-scan, .data-blink { animation: none !important; } } /* ============================================================ ACCESSIBILITY + PREFERENCES (§10 — driven by ) ============================================================ */ :focus-visible { outline: 2px solid var(--g-a); outline-offset: 2px; border-radius: 3px; } /* Reduce motion (manual toggle, independent of OS setting) */ html[data-motion="reduced"] *, html[data-motion="reduced"] *::before, html[data-motion="reduced"] *::after { animation-duration: 0.001s !important; animation-iteration-count: 1 !important; transition-duration: 0.001s !important; scroll-behavior: auto !important; } /* High contrast */ html[data-contrast="high"] { --text-0: #ffffff; --text-1: #cdcde0; --text-2: #a0a0bc; --border: #4c4c6a; --border-hi: #70709a; --bg-0: #000005; --bg-1: #0c0c16; --bg-2: #16161f; } html[data-contrast="high"] .scanlines::after, html[data-contrast="high"] .intel-surface::after { opacity: 0.015 !important; } /* Text size — zoom scales the whole UI predictably */ html[data-text="sm"] body { zoom: 0.92; } html[data-text="lg"] body { zoom: 1.12; } html[data-text="xl"] body { zoom: 1.25; } /* Colorblind-safe — retunes red→orange; meaning never rests on color alone */ html[data-cb="1"] { --miss: #fe6100; --g-d: #fe6100; --live: #fe6100; --s-nba: #ff6f3c; } html[data-cb="1"] .cb-neg { text-decoration: underline; text-underline-offset: 2px; } html[data-cb="1"] .cb-pos { text-decoration: underline; text-underline-offset: 2px; } /* Readable font — high-legibility stack for dyslexia/low-vision */ html[data-font="readable"] { --sans: "Atkinson Hyperlegible", "Verdana", "Trebuchet MS", system-ui, sans-serif; --mono: "Atkinson Hyperlegible Mono", "DejaVu Sans Mono", ui-monospace, monospace; letter-spacing: 0.01em; } html[data-font="readable"] .wm::before, html[data-font="readable"] .wm::after { opacity: 0.45 !important; } /* ============================================================ MOBILE PARITY (§6, §F — Session 37) Mobile is the primary (PWA) layout; desktop is the enhancement. ============================================================ */ /* The 5-tab bar is mobile-only — hidden once the desktop nav has room. */ @media (min-width: 768px) { .mobile-tab-bar { display: none !important; } } /* Clear the fixed bottom tab bar (64px + iOS safe area) so content isn't hidden behind it. Desktop keeps the standard 80px footer breathing room. */ @media (max-width: 767px) { main { padding-bottom: calc(84px + env(safe-area-inset-bottom, 0px)) !important; } } /* Grade hero zone: full-width, slightly smaller letter on phones. */ @media (max-width: 640px) { .grade-hero { font-size: 80px !important; } /* Session 43 — player-profile hero name was clipping (e.g. "Wembanyam") at 390px. Shrink + wrap instead of overflowing the flex row. */ .player-hero-name { font-size: 24px !important; overflow-wrap: anywhere; } } /* Book-line tables scroll horizontally on small screens with a sticky first (team/book) column so the matchup stays anchored while prices scroll. */ @media (max-width: 640px) { .game-lines-grid { overflow-x: auto; -webkit-overflow-scrolling: touch; } .game-lines-grid .team-col { position: sticky; left: 0; z-index: 1; background: var(--bg-1); } } /* Terminal's multi-column grid stacks on mobile. */ @media (max-width: 768px) { .terminal-grid { grid-template-columns: 1fr !important; } /* Pitcher-identity (Wave 5B): identity + arsenal columns stack; the arsenal table keeps its own internal grid. */ .parsenal-grid { grid-template-columns: 1fr !important; } } /* ── Session 59 (work-order 3.2) — overflow containment at 390px ──────── */ /* Horizontal scroll is CONTAINED to strips (tabs, chips, book tables) — the document itself never scrolls sideways. */ @media (max-width: 767px) { html, body { overflow-x: hidden; } } /* Card meta: venue drops FIRST on phones so time/pitchers never truncate. */ @media (max-width: 639px) { .gc-venue { display: none !important; } } /* GAME LINES: full 8-book table on desktop; on phones it collapses to the best-line summary row (.gl-summary) with an "N BOOKS ⌄" expander that reveals the full table (.gl-full.gl-expanded). */ .gl-summary { display: none; } @media (max-width: 639px) { .gl-summary { display: flex !important; } .gl-full:not(.gl-expanded) { display: none !important; } .gl-full.gl-expanded { margin-top: 10px; } } /* ============================================================ DESIGN COMPLETION TRAIN — M1 MOBILE FOUNDATION (2026-07-16) Now RECONCILED against Design's drawn mobile (specs/design-reference/ MOBILE-SPEC.md — Design DREW 390×844, so these implement it, not infer it). Design agrees: no EKG on mobile, ≥44px tap targets. Design OVERRIDES the header shape — mobile is an app bar (logo + sync clock) + breadth strip + bottom wire, not the collapsed heartbeat bar; that full recomposition is M1b (the board/shell surface). Built to Design's mobile spec, but still VISUALLY UNVERIFIED at 390px (WSL2↔Chrome unreachable — Chrome audit is eyes). ============================================================ */ /* Mobile app-bar clock (M1b) — Design's mobile drops SIGNAL LIVE + EKG + graded + the SYNC label and shows a single right-aligned clock that RESTS as a wall clock and REACTS to amber/STALE off the real freshness tier (Hybrid: clock = stillness, STALE = punctuation; never silently stale on mobile). Full app-bar recomposition (merge into the logo row + breadth strip + bottom wire) is the next M1b shell increment. */ @media (max-width: 767px) { .hb-ekg, .hb-desktop { display: none !important; } .hb-mobile-clock { display: inline-flex !important; } .heartbeat-bar { height: 28px !important; gap: 10px !important; padding: 0 12px !important; justify-content: flex-end; } .heartbeat-bar > span { white-space: nowrap; } } /* Tap targets — the PRIMARY button component meets the 44px touch minimum. Scoped to .vbtn only (not dense in-row data chips — density is a feature; per-surface tap-target review rides with each M1b surface). */ @media (max-width: 767px) { .vbtn { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; } } /* (removed .m-hero — RECONCILE 2026-07-16: Design's mobile uses CONTEXT- SPECIFIC hero sizes, not one generic clamp: 74px grade tier, 40px live tier, 24px /u stats. Those land per-surface in M1b, not as a global class.) */ /* ============================================================ Session 60 (night2/E) — §7 reveal choreography. Stamp slam + 90ms staggered context panels. Entrance keyframes floor at a VISIBLE state (Phase-0 rule: a paused frame is never invisible), and reduced-motion kills the choreography entirely (ProcessingGrade also skips straight to the card). ============================================================ */ @keyframes stamp-in { 0% { opacity: .5; transform: rotate(-7deg) scale(1.7); } 60% { opacity: 1; transform: rotate(-7deg) scale(0.96); } 100% { opacity: 1; transform: rotate(-7deg) scale(1); } } .stamp-in { animation: stamp-in .34s cubic-bezier(.2,1.6,.4,1) both; } @keyframes panel-in { from { opacity: .55; transform: translateY(7px); } to { opacity: 1; transform: translateY(0); } } .grade-reveal > div > * { animation: panel-in .3s ease-out both; } .grade-reveal > div > *:nth-child(2) { animation-delay: 90ms; } .grade-reveal > div > *:nth-child(3) { animation-delay: 180ms; } .grade-reveal > div > *:nth-child(4) { animation-delay: 270ms; } .grade-reveal > div > *:nth-child(5) { animation-delay: 360ms; } .grade-reveal > div > *:nth-child(6) { animation-delay: 450ms; } .grade-reveal > div > *:nth-child(7) { animation-delay: 540ms; } .grade-reveal > div > *:nth-child(n+8) { animation-delay: 630ms; } @media (prefers-reduced-motion: reduce) { .stamp-in, .grade-reveal > div > * { animation: none !important; } }