Spaces:
Sleeping
Sleeping
Download src/app/globals.css from Chunte/lerobot-visualizer-test: direct link, hf CLI and curl.
- Browser
- Download file 24 kB
-
https://huggingface.co/spaces/Chunte/lerobot-visualizer-test/resolve/main/src/app/globals.css
- Command line
-
hf download hf://spaces/Chunte/lerobot-visualizer-test/src/app/globals.css
-
curl -L -o globals.css https://huggingface.co/spaces/Chunte/lerobot-visualizer-test/resolve/main/src/app/globals.css
24 kB
| @import "tailwindcss"; | |
| /* ββ Minimal design system ββββββββββββββββββββββββββββββββββββββββ | |
| Quiet, monochrome chrome in the spirit of Apple / OpenAI interfaces: | |
| white (or near-black) canvases, hairline separators instead of boxes, | |
| large radii, pill controls, frosted floating bars and one functional | |
| accent (system blue) reserved for selection, focus and links. The | |
| primary action is a solid black (white in dark) pill. Flagged episodes | |
| use system orange (as Mail flags do), which also echoes the LeRobot logo. | |
| <html class="dark"> selects the dark set; see utils/theme.ts. Never | |
| hard-code colours in components: every value has a token below. */ | |
| @custom-variant dark (&:where(.dark, .dark *)); | |
| :root { | |
| color-scheme: light; | |
| /* Canvas & surfaces */ | |
| --bg: #ffffff; | |
| --bg-subtle: #f9f9fb; /* sidebar, page wells */ | |
| --surface: #ffffff; /* cards */ | |
| --surface-muted: #f4f4f6; /* inputs, segmented tracks, code */ | |
| --surface-raised: rgba(255, 255, 255, 0.82); /* glass: menus, floating bars */ | |
| --media: #0b0b0c; /* behind video and 3D */ | |
| /* Text */ | |
| --fg: #111113; | |
| --fg-secondary: #5f5f66; /* 6.4:1 β any text the user must read */ | |
| --fg-tertiary: #8e8e93; /* 3.3:1 β decoration, placeholders */ | |
| /* Lines & fills */ | |
| --separator: rgba(0, 0, 0, 0.08); | |
| --separator-strong: rgba(0, 0, 0, 0.14); | |
| --fill: rgba(0, 0, 0, 0.04); | |
| --fill-hover: rgba(0, 0, 0, 0.065); | |
| --fill-press: rgba(0, 0, 0, 0.1); | |
| /* Primary action: solid monochrome pill */ | |
| --primary: #111113; | |
| --on-primary: #ffffff; | |
| /* Functional accent: selection, focus, links */ | |
| --accent: #0071e3; | |
| --accent-fg: #0066cc; | |
| --accent-soft: rgba(0, 113, 227, 0.1); | |
| --focus-ring: rgba(0, 113, 227, 0.35); | |
| /* Flagged episodes only */ | |
| --flag: #ff9500; | |
| --flag-fg: #b25f00; | |
| --flag-soft: rgba(255, 149, 0, 0.12); | |
| /* Status */ | |
| --success: #248a3d; | |
| --success-soft: rgba(52, 199, 89, 0.12); | |
| --warning: #8a6100; /* amber, distinct from the flag orange */ | |
| --warning-soft: rgba(255, 204, 0, 0.16); | |
| --danger: #d70015; | |
| --danger-soft: rgba(255, 59, 48, 0.1); | |
| /* Playback position: neutral, like a QuickTime scrubber */ | |
| --playhead: #111113; | |
| /* Charts */ | |
| --chart-grid: rgba(0, 0, 0, 0.06); | |
| --chart-axis: rgba(0, 0, 0, 0.12); | |
| --chart-1: #0071e3; | |
| --chart-2: #28a745; | |
| --chart-3: #af52de; | |
| --chart-4: #00a3bf; | |
| --chart-5: #ff2d55; | |
| --chart-6: #5856d6; | |
| --chart-7: #a2845e; | |
| --chart-8: #30b0c7; | |
| --chart-9: #c7a400; | |
| --chart-10: #6e8b3d; /* olive: distinct from the tertiary grey */ | |
| /* Shape */ | |
| --radius-sm: 8px; | |
| --radius: 10px; /* controls, inputs */ | |
| --radius-lg: 16px; /* cards */ | |
| --radius-xl: 20px; /* media frames, sheets */ | |
| /* Elevation: almost flat; soft shadow only on floating surfaces */ | |
| --shadow-float: | |
| 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 8px 28px -6px rgba(0, 0, 0, 0.16), | |
| 0 2px 6px -2px rgba(0, 0, 0, 0.06); | |
| --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.03); | |
| /* Motion */ | |
| /* House curves. --ease-out is transitions.dev's --ease-smooth-out (same curve): every surface | |
| that appears or leaves. --ease-spring (the non-overshooting iOS sheet curve) is only for | |
| things that physically slide: tab pill, segmented thumb, switch knob, phone sheet. */ | |
| --ease-out: cubic-bezier(0.22, 1, 0.36, 1); | |
| --ease-smooth-out: var(--ease-out); | |
| --ease-spring: cubic-bezier(0.32, 0.72, 0, 1); | |
| /* House durations. Careful: --dur-fast (120ms, hover/press) is NOT --duration-fast (250ms). */ | |
| --dur-fast: 120ms; | |
| --dur: 200ms; | |
| --dur-slow: 320ms; | |
| /* transitions.dev motion scale, matched by usage (see the transitions-dev skill). */ | |
| --duration-stagger: 40ms; | |
| --duration-micro: 80ms; | |
| --duration-quick: 150ms; /* any close: menu, popover, modal; text/tooltip appear */ | |
| --duration-fast: 250ms; /* menu / modal open, icon swap, accordion, sliding tabs */ | |
| --duration-medium: 350ms; /* panel close */ | |
| --duration-slow: 400ms; /* panel open, skeleton -> content reveal */ | |
| --duration-very-slow: 500ms; | |
| --scale-large: 0.96; /* modal open / close */ | |
| --scale-medium: 0.97; /* menu open */ | |
| --scale-small: 0.98; /* tooltip open */ | |
| --scale-tiny: 0.99; /* menu close */ | |
| --blur-small: 2px; /* icon swap, skeleton reveal (small layers only) */ | |
| --blur-medium: 3px; | |
| --distance-micro: 4px; | |
| --distance-medium: 12px; | |
| --scrollbar: rgba(0, 0, 0, 0.16); | |
| } | |
| :root.dark { | |
| color-scheme: dark; | |
| --bg: #0f0f10; | |
| --bg-subtle: #141416; | |
| --surface: #18181a; | |
| --surface-muted: #222225; | |
| --surface-raised: rgba(32, 32, 35, 0.78); | |
| --media: #000000; | |
| --fg: #f5f5f7; | |
| --fg-secondary: #a1a1a6; | |
| --fg-tertiary: #6e6e73; | |
| --separator: rgba(255, 255, 255, 0.08); | |
| --separator-strong: rgba(255, 255, 255, 0.14); | |
| --fill: rgba(255, 255, 255, 0.05); | |
| --fill-hover: rgba(255, 255, 255, 0.08); | |
| --fill-press: rgba(255, 255, 255, 0.12); | |
| --primary: #f5f5f7; | |
| --on-primary: #111113; | |
| --accent: #0a84ff; | |
| --accent-fg: #4da3ff; | |
| --accent-soft: rgba(10, 132, 255, 0.16); | |
| --focus-ring: rgba(10, 132, 255, 0.45); | |
| --flag: #ff9f0a; | |
| --flag-fg: #ffb340; | |
| --flag-soft: rgba(255, 159, 10, 0.16); | |
| --success: #30d158; | |
| --success-soft: rgba(48, 209, 88, 0.14); | |
| --warning: #ffd60a; | |
| --warning-soft: rgba(255, 214, 10, 0.14); | |
| --danger: #ff6961; | |
| --danger-soft: rgba(255, 69, 58, 0.16); | |
| --playhead: #f5f5f7; | |
| --chart-grid: rgba(255, 255, 255, 0.06); | |
| --chart-axis: rgba(255, 255, 255, 0.14); | |
| --chart-1: #0a84ff; | |
| --chart-2: #30d158; | |
| --chart-3: #bf5af2; | |
| --chart-4: #64d2ff; | |
| --chart-5: #ff375f; | |
| --chart-6: #7d7aff; | |
| --chart-7: #ac8e68; | |
| --chart-8: #40c8e0; | |
| --chart-9: #ffd60a; | |
| --chart-10: #a3c76b; | |
| --shadow-float: | |
| 0 0 0 0.5px rgba(255, 255, 255, 0.1), 0 10px 32px -6px rgba(0, 0, 0, 0.6), | |
| 0 2px 8px -2px rgba(0, 0, 0, 0.4); | |
| --shadow-card: none; | |
| --scrollbar: rgba(255, 255, 255, 0.18); | |
| } | |
| @theme inline { | |
| --color-bg: var(--bg); | |
| --color-bg-subtle: var(--bg-subtle); | |
| --color-surface: var(--surface); | |
| --color-surface-muted: var(--surface-muted); | |
| --color-surface-raised: var(--surface-raised); | |
| --color-media: var(--media); | |
| --color-fg: var(--fg); | |
| --color-fg-secondary: var(--fg-secondary); | |
| --color-fg-tertiary: var(--fg-tertiary); | |
| --color-separator: var(--separator); | |
| --color-separator-strong: var(--separator-strong); | |
| --color-fill: var(--fill); | |
| --color-fill-hover: var(--fill-hover); | |
| --color-fill-press: var(--fill-press); | |
| --color-primary: var(--primary); | |
| --color-on-primary: var(--on-primary); | |
| --color-accent: var(--accent); | |
| --color-accent-fg: var(--accent-fg); | |
| --color-accent-soft: var(--accent-soft); | |
| --color-flag: var(--flag); | |
| --color-flag-fg: var(--flag-fg); | |
| --color-flag-soft: var(--flag-soft); | |
| --color-success: var(--success); | |
| --color-success-soft: var(--success-soft); | |
| --color-warning: var(--warning); | |
| --color-warning-soft: var(--warning-soft); | |
| --color-danger: var(--danger); | |
| --color-danger-soft: var(--danger-soft); | |
| --font-sans: | |
| var(--font-inter), -apple-system, BlinkMacSystemFont, "SF Pro Text", | |
| "Segoe UI", system-ui, sans-serif; | |
| --font-mono: | |
| var(--font-geist-mono), "SF Mono", ui-monospace, Menlo, monospace; | |
| /* 11px caption; 13px is the workhorse secondary size */ | |
| --text-2xs: 0.6875rem; | |
| --text-2xs--line-height: 1rem; | |
| --text-13: 0.8125rem; | |
| --text-13--line-height: 1.125rem; | |
| /* Tracking is size-specific (SF-style): small text opens up slightly, large text tightens. | |
| Body (14px) keeps the -0.006em set on <body>. */ | |
| --text-2xs--letter-spacing: 0.01em; | |
| --text-xs--letter-spacing: 0.005em; | |
| --text-13--letter-spacing: -0.002em; | |
| --text-lg--letter-spacing: -0.012em; | |
| --text-xl--letter-spacing: -0.016em; | |
| --text-2xl--letter-spacing: -0.02em; | |
| --text-3xl--letter-spacing: -0.024em; | |
| --text-4xl--letter-spacing: -0.026em; | |
| --text-5xl--letter-spacing: -0.03em; | |
| --text-6xl--letter-spacing: -0.032em; | |
| /* Every bare transition-colors / transition-opacity runs on the hover token and house curve. */ | |
| --default-transition-duration: var(--dur-fast); | |
| --default-transition-timing-function: var(--ease-out); | |
| --radius-sm: var(--radius-sm); | |
| --radius-md: var(--radius); | |
| --radius-lg: var(--radius-lg); | |
| --radius-xl: var(--radius-xl); | |
| --shadow-float: var(--shadow-float); | |
| --shadow-card: var(--shadow-card); | |
| --ease-out: var(--ease-out); | |
| --ease-spring: var(--ease-spring); | |
| } | |
| /* Compatibility: the previous token names, mapped onto the new system so | |
| untouched components keep working. New code uses the names above. */ | |
| :root, | |
| :root.dark { | |
| --surface-0: var(--bg); | |
| --surface-1: var(--surface); | |
| --surface-2: var(--surface-muted); | |
| --fg-soft: var(--fg); | |
| --fg-muted: var(--fg-secondary); | |
| --fg-faint: var(--fg-tertiary); | |
| --line: var(--separator); | |
| --line-subtle: var(--separator); | |
| --line-strong: var(--separator-strong); | |
| --fill-strong: var(--fill-hover); | |
| --accent-ring: var(--focus-ring); | |
| --playhead-glow: transparent; | |
| } | |
| @theme inline { | |
| --color-surface-0: var(--bg); | |
| --color-surface-1: var(--surface); | |
| --color-surface-2: var(--surface-muted); | |
| --color-fg-soft: var(--fg); | |
| --color-fg-muted: var(--fg-secondary); | |
| --color-fg-faint: var(--fg-tertiary); | |
| --color-line: var(--separator); | |
| --color-line-subtle: var(--separator); | |
| --color-line-strong: var(--separator-strong); | |
| --color-fill-strong: var(--fill-hover); | |
| } | |
| html { | |
| font-size: 16px; | |
| /* Phones would flash a grey rectangle over pill buttons; feedback is the press state. */ | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| body { | |
| background: var(--bg); | |
| color: var(--fg); | |
| font-size: 0.875rem; /* 14px UI text */ | |
| letter-spacing: -0.006em; | |
| font-feature-settings: | |
| "cv11" 1, | |
| "ss01" 1, | |
| "ss03" 1; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| text-rendering: optimizeLegibility; | |
| } | |
| ::selection { | |
| background: var(--accent-soft); | |
| } | |
| /* Lucide icons at a lighter 1.75 stroke: closer to SF Symbols. */ | |
| svg[stroke-width="2"] { | |
| stroke-width: 1.75; | |
| } | |
| /* Focus: a soft accent halo, keyboard only. Base layer, so components can | |
| restyle it, never remove it without a replacement. */ | |
| @layer base { | |
| :where( | |
| a, | |
| button, | |
| input, | |
| select, | |
| textarea, | |
| summary, | |
| [tabindex], | |
| [role="button"] | |
| ):focus-visible { | |
| outline: none; | |
| box-shadow: 0 0 0 3px var(--focus-ring); | |
| } | |
| input[type="range"]:focus-visible { | |
| box-shadow: none; | |
| } | |
| input[type="range"]:focus-visible::-webkit-slider-thumb { | |
| box-shadow: 0 0 0 4px var(--focus-ring); | |
| } | |
| input[type="range"]:focus-visible::-moz-range-thumb { | |
| box-shadow: 0 0 0 4px var(--focus-ring); | |
| } | |
| } | |
| /* Numeric readouts always use tabular figures so rows don't jitter. */ | |
| .tabular, | |
| [data-tabular] { | |
| font-variant-numeric: tabular-nums; | |
| } | |
| /* Titles: tight tracking, like SF Display. */ | |
| .title { | |
| letter-spacing: -0.022em; | |
| font-weight: 600; | |
| } | |
| /* Quiet section label (sentence case, never uppercase). */ | |
| .label { | |
| font-size: var(--text-13); | |
| line-height: var(--text-13--line-height); | |
| font-weight: 500; | |
| color: var(--fg-secondary); | |
| } | |
| /* Frosted floating material: menus, popovers, the playback bar. */ | |
| @utility glass { | |
| background: var(--surface-raised); | |
| -webkit-backdrop-filter: saturate(180%) blur(20px); | |
| backdrop-filter: saturate(180%) blur(20px); | |
| box-shadow: var(--shadow-float); | |
| } | |
| /* Translucent chrome bar (top bar). */ | |
| @utility chrome { | |
| background: color-mix(in srgb, var(--bg) 80%, transparent); | |
| -webkit-backdrop-filter: saturate(180%) blur(20px); | |
| backdrop-filter: saturate(180%) blur(20px); | |
| } | |
| @layer components { | |
| /* Content card: hairline, large radius, flat. */ | |
| .card { | |
| @apply rounded-lg border border-separator bg-surface shadow-card; | |
| } | |
| /* Kept for components that still use the old helper names. */ | |
| .panel { | |
| @apply rounded-lg border border-separator bg-surface; | |
| } | |
| .panel-raised { | |
| @apply glass rounded-xl; | |
| } | |
| /* Buttons β pills, no borders, a gentle press. Variants: | |
| .btn (tinted secondary), .btn-primary (solid monochrome: the one main | |
| action), .btn-ghost (toolbar), .btn-active (selected toggle), | |
| .btn-flagged (flag filters/actions), .btn-danger, .btn-icon (circle). | |
| Heights: .btn 32px; .btn-sm 28px; .btn-lg 40px. */ | |
| .btn { | |
| @apply inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-full bg-fill px-3.5 text-13 font-medium whitespace-nowrap text-fg select-none; | |
| transition: | |
| background-color var(--dur-fast) var(--ease-out), | |
| color var(--dur-fast) var(--ease-out), | |
| transform var(--dur-fast) var(--ease-out), | |
| box-shadow var(--dur-fast) var(--ease-out), | |
| opacity var(--dur-fast) var(--ease-out); | |
| } | |
| .btn:hover { | |
| @apply bg-fill-hover; | |
| } | |
| .btn:active { | |
| @apply bg-fill-press; | |
| transform: scale(0.97); | |
| } | |
| .btn:disabled { | |
| @apply pointer-events-none cursor-not-allowed opacity-40; | |
| } | |
| .btn-sm { | |
| @apply h-7 gap-1 px-3 text-2xs; | |
| } | |
| .btn-lg { | |
| @apply h-10 px-5 text-sm; | |
| } | |
| .btn-primary, | |
| .btn-primary:hover { | |
| @apply bg-primary text-on-primary; | |
| } | |
| .btn-primary:hover { | |
| opacity: 0.86; | |
| } | |
| .btn-primary:active { | |
| @apply bg-primary; | |
| opacity: 0.8; | |
| } | |
| .btn-ghost { | |
| @apply bg-transparent text-fg-secondary; | |
| } | |
| .btn-ghost:hover { | |
| @apply bg-fill text-fg; | |
| } | |
| .btn-active, | |
| .btn-active:hover { | |
| @apply bg-accent-soft text-accent-fg; | |
| } | |
| .btn-flagged, | |
| .btn-flagged:hover { | |
| @apply bg-flag-soft text-flag-fg; | |
| } | |
| .btn-danger, | |
| .btn-danger:hover { | |
| @apply bg-danger-soft text-danger; | |
| } | |
| .btn-icon { | |
| @apply aspect-square px-0; | |
| } | |
| /* Text inputs and selects. */ | |
| .field { | |
| @apply h-8 rounded-md border border-transparent bg-surface-muted px-3 text-sm text-fg placeholder:text-fg-tertiary; | |
| transition: | |
| background-color var(--dur-fast) var(--ease-out), | |
| border-color var(--dur-fast) var(--ease-out), | |
| box-shadow var(--dur-fast) var(--ease-out); | |
| } | |
| .field:hover { | |
| @apply bg-fill-hover; | |
| } | |
| .field:focus, | |
| .field:focus-visible { | |
| @apply border-accent bg-surface; | |
| outline: none; | |
| box-shadow: 0 0 0 3px var(--focus-ring); | |
| } | |
| /* Loading placeholder. */ | |
| .skeleton { | |
| position: relative; | |
| overflow: hidden; | |
| background: var(--fill); | |
| border-radius: var(--radius); | |
| } | |
| .skeleton::after { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| transform: translateX(-100%); | |
| background: linear-gradient( | |
| 90deg, | |
| transparent, | |
| var(--fill-hover), | |
| transparent | |
| ); | |
| animation: shimmer 1.6s linear infinite; | |
| } | |
| } | |
| @keyframes shimmer { | |
| to { | |
| transform: translateX(100%); | |
| } | |
| } | |
| /* Range sliders: thin monochrome track, white knob with a soft shadow | |
| (QuickTime / iOS). The filled part is drawn by the component with a | |
| background gradient on --range-fill when it wants one. */ | |
| input[type="range"].range { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| height: 20px; | |
| background: transparent; | |
| cursor: pointer; | |
| } | |
| input[type="range"].range::-webkit-slider-runnable-track { | |
| height: 4px; | |
| border-radius: 999px; | |
| background: linear-gradient( | |
| to right, | |
| var(--playhead) 0 var(--range-fill, 0%), | |
| var(--fill-press) var(--range-fill, 0%) 100% | |
| ); | |
| } | |
| input[type="range"].range::-moz-range-track { | |
| height: 4px; | |
| border-radius: 999px; | |
| background: var(--fill-press); | |
| } | |
| input[type="range"].range::-moz-range-progress { | |
| height: 4px; | |
| border-radius: 999px; | |
| background: var(--playhead); | |
| } | |
| input[type="range"].range::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| margin-top: -6px; | |
| width: 16px; | |
| height: 16px; | |
| border-radius: 999px; | |
| background: #ffffff; | |
| box-shadow: | |
| 0 0 0 0.5px rgba(0, 0, 0, 0.12), | |
| 0 2px 6px rgba(0, 0, 0, 0.22); | |
| transition: transform var(--dur-fast) var(--ease-out); | |
| } | |
| input[type="range"].range::-moz-range-thumb { | |
| width: 16px; | |
| height: 16px; | |
| border: none; | |
| border-radius: 999px; | |
| background: #ffffff; | |
| box-shadow: | |
| 0 0 0 0.5px rgba(0, 0, 0, 0.12), | |
| 0 2px 6px rgba(0, 0, 0, 0.22); | |
| } | |
| input[type="range"].range:active::-moz-range-thumb { | |
| transform: scale(1.12); | |
| } | |
| input[type="range"].range:active::-webkit-slider-thumb { | |
| transform: scale(1.12); | |
| } | |
| /* Scrollbars: thin, overlay-like. */ | |
| * { | |
| scrollbar-width: thin; | |
| scrollbar-color: var(--scrollbar) transparent; | |
| } | |
| *::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| *::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| *::-webkit-scrollbar-thumb { | |
| background: transparent; | |
| border-radius: 8px; | |
| border: 2px solid transparent; | |
| background-clip: padding-box; | |
| } | |
| *:hover::-webkit-scrollbar-thumb { | |
| background-color: var(--scrollbar); | |
| } | |
| /* ββ Motion βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| @keyframes fadeInUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(12px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .animate-fade-in-up { | |
| animation: fadeInUp var(--dur-slow) var(--ease-out) both; | |
| } | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| } | |
| to { | |
| opacity: 1; | |
| } | |
| } | |
| .animate-fade-in { | |
| animation: fadeIn var(--dur) var(--ease-out) both; | |
| } | |
| .animate-fade-in-late { | |
| animation: fadeInUp var(--dur-slow) var(--ease-out) 0.25s both; | |
| } | |
| /* Menus and popovers: a small spring from just above. */ | |
| @keyframes menuPop { | |
| from { | |
| opacity: 0; | |
| transform: translateY(-4px) scale(0.98); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0) scale(1); | |
| } | |
| } | |
| .animate-menu-pop { | |
| animation: menuPop var(--dur) var(--ease-spring) both; | |
| transform-origin: top center; | |
| } | |
| /* ββ Shared transitions (transitions.dev patterns on the house tokens) ββ | |
| All use the `scale` / `opacity` properties and CSS transitions, never keyframes, for anything | |
| that can be reopened mid-exit: a transition retargets from the live value, so it reverses | |
| smoothly (apple-design: interruptible). Drive them with `data-state` from usePresence(). */ | |
| /* Surface that grows from its trigger (menus, popovers). Set transform-origin to the trigger. */ | |
| .t-pop { | |
| transition: | |
| opacity var(--duration-fast) var(--ease-out), | |
| scale var(--duration-fast) var(--ease-out); | |
| } | |
| .t-pop[data-state="open"] { | |
| opacity: 1; | |
| scale: 1; | |
| @starting-style { | |
| opacity: 0; | |
| scale: var(--scale-medium); | |
| } | |
| } | |
| .t-pop[data-state="closing"] { | |
| opacity: 0; | |
| scale: var(--scale-tiny); | |
| transition-duration: var(--duration-quick); | |
| pointer-events: none; | |
| } | |
| /* Centred overlay content (enlarged camera, fullscreen insight). */ | |
| .t-modal { | |
| transition: | |
| opacity var(--duration-fast) var(--ease-out), | |
| scale var(--duration-fast) var(--ease-out); | |
| } | |
| .t-modal[data-state="open"] { | |
| opacity: 1; | |
| scale: 1; | |
| @starting-style { | |
| opacity: 0; | |
| scale: var(--scale-large); | |
| } | |
| } | |
| .t-modal[data-state="closing"] { | |
| opacity: 0; | |
| scale: var(--scale-large); | |
| transition-duration: var(--duration-quick); | |
| pointer-events: none; | |
| } | |
| /* For an element that is re-classed rather than mounted (no @starting-style to hook). */ | |
| @keyframes t-modal-in { | |
| from { | |
| opacity: 0; | |
| scale: var(--scale-large); | |
| } | |
| } | |
| .t-modal-in { | |
| animation: t-modal-in var(--duration-fast) var(--ease-out) both; | |
| } | |
| /* Opacity-only enter/exit: scrims, veils, backdrops (never animate the blur radius). */ | |
| .t-fade { | |
| transition: opacity var(--duration-fast) var(--ease-out); | |
| } | |
| .t-fade[data-state="open"] { | |
| opacity: 1; | |
| @starting-style { | |
| opacity: 0; | |
| } | |
| } | |
| .t-fade[data-state="closing"] { | |
| opacity: 0; | |
| transition-duration: var(--duration-quick); | |
| pointer-events: none; | |
| } | |
| /* Skeleton -> content: play only on the loading -> loaded change, not on every mount. */ | |
| @keyframes t-reveal { | |
| from { | |
| opacity: 0; | |
| } | |
| } | |
| .t-reveal { | |
| animation: t-reveal var(--duration-slow) ease-in-out both; | |
| } | |
| /* Two icons in one slot cross-fade with blur + scale (IconSwap in ui.tsx). */ | |
| .icon-swap { | |
| display: inline-grid; | |
| place-items: center; | |
| } | |
| .icon-swap > [data-slot] { | |
| grid-area: 1 / 1; | |
| display: inline-flex; | |
| transition: | |
| opacity var(--duration-fast) ease-in-out, | |
| scale var(--duration-fast) ease-in-out, | |
| filter var(--duration-fast) ease-in-out; | |
| } | |
| .icon-swap[data-state="on"] > [data-slot="off"], | |
| .icon-swap[data-state="off"] > [data-slot="on"] { | |
| opacity: 0; | |
| scale: 0.25; | |
| filter: blur(var(--blur-small)); | |
| } | |
| .icon-swap[data-instant] > [data-slot] { | |
| transition: none; | |
| } | |
| /* In-flow disclosure that grows / shrinks (Collapse in ui.tsx). Padding goes on the inner | |
| element, never the track, or a strip stays visible when closed. */ | |
| .collapse-track { | |
| display: grid; | |
| grid-template-rows: 0fr; | |
| transition: grid-template-rows var(--duration-fast) var(--ease-out); | |
| } | |
| .collapse-track[data-state="open"] { | |
| grid-template-rows: 1fr; | |
| } | |
| .collapse-inner { | |
| min-height: 0; | |
| overflow: hidden; | |
| opacity: 0; | |
| transition: opacity var(--duration-fast) var(--ease-out); | |
| } | |
| .collapse-track[data-state="open"] > .collapse-inner { | |
| opacity: 1; | |
| } | |
| /* One frame without transitions while the theme flips, so 120ms control fades can't lag the | |
| instantly re-themed canvas (utils/theme.ts adds and removes it). */ | |
| .theme-switching, | |
| .theme-switching *, | |
| .theme-switching *::before, | |
| .theme-switching *::after { | |
| transition: none ; | |
| } | |
| /* ββ Accessibility preferences ββββββββββββββββββββββββββββββββββββ */ | |
| /* Reduced motion is not "no feedback": state changes are instant and moving keyframes become a | |
| short fade, but spinners keep turning so a load never looks hung. */ | |
| @media (prefers-reduced-motion: reduce) { | |
| *, | |
| *::before, | |
| *::after { | |
| animation-duration: 0.01ms ; | |
| animation-iteration-count: 1 ; | |
| transition-duration: 0.01ms ; | |
| scroll-behavior: auto ; | |
| } | |
| .animate-spin { | |
| animation-duration: 900ms ; | |
| animation-iteration-count: infinite ; | |
| } | |
| .animate-menu-pop, | |
| .animate-fade-in-up, | |
| .animate-fade-in-late, | |
| .t-modal-in, | |
| .t-reveal { | |
| animation-name: fadeIn ; | |
| animation-duration: var(--dur) ; | |
| } | |
| .skeleton::after { | |
| animation: none ; | |
| } | |
| } | |
| /* Reduced transparency: frosted surfaces become solid. */ | |
| @media (prefers-reduced-transparency: reduce) { | |
| :root, | |
| :root.dark { | |
| --surface-raised: var(--surface); | |
| } | |
| .glass, | |
| .chrome { | |
| -webkit-backdrop-filter: none ; | |
| backdrop-filter: none ; | |
| } | |
| .chrome { | |
| background: var(--bg) ; | |
| } | |
| } | |
| /* More contrast: firmer separators and secondary text, and an edge on glass. */ | |
| @media (prefers-contrast: more) { | |
| :root { | |
| --separator: rgba(0, 0, 0, 0.2); | |
| --separator-strong: rgba(0, 0, 0, 0.32); | |
| --fg-secondary: #45454b; | |
| --surface-raised: var(--surface); | |
| } | |
| :root.dark { | |
| --separator: rgba(255, 255, 255, 0.22); | |
| --separator-strong: rgba(255, 255, 255, 0.34); | |
| --fg-secondary: #c7c7cc; | |
| --surface-raised: var(--surface); | |
| } | |
| .glass { | |
| outline: 1px solid var(--separator-strong); | |
| } | |
| } | |
| @media (forced-colors: active) { | |
| .glass, | |
| .card { | |
| border: 1px solid CanvasText; | |
| } | |
| } | |