@import "tailwindcss"; /* ------------------------------------------------------------------ */ /* MedASR Bench — "Clinical Instrument" token system */ /* ------------------------------------------------------------------ */ :root { color-scheme: light; /* Palette — light "precision instrument": cool paper-white surfaces, ink text, deep violet accent (user-picked hue; deepened for light-mode contrast: oklch(0.50 0.17 290) = 6.1:1 on --bg). Built in OKLCH; all small-text pairings clear WCAG 4.5:1. Saturated hues are reserved for meaning (accent/warn/crit). NO green — repeatedly rejected by the user. */ --bg: #f9f9fb; --surface: #ffffff; --surface-2: #eeeef2; --line: rgba(36, 36, 60, 0.13); --text: #24242f; --muted: #62626f; --accent: #654abb; /* deep violet — sparingly */ --warn: #a15e00; /* contamination / caution only */ --crit: #c53637; /* critical / error only */ /* Derived tints — single-source: everything mixes from the tokens above */ --accent-dim: color-mix(in srgb, var(--accent) 12%, transparent); --warn-dim: color-mix(in srgb, var(--warn) 12%, transparent); --crit-dim: color-mix(in srgb, var(--crit) 10%, transparent); --grid-ink: rgba(36, 36, 80, 0.045); --grid-ink-major: rgba(36, 36, 80, 0.065); } @theme inline { /* Colors → Tailwind utilities (bg-bg, text-muted, border-line, …) */ --color-bg: var(--bg); --color-surface: var(--surface); --color-surface-2: var(--surface-2); --color-line: var(--line); --color-text: var(--text); --color-muted: var(--muted); --color-accent: var(--accent); --color-warn: var(--warn); --color-crit: var(--crit); --color-accent-dim: var(--accent-dim); --color-warn-dim: var(--warn-dim); --color-crit-dim: var(--crit-dim); /* Fonts → font-display / font-body / font-mono utilities. The var() chain resolves to next/font variables set on . */ --font-display: var(--font-display), "Instrument Serif", Georgia, serif; --font-body: var(--font-body), "Be Vietnam Pro", system-ui, sans-serif; --font-sans: var(--font-body), "Be Vietnam Pro", system-ui, sans-serif; --font-mono: var(--font-mono), "IBM Plex Mono", ui-monospace, SFMono-Regular, monospace; } /* ------------------------------------------------------------------ */ /* Base + atmosphere: graph-paper micro-grid + scanline layer */ /* ------------------------------------------------------------------ */ html { background-color: var(--bg); } body { /* solid color lives on ; body stays transparent so the fixed negative-z ambient glow layers (AmbientEffects) shine through the grid */ background-color: transparent; color: var(--text); font-family: var(--font-body), "Be Vietnam Pro", system-ui, sans-serif; /* graph-paper grid: 140px major rhythm over the 28px minor micro-grid */ background-image: linear-gradient(var(--grid-ink-major) 1px, transparent 1px), linear-gradient(90deg, var(--grid-ink-major) 1px, transparent 1px), linear-gradient(var(--grid-ink) 1px, transparent 1px), linear-gradient(90deg, var(--grid-ink) 1px, transparent 1px); background-size: 140px 140px, 140px 140px, 28px 28px, 28px 28px; -webkit-font-smoothing: antialiased; } /* ------------------------------------------------------------------ */ /* Grain texture — subtle film noise for tactile depth */ /* ------------------------------------------------------------------ */ body::before { content: ""; position: fixed; inset: 0; z-index: 29; pointer-events: none; opacity: 0.02; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); } /* scanline veil — fixed, non-interactive, layered above grain */ body::after { content: ""; position: fixed; inset: 0; z-index: 30; pointer-events: none; background: repeating-linear-gradient( to bottom, color-mix(in srgb, var(--text) 1.2%, transparent) 0px, color-mix(in srgb, var(--text) 1.2%, transparent) 1px, transparent 1px, transparent 3px ); } ::selection { background: var(--accent-dim); color: var(--text); } /* ------------------------------------------------------------------ */ /* Ambient light — fixed phosphor glow washes behind all content */ /* (rendered by components/ui/AmbientEffects; GPU-cheap transform drift) */ /* ------------------------------------------------------------------ */ @keyframes ambient-drift-a { 0%, 100% { transform: translate3d(0, 0, 0) scale(1); } 50% { transform: translate3d(4%, 3%, 0) scale(1.08); } } @keyframes ambient-drift-b { 0%, 100% { transform: translate3d(0, 0, 0) scale(1); } 50% { transform: translate3d(-3%, -4%, 0) scale(1.06); } } .ambient-glow { position: absolute; pointer-events: none; will-change: transform; } /* top-center wash — the room's key light */ .ambient-glow-top { top: -32vh; left: -12vw; right: -12vw; height: 78vh; background: radial-gradient( ellipse 55% 55% at 50% 45%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 70% ); animation: ambient-drift-a 70s ease-in-out infinite; } /* lower-right fill — faint counter-glow */ .ambient-glow-corner { right: -22vw; bottom: -30vh; width: 72vw; height: 75vh; background: radial-gradient( ellipse 50% 50% at 55% 55%, color-mix(in srgb, var(--accent) 5.5%, transparent), transparent 70% ); animation: ambient-drift-b 90s ease-in-out infinite; } /* ------------------------------------------------------------------ */ /* Glass morphism — translucent surfaces tuned to the lifted palette */ /* ------------------------------------------------------------------ */ /* layered as components so Tailwind utilities (bg-surface/55 …) can override the surface opacity per-instance */ @layer components { /* veil only: translucent surface + blur, no border (compose freely) */ .glass-veil { background: color-mix(in srgb, var(--surface) 66%, transparent); -webkit-backdrop-filter: blur(16px) saturate(1.2); backdrop-filter: blur(16px) saturate(1.2); } /* full glass panel: veil + hairline + machined top-edge relief */ .glass { background: color-mix(in srgb, var(--surface) 72%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.2); backdrop-filter: blur(14px) saturate(1.2); border: 1px solid var(--line); box-shadow: inset 0 1px 0 color-mix(in srgb, var(--text) 6%, transparent); } } /* ------------------------------------------------------------------ */ /* Hairline utilities (1px --line rules, the only "shadows" we use) */ /* ------------------------------------------------------------------ */ .hairline { border: 1px solid var(--line); /* machined top-edge relief — the only "shadow" beyond hairlines */ box-shadow: inset 0 1px 0 color-mix(in srgb, var(--text) 4%, transparent); } .hairline-t { border-top: 1px solid var(--line); } .hairline-b { border-bottom: 1px solid var(--line); } .hairline-l { border-left: 1px solid var(--line); } .hairline-r { border-right: 1px solid var(--line); } /* ------------------------------------------------------------------ */ /* Spring animations — physics-based motion */ /* ------------------------------------------------------------------ */ @keyframes spring-in { from { opacity: 0; transform: scale(0.95) translateY(12px); } to { opacity: 1; transform: scale(1) translateY(0); } } @keyframes spring-up { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } @keyframes soft-appear { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } /* Spring-based entrance animations */ .spring-in { animation: spring-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both; } .spring-up { animation: spring-up 0.55s cubic-bezier(0.22, 1, 0.36, 1) both; } .soft-appear { animation: soft-appear 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; } /* Improved stagger with spring feel */ .stagger-spring > * { animation: spring-up 0.5s cubic-bezier(0.22, 1, 0.36, 1) both; } .stagger-spring > :nth-child(1) { animation-delay: 0ms; } .stagger-spring > :nth-child(2) { animation-delay: 60ms; } .stagger-spring > :nth-child(3) { animation-delay: 120ms; } .stagger-spring > :nth-child(4) { animation-delay: 180ms; } .stagger-spring > :nth-child(5) { animation-delay: 240ms; } .stagger-spring > :nth-child(6) { animation-delay: 300ms; } .stagger-spring > :nth-child(7) { animation-delay: 360ms; } .stagger-spring > :nth-child(8) { animation-delay: 420ms; } /* ------------------------------------------------------------------ */ /* Spotlight border effect — card illumination on hover */ /* ------------------------------------------------------------------ */ .spotlight-card { position: relative; isolation: isolate; transition: border-color 0.3s ease, box-shadow 0.3s ease; } .spotlight-card::before { content: ""; position: absolute; inset: 0; border-radius: inherit; background: radial-gradient( 500px circle at var(--mouse-x, 50%) var(--mouse-y, 50%), color-mix(in srgb, var(--accent) 12%, transparent), transparent 40% ); opacity: 0; transition: opacity 0.4s ease; z-index: -1; pointer-events: none; } .spotlight-card:hover { border-color: color-mix(in srgb, var(--accent) 25%, transparent); box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent) 8%, transparent); } .spotlight-card:hover::before { opacity: 1; } /* Spotlight row for tables. IMPORTANT: no ::before/::after on this class — it sits on , and a generated box on a table row gets wrapped in an ANONYMOUS CELL, silently shifting every real cell one column right of the headers. The hover sweep is painted as the row's own background instead. */ .spotlight-row { transition: background-color 0.2s ease; } .spotlight-row:hover { background-color: var(--surface-2); background-image: linear-gradient( 90deg, transparent 0%, color-mix(in srgb, var(--accent) 4%, transparent) 50%, transparent 100% ); } /* ------------------------------------------------------------------ */ /* Tactile button states */ /* ------------------------------------------------------------------ */ .tactile { transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1), background-color 0.2s ease, border-color 0.2s ease; } .tactile:active { transform: scale(0.975) translateY(1px); } .tactile:hover:not(:disabled) { background-color: var(--surface-2); } /* Interactive elements with subtle lift */ .hover-lift { transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.2s ease; } .hover-lift:hover { transform: translateY(-2px); box-shadow: 0 4px 14px rgba(20, 20, 45, 0.1), inset 0 1px 0 color-mix(in srgb, var(--text) 4%, transparent); } /* ------------------------------------------------------------------ */ /* Accent glow effects */ /* ------------------------------------------------------------------ */ .glow-accent-subtle { box-shadow: 0 0 12px color-mix(in srgb, var(--accent) 10%, transparent); } /* ------------------------------------------------------------------ */ /* Edge accent bar — vertical phosphor line */ /* ------------------------------------------------------------------ */ .edge-accent { position: relative; } .edge-accent::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px; background: linear-gradient( to bottom, transparent, var(--accent) 20%, var(--accent) 80%, transparent ); opacity: 0.6; } /* Mono numerals everywhere data lives */ .num { font-family: var(--font-mono), "IBM Plex Mono", ui-monospace, monospace; font-variant-numeric: tabular-nums; } .tabular { font-variant-numeric: tabular-nums; } /* ------------------------------------------------------------------ */ /* Keyframes */ /* ------------------------------------------------------------------ */ @keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } } /* pair with pathLength={1} + stroke-dasharray: 1 on an SVG path */ @keyframes drawLine { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } } /* ambient ECG sweep: a short bright tracer cycling over the dim base path */ @keyframes sweep { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } } @keyframes shimmer { from { background-position: -200% 0; } to { background-position: 200% 0; } } @keyframes pulseDot { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.35; transform: scale(0.78); } } @keyframes scaleInX { from { transform: scaleX(0); } to { transform: scaleX(1); } } /* ------------------------------------------------------------------ */ /* Motion helpers — one orchestrated load per route */ /* ------------------------------------------------------------------ */ /* stagger container: children rise with 55ms steps */ .stagger > * { animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both; } .stagger > :nth-child(1) { animation-delay: 0ms; } .stagger > :nth-child(2) { animation-delay: 55ms; } .stagger > :nth-child(3) { animation-delay: 110ms; } .stagger > :nth-child(4) { animation-delay: 165ms; } .stagger > :nth-child(5) { animation-delay: 220ms; } .stagger > :nth-child(6) { animation-delay: 275ms; } .stagger > :nth-child(7) { animation-delay: 330ms; } .stagger > :nth-child(8) { animation-delay: 385ms; } .stagger > :nth-child(9) { animation-delay: 440ms; } .stagger > :nth-child(10) { animation-delay: 495ms; } .stagger > :nth-child(11) { animation-delay: 550ms; } .stagger > :nth-child(12) { animation-delay: 605ms; } /* manual stagger steps for non-sibling layouts */ .delay-1 { animation-delay: 55ms; } .delay-2 { animation-delay: 110ms; } .delay-3 { animation-delay: 165ms; } .delay-4 { animation-delay: 220ms; } /* skeleton block: surface placeholder swept by the shimmer keyframes */ .skeleton { position: relative; overflow: hidden; border-radius: 2px; background-color: var(--surface-2); background-image: linear-gradient( 100deg, transparent 40%, color-mix(in srgb, var(--text) 7%, transparent) 50%, transparent 60% ); background-size: 200% 100%; animation: shimmer 1.8s linear infinite; } /* shimmering surface for status=running chips */ .chip-shimmer { background-image: linear-gradient( 100deg, color-mix(in srgb, var(--accent) 7%, transparent) 40%, color-mix(in srgb, var(--accent) 22%, transparent) 50%, color-mix(in srgb, var(--accent) 7%, transparent) 60% ); background-size: 200% 100%; animation: shimmer 2.4s linear infinite; } .pulse-dot { animation: pulseDot 1.4s ease-in-out infinite; } .draw-line { stroke-dasharray: 1; animation: drawLine 1.4s cubic-bezier(0.4, 0, 0.2, 1) 0.15s both; } /* pair with pathLength={1}: 6%-long phosphor tracer, ~7s per pass */ .pulse-sweep { stroke-dasharray: 0.06 0.94; animation: sweep 7s linear infinite; } /* CI whisker draw-in: the micro-bar sweeps open left → right once */ .ci-draw { transform-origin: left center; animation: scaleInX 0.6s cubic-bezier(0.22, 1, 0.36, 1) both; } /* NOTE: scroll-driven entrances (animation-timeline: view()) were tried and removed — with fill-mode both, content whose timeline binds to the wrong scroll container (e.g. rows inside overflow-x-auto) can be stuck at opacity 0 forever. Entrances are load-orchestrated only. */ /* ------------------------------------------------------------------ */ /* Scroll-triggered animations (Intersection Observer based) */ /* ------------------------------------------------------------------ */ /* Base scroll animation classes */ .scroll-animate { opacity: 0; transform: translateY(24px); transition: opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1), transform 0.5s cubic-bezier(0.22, 1, 0.36, 1); } .scroll-animate.in-view { opacity: 1; transform: translateY(0); } /* Scroll scale animation */ .scroll-scale { opacity: 0; transform: scale(0.95); transition: opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1), transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); } .scroll-scale.in-view { opacity: 1; transform: scale(1); } /* Scroll slide animations */ .scroll-slide-left { opacity: 0; transform: translateX(-24px); transition: opacity 0.4s cubic-bezier(0.22, 1, 0.36, 1), transform 0.4s cubic-bezier(0.22, 1, 0.36, 1); } .scroll-slide-left.in-view { opacity: 1; transform: translateX(0); } .scroll-slide-right { opacity: 0; transform: translateX(24px); transition: opacity 0.4s cubic-bezier(0.22, 1, 0.36, 1), transform 0.4s cubic-bezier(0.22, 1, 0.36, 1); } .scroll-slide-right.in-view { opacity: 1; transform: translateX(0); } .scroll-slide-down { opacity: 0; transform: translateY(-24px); transition: opacity 0.4s cubic-bezier(0.22, 1, 0.36, 1), transform 0.4s cubic-bezier(0.22, 1, 0.36, 1); } .scroll-slide-down.in-view { opacity: 1; transform: translateY(0); } /* Scroll fade animation */ .scroll-fade { opacity: 0; transition: opacity 0.4s ease-out; } .scroll-fade.in-view { opacity: 1; } /* Stagger children on scroll */ .scroll-stagger > * { opacity: 0; transform: translateY(16px); transition: opacity 0.4s ease-out, transform 0.4s cubic-bezier(0.22, 1, 0.36, 1); } .scroll-stagger.in-view > *:nth-child(1) { opacity: 1; transform: translateY(0); transition-delay: 0ms; } .scroll-stagger.in-view > *:nth-child(2) { opacity: 1; transform: translateY(0); transition-delay: 60ms; } .scroll-stagger.in-view > *:nth-child(3) { opacity: 1; transform: translateY(0); transition-delay: 120ms; } .scroll-stagger.in-view > *:nth-child(4) { opacity: 1; transform: translateY(0); transition-delay: 180ms; } .scroll-stagger.in-view > *:nth-child(5) { opacity: 1; transform: translateY(0); transition-delay: 240ms; } .scroll-stagger.in-view > *:nth-child(6) { opacity: 1; transform: translateY(0); transition-delay: 300ms; } .scroll-stagger.in-view > *:nth-child(7) { opacity: 1; transform: translateY(0); transition-delay: 360ms; } .scroll-stagger.in-view > *:nth-child(8) { opacity: 1; transform: translateY(0); transition-delay: 420ms; } /* ------------------------------------------------------------------ */ /* Micro-interactions */ /* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */ /* Card depth variations */ /* ------------------------------------------------------------------ */ .depth-1 { box-shadow: 0 1px 3px rgba(20, 20, 45, 0.06); } .depth-2 { box-shadow: 0 2px 10px rgba(20, 20, 45, 0.07); } .depth-3 { box-shadow: 0 8px 28px rgba(20, 20, 45, 0.09), 0 2px 6px rgba(20, 20, 45, 0.05); } /* ------------------------------------------------------------------ */ /* Full-bleed breakout — a block wider than the page container, */ /* centered on the viewport (parent must be a centered max-w column) */ /* ------------------------------------------------------------------ */ @media (min-width: 80rem) { .breakout-wide { --breakout-w: min(100vw - 5rem, 85rem); width: var(--breakout-w); margin-left: calc(50% - var(--breakout-w) / 2); } } /* ------------------------------------------------------------------ */ /* Nav sweep — a phosphor tracer riding the sticky nav's bottom */ /* hairline; the instrument is always on */ /* ------------------------------------------------------------------ */ .nav-sweep { position: absolute; inset-inline: 0; bottom: -1px; height: 1px; overflow: hidden; pointer-events: none; } .nav-sweep::before { content: ""; position: absolute; top: 0; left: 0; height: 100%; width: 140px; background: linear-gradient( 90deg, transparent, color-mix(in srgb, var(--accent) 55%, transparent), transparent ); animation: nav-sweep 9s linear infinite; } @keyframes nav-sweep { from { transform: translateX(-140px); } to { transform: translateX(100vw); } } /* ------------------------------------------------------------------ */ /* Reduced motion: everything settles instantly */ /* ------------------------------------------------------------------ */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.001ms !important; animation-delay: 0ms !important; animation-iteration-count: 1 !important; animation-timeline: auto !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; } /* ambient effects are pure ambience — remove them when motion is reduced */ .pulse-sweep { visibility: hidden; } .nav-sweep { display: none; } /* ambient glow drift stops; the static light wash may remain */ .ambient-glow { animation: none; will-change: auto; } /* skeletons settle to a static placeholder */ .skeleton { animation: none; background-image: none; } /* grain and scanline remain as they don't animate */ body::before, body::after { display: none; } /* spotlight effects collapse to static */ .spotlight-card::before { display: none; } /* hover lifts settle */ .hover-lift:hover { transform: none; } /* glows collapse */ .glow-accent-subtle { box-shadow: none; } /* scroll animations resolve immediately */ .scroll-animate, .scroll-scale, .scroll-slide-left, .scroll-slide-right, .scroll-slide-down, .scroll-fade { opacity: 1; transform: none; } .scroll-stagger > * { opacity: 1; transform: none; } }