@import "tailwindcss"; @custom-variant dark (&:is(.dark, .dark *));@custom-variant light (&:not(.dark, .dark *));/* Window Controls Overlay: active when Electron exposes native titlebar control geometry. */@custom-variant wco (&:is(.wco, .wco *)); /* On mobile, morph the expanded hero composer into the compact docked composer while it moves; the rest of the app remains visually stationary. */html[data-mobile-composer-route-transition="true"]::view-transition-old(root),html[data-mobile-composer-route-transition="true"]::view-transition-new(root) { animation: none;} html[data-mobile-composer-route-transition="true"]::view-transition-group(t3-mobile-composer) { animation-duration: 180ms; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);} html[data-mobile-composer-route-transition="true"]::view-transition-image-pair(t3-mobile-composer) { isolation: isolate;} html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobile-composer) { animation: t3-mobile-composer-old 180ms linear both; mix-blend-mode: normal;} html[data-mobile-composer-route-transition="true"]::view-transition-new(t3-mobile-composer) { animation: t3-mobile-composer-new 180ms linear both; mix-blend-mode: normal;} /* The expanded and compact composers have different internal layouts. Use a brief crossfade while their shared wrapper moves so the layout change does not read as a single-frame cut. */@keyframes t3-mobile-composer-old { 0%, 35% { opacity: 1; } 65%, 100% { opacity: 0; }} @keyframes t3-mobile-composer-new { 0%, 35% { opacity: 0; } 65%, 100% { opacity: 1; }} html[data-mobile-composer-route-transition="true"]::view-transition-group(t3-mobile-draft-headline) { animation-duration: 130ms;} html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobile-draft-headline) { animation: t3-mobile-draft-headline-exit 130ms cubic-bezier(0.4, 0, 1, 1) both; mix-blend-mode: normal;} @keyframes t3-mobile-draft-headline-exit { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-6px); }} :root { --app-scrollbar-width: 6px; --appearance-contrast-base: 100%; --appearance-contrast-boost: 0%; --appearance-contrast-border-boost: 0%; --appearance-contrast-target: black; --app-scrollbar-thumb: rgb(217 217 217); --app-scrollbar-thumb-hover: rgb(191 191 191); /* * Compact UI geometry. Keep these values semantic so sidebar, palette, * tooltip, and toolbar controls cannot quietly drift apart. */ --control-radius: 0.5rem; --sidebar-content-inset: 0.5rem; --sidebar-control-gap: 0.5rem; --sidebar-icon-color: color-mix( in srgb, var(--contrast-sidebar-muted-foreground) 60%, var(--sidebar) ); --sidebar-row-content-inset: 0.625rem; --command-shell-inset: 0.5rem; --command-content-inset: 1rem; --floating-content-inset: 0.75rem; --glass-blur: 12px; --glass-opacity: 80%; --glass-saturation: 1.14; --desktop-window-right-resize-inset: 0px; --workspace-topbar-height: 52px; --workspace-controls-top: 0px; --workspace-controls-left: calc(env(safe-area-inset-left) + 0.75rem); --workspace-controls-right: calc(env(safe-area-inset-right) + 0.75rem); --workspace-native-controls-inset: 0px; --workspace-titlebar-control-size: 1.75rem; --workspace-titlebar-control-gap: 0.75rem; @variant dark { --appearance-contrast-target: white; --app-scrollbar-thumb: rgb(255 255 255 / 8%); --app-scrollbar-thumb-hover: rgb(255 255 255 / 12%); --glass-blur: 16px; --glass-saturation: 1.08; }} .wco { --workspace-topbar-height: env(titlebar-area-height, 52px); --workspace-controls-top: env(titlebar-area-y, 0px); --workspace-controls-left: calc(env(titlebar-area-x, 0px) + 0.75rem); --workspace-controls-right: calc( 100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px) + 0.75rem ); --workspace-native-controls-inset: calc( 100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px) + 0.75rem );} /* The font tokens are declared outside the inline theme so utilities reference the variables and Settings -> Appearance can override them at runtime. The default stacks are mirrored in `appearanceFonts.ts`. */@theme { --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; --font-mono: ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;} @theme inline { --color-zinc-25: oklch(99.2% 0 0); --animate-skeleton: skeleton 2s infinite linear; /* Duty-cycled indicator animations: long holds with stepped ramps, so the compositor updates discrete frames instead of every vsync. */ --animate-status-pulse: status-pulse 2s infinite; --animate-ghost-pulse: ghost-pulse 2.4s infinite; --animate-status-ping: status-ping 2s infinite; --color-warning-foreground: var(--warning-foreground); --color-warning: var(--warning); --color-warning-surface: var(--warning-surface); --color-error-foreground: var(--error-foreground); --color-error: var(--error); --color-error-surface: var(--error-surface); --color-update-foreground: var(--update-foreground); --color-update: var(--update); --color-update-surface: var(--update-surface); --color-success-foreground: var(--success-foreground); --color-success: var(--success); --color-info-foreground: var(--info-foreground); --color-info: var(--info); --color-destructive-foreground: var(--destructive-foreground); --color-ring: var(--ring); --color-input: var(--contrast-input); --color-border: var(--contrast-border); --color-destructive: var(--destructive); --color-accent-foreground: var(--contrast-accent-foreground); --color-accent: var(--accent); --color-muted-foreground: var(--contrast-muted-foreground); --color-muted: var(--muted); --color-placeholder: var(--contrast-placeholder); --color-secondary-label: var(--contrast-secondary-label); --color-icon-muted: var(--contrast-icon-muted); --color-secondary-foreground: var(--contrast-secondary-foreground); --color-secondary: var(--secondary); --color-primary-foreground: var(--primary-foreground); --color-primary: var(--primary); --color-popover-foreground: var(--contrast-popover-foreground); --color-popover: var(--popover); --color-card-foreground: var(--contrast-card-foreground); --color-card: var(--card); --color-foreground: var(--contrast-foreground); --color-background: var(--background); --color-surface-raised: var(--surface-raised); --color-message: var(--message-surface); --color-message-foreground: var(--contrast-message-foreground); --color-message-action: var(--message-action); --color-message-action-foreground: var(--message-action-foreground); --color-message-action-hover: var(--message-action-hover); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--contrast-sidebar-foreground); --color-sidebar-muted-foreground: var(--contrast-sidebar-muted-foreground); --color-sidebar-control-surface: var(--sidebar-control-surface); --color-sidebar-row-hover: var(--sidebar-row-hover); --color-sidebar-row-active: var(--sidebar-row-active); --color-sidebar-row-selected: var(--sidebar-row-selected); --color-sidebar-border: var(--contrast-sidebar-border); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); --radius-2xl: calc(var(--radius) + 8px); --radius-3xl: calc(var(--radius) + 12px); --radius-4xl: calc(var(--radius) + 16px); @keyframes skeleton { /* Transform-only so the highlight sweep stays on the compositor, then a long hold with the band parked off-screen instead of a constant shimmer. */ 0% { transform: translateX(-100%); } 60%, 100% { transform: translateX(100%); } } @keyframes ghost-pulse { /* The loading ghosts' breath. Stepped like the status indicators, so however many bars a ghost holds, the compositor draws a handful of discrete frames per cycle rather than one per vsync — which on a 120Hz display is the difference between ~14 and ~288 updates. */ 0%, 42% { opacity: 1; animation-timing-function: steps(4); } 50%, 92% { opacity: 0.55; animation-timing-function: steps(4); } 100% { opacity: 1; } } @keyframes status-pulse { 0%, 40% { opacity: 1; animation-timing-function: steps(6); } 50%, 90% { opacity: 0.5; animation-timing-function: steps(6); } 100% { opacity: 1; } } @keyframes status-ping { /* Burst first (immediate feedback for click ripples), then hold invisible for the rest of the cycle. Mirrors animate-ping's 75%-scale start. */ 0% { opacity: 0.9; scale: 0.75; animation-timing-function: steps(8); } 40%, 100% { opacity: 0; scale: 2; } }} @utility surface-glass { background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { background: var(--background) !important; }} @utility alert-glass { --alert-glass-tint: transparent; background: linear-gradient( color-mix(in srgb, var(--alert-glass-tint) 4%, transparent), color-mix(in srgb, var(--alert-glass-tint) 4%, transparent) ), color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important; -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); &[data-variant="error"] { --alert-glass-tint: var(--destructive); } &[data-variant="info"] { --alert-glass-tint: var(--info); } &[data-variant="success"] { --alert-glass-tint: var(--success); } &[data-variant="warning"] { --alert-glass-tint: var(--warning); } @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { background: var(--background) !important; }} @utility dialog-glass { background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); border-color: color-mix(in srgb, var(--contrast-foreground) 10%, transparent); box-shadow: 0 24px 64px -24px rgb(0 0 0 / 65%); @variant dark { border-color: color-mix(in srgb, var(--color-white) 8%, transparent); box-shadow: inset 0 1px rgb(255 255 255 / 4%), 0 24px 72px -20px rgb(0 0 0 / 90%); } @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { background: var(--popover) !important; }} @utility dialog-backdrop { background: color-mix(in srgb, var(--background) 60%, transparent); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); @variant dark { background: color-mix(in srgb, var(--background) 64%, transparent); }} @utility dropdown-glass { background: color-mix( in srgb, var(--popover) 18%, color-mix(in srgb, var(--popover) var(--glass-opacity), transparent) ); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); border: 1px solid color-mix(in srgb, var(--contrast-foreground) 10%, transparent); @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { background: var(--popover) !important; }} @utility topbar-scroll-fade { --topbar-scroll-fade-height: 2.5rem; -webkit-mask-image: linear-gradient( to bottom, transparent 0%, rgb(0 0 0 / 10%) 10%, rgb(0 0 0 / 30%) 24%, rgb(0 0 0 / 58%) 42%, rgb(0 0 0 / 82%) 62%, rgb(0 0 0 / 96%) 82%, black 100% ), linear-gradient(black, black), linear-gradient(black, black); -webkit-mask-position: top, bottom, right; -webkit-mask-repeat: no-repeat; -webkit-mask-size: 100% var(--topbar-scroll-fade-height), 100% calc(100% - var(--topbar-scroll-fade-height)), var(--app-scrollbar-width) 100%; mask-image: linear-gradient( to bottom, transparent 0%, rgb(0 0 0 / 10%) 10%, rgb(0 0 0 / 30%) 24%, rgb(0 0 0 / 58%) 42%, rgb(0 0 0 / 82%) 62%, rgb(0 0 0 / 96%) 82%, black 100% ), linear-gradient(black, black), linear-gradient(black, black); mask-position: top, bottom, right; mask-repeat: no-repeat; mask-size: 100% var(--topbar-scroll-fade-height), 100% calc(100% - var(--topbar-scroll-fade-height)), var(--app-scrollbar-width) 100%; @variant sm { --topbar-scroll-fade-height: 3rem; }} /* Virtualizers own their native scroll element, so they cannot use ScrollArea's viewport fade. Keep the scrollbar lane opaque while sharing the same fade contract across those lists. */@utility virtualized-scroll-fade { -webkit-mask-image: var(--virtualized-scroll-fade-mask), linear-gradient(black, black); mask-image: var(--virtualized-scroll-fade-mask), linear-gradient(black, black); -webkit-mask-position: left, right; mask-position: left, right; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: calc(100% - var(--app-scrollbar-width)) 100%, var(--app-scrollbar-width) 100%; mask-size: calc(100% - var(--app-scrollbar-width)) 100%, var(--app-scrollbar-width) 100%;} /* Stage-channel art needs a mask and pseudo-element gradient, so keep the behavior composable without tying it to the global components layer. */@utility sidebar-stage-backdrop { --stage-fade: var(--sidebar-stage-fade, var(--app-chrome-background)); mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 92%); -webkit-mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 92%); &::after { content: ""; position: absolute; inset: 0; background: linear-gradient( to bottom, transparent 0%, transparent 28%, color-mix(in srgb, var(--stage-fade) 10%, transparent) 40%, color-mix(in srgb, var(--stage-fade) 30%, transparent) 52%, color-mix(in srgb, var(--stage-fade) 58%, transparent) 64%, color-mix(in srgb, var(--stage-fade) 82%, transparent) 75%, color-mix(in srgb, var(--stage-fade) 96%, transparent) 85%, var(--stage-fade) 93% ); }} @keyframes live-activity-focus { 0% { transform: translateX(0); } 100% { transform: translateX(100%); }} @keyframes live-activity-focus-counter { 0% { transform: translateX(0); } 100% { transform: translateX(-100%); }} @utility live-activity-focus { --live-activity-focus-width: 4.5rem; right: auto; left: calc(-1 * var(--live-activity-focus-width)); width: calc(100% + var(--live-activity-focus-width) + var(--live-activity-focus-width)); -webkit-mask-image: linear-gradient( to right, transparent 0, rgb(0 0 0 / 12%) 0.675rem, rgb(0 0 0 / 55%) 1.575rem, black 2.25rem, rgb(0 0 0 / 55%) 2.925rem, rgb(0 0 0 / 12%) 3.825rem, transparent var(--live-activity-focus-width), transparent 100% ); -webkit-mask-repeat: no-repeat; mask-image: linear-gradient( to right, transparent 0, rgb(0 0 0 / 12%) 0.675rem, rgb(0 0 0 / 55%) 1.575rem, black 2.25rem, rgb(0 0 0 / 55%) 2.925rem, rgb(0 0 0 / 12%) 3.825rem, transparent var(--live-activity-focus-width), transparent 100% ); mask-repeat: no-repeat; animation: live-activity-focus 2.2s linear infinite; will-change: transform; @media (prefers-reduced-motion: reduce) { animation: none; opacity: 0; will-change: auto; }} Lines 1–500 of 2523. Load more
@import "tailwindcss"; @custom-variant dark (&:is(.dark, .dark *)); @custom-variant light (&:not(.dark, .dark *)); /* Window Controls Overlay: active when Electron exposes native titlebar control geometry. */ @custom-variant wco (&:is(.wco, .wco *)); /* On mobile, morph the expanded hero composer into the compact docked composer while it moves; the rest of the app remains visually stationary. */ html[data-mobile-composer-route-transition="true"]::view-transition-old(root), html[data-mobile-composer-route-transition="true"]::view-transition-new(root) { animation: none; } html[data-mobile-composer-route-transition="true"]::view-transition-group(t3-mobile-composer) { animation-duration: 180ms; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } html[data-mobile-composer-route-transition="true"]::view-transition-image-pair(t3-mobile-composer) { isolation: isolate; } html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobile-composer) { animation: t3-mobile-composer-old 180ms linear both; mix-blend-mode: normal; } html[data-mobile-composer-route-transition="true"]::view-transition-new(t3-mobile-composer) { animation: t3-mobile-composer-new 180ms linear both; mix-blend-mode: normal; } /* The expanded and compact composers have different internal layouts. Use a brief crossfade while their shared wrapper moves so the layout change does not read as a single-frame cut. */ @keyframes t3-mobile-composer-old { 0%, 35% { opacity: 1; } 65%, 100% { opacity: 0; } } @keyframes t3-mobile-composer-new { 0%, 35% { opacity: 0; } 65%, 100% { opacity: 1; } } html[data-mobile-composer-route-transition="true"]::view-transition-group(t3-mobile-draft-headline) { animation-duration: 130ms; } html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobile-draft-headline) { animation: t3-mobile-draft-headline-exit 130ms cubic-bezier(0.4, 0, 1, 1) both; mix-blend-mode: normal; } @keyframes t3-mobile-draft-headline-exit { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-6px); } } :root { --app-scrollbar-width: 6px; --appearance-contrast-base: 100%; --appearance-contrast-boost: 0%; --appearance-contrast-border-boost: 0%; --appearance-contrast-target: black; --app-scrollbar-thumb: rgb(217 217 217); --app-scrollbar-thumb-hover: rgb(191 191 191); /* * Compact UI geometry. Keep these values semantic so sidebar, palette, * tooltip, and toolbar controls cannot quietly drift apart. */ --control-radius: 0.5rem; --sidebar-content-inset: 0.5rem; --sidebar-control-gap: 0.5rem; --sidebar-icon-color: color-mix( in srgb, var(--contrast-sidebar-muted-foreground) 60%, var(--sidebar) ); --sidebar-row-content-inset: 0.625rem; --command-shell-inset: 0.5rem; --command-content-inset: 1rem; --floating-content-inset: 0.75rem; --glass-blur: 12px; --glass-opacity: 80%; --glass-saturation: 1.14; --desktop-window-right-resize-inset: 0px; --workspace-topbar-height: 52px; --workspace-controls-top: 0px; --workspace-controls-left: calc(env(safe-area-inset-left) + 0.75rem); --workspace-controls-right: calc(env(safe-area-inset-right) + 0.75rem); --workspace-native-controls-inset: 0px; --workspace-titlebar-control-size: 1.75rem; --workspace-titlebar-control-gap: 0.75rem; @variant dark { --appearance-contrast-target: white; --app-scrollbar-thumb: rgb(255 255 255 / 8%); --app-scrollbar-thumb-hover: rgb(255 255 255 / 12%); --glass-blur: 16px; --glass-saturation: 1.08; } } .wco { --workspace-topbar-height: env(titlebar-area-height, 52px); --workspace-controls-top: env(titlebar-area-y, 0px); --workspace-controls-left: calc(env(titlebar-area-x, 0px) + 0.75rem); --workspace-controls-right: calc( 100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px) + 0.75rem ); --workspace-native-controls-inset: calc( 100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px) + 0.75rem ); } /* The font tokens are declared outside the inline theme so utilities reference the variables and Settings -> Appearance can override them at runtime. The default stacks are mirrored in `appearanceFonts.ts`. */ @theme { --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; --font-mono: ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace; } @theme inline { --color-zinc-25: oklch(99.2% 0 0); --animate-skeleton: skeleton 2s infinite linear; /* Duty-cycled indicator animations: long holds with stepped ramps, so the compositor updates discrete frames instead of every vsync. */ --animate-status-pulse: status-pulse 2s infinite; --animate-ghost-pulse: ghost-pulse 2.4s infinite; --animate-status-ping: status-ping 2s infinite; --color-warning-foreground: var(--warning-foreground); --color-warning: var(--warning); --color-warning-surface: var(--warning-surface); --color-error-foreground: var(--error-foreground); --color-error: var(--error); --color-error-surface: var(--error-surface); --color-update-foreground: var(--update-foreground); --color-update: var(--update); --color-update-surface: var(--update-surface); --color-success-foreground: var(--success-foreground); --color-success: var(--success); --color-info-foreground: var(--info-foreground); --color-info: var(--info); --color-destructive-foreground: var(--destructive-foreground); --color-ring: var(--ring); --color-input: var(--contrast-input); --color-border: var(--contrast-border); --color-destructive: var(--destructive); --color-accent-foreground: var(--contrast-accent-foreground); --color-accent: var(--accent); --color-muted-foreground: var(--contrast-muted-foreground); --color-muted: var(--muted); --color-placeholder: var(--contrast-placeholder); --color-secondary-label: var(--contrast-secondary-label); --color-icon-muted: var(--contrast-icon-muted); --color-secondary-foreground: var(--contrast-secondary-foreground); --color-secondary: var(--secondary); --color-primary-foreground: var(--primary-foreground); --color-primary: var(--primary); --color-popover-foreground: var(--contrast-popover-foreground); --color-popover: var(--popover); --color-card-foreground: var(--contrast-card-foreground); --color-card: var(--card); --color-foreground: var(--contrast-foreground); --color-background: var(--background); --color-surface-raised: var(--surface-raised); --color-message: var(--message-surface); --color-message-foreground: var(--contrast-message-foreground); --color-message-action: var(--message-action); --color-message-action-foreground: var(--message-action-foreground); --color-message-action-hover: var(--message-action-hover); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--contrast-sidebar-foreground); --color-sidebar-muted-foreground: var(--contrast-sidebar-muted-foreground); --color-sidebar-control-surface: var(--sidebar-control-surface); --color-sidebar-row-hover: var(--sidebar-row-hover); --color-sidebar-row-active: var(--sidebar-row-active); --color-sidebar-row-selected: var(--sidebar-row-selected); --color-sidebar-border: var(--contrast-sidebar-border); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); --radius-2xl: calc(var(--radius) + 8px); --radius-3xl: calc(var(--radius) + 12px); --radius-4xl: calc(var(--radius) + 16px); @keyframes skeleton { /* Transform-only so the highlight sweep stays on the compositor, then a long hold with the band parked off-screen instead of a constant shimmer. */ 0% { transform: translateX(-100%); } 60%, 100% { transform: translateX(100%); } } @keyframes ghost-pulse { /* The loading ghosts' breath. Stepped like the status indicators, so however many bars a ghost holds, the compositor draws a handful of discrete frames per cycle rather than one per vsync — which on a 120Hz display is the difference between ~14 and ~288 updates. */ 0%, 42% { opacity: 1; animation-timing-function: steps(4); } 50%, 92% { opacity: 0.55; animation-timing-function: steps(4); } 100% { opacity: 1; } } @keyframes status-pulse { 0%, 40% { opacity: 1; animation-timing-function: steps(6); } 50%, 90% { opacity: 0.5; animation-timing-function: steps(6); } 100% { opacity: 1; } } @keyframes status-ping { /* Burst first (immediate feedback for click ripples), then hold invisible for the rest of the cycle. Mirrors animate-ping's 75%-scale start. */ 0% { opacity: 0.9; scale: 0.75; animation-timing-function: steps(8); } 40%, 100% { opacity: 0; scale: 2; } } } @utility surface-glass { background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { background: var(--background) !important; } } @utility alert-glass { --alert-glass-tint: transparent; background: linear-gradient( color-mix(in srgb, var(--alert-glass-tint) 4%, transparent), color-mix(in srgb, var(--alert-glass-tint) 4%, transparent) ), color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important; -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); &[data-variant="error"] { --alert-glass-tint: var(--destructive); } &[data-variant="info"] { --alert-glass-tint: var(--info); } &[data-variant="success"] { --alert-glass-tint: var(--success); } &[data-variant="warning"] { --alert-glass-tint: var(--warning); } @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { background: var(--background) !important; } } @utility dialog-glass { background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); border-color: color-mix(in srgb, var(--contrast-foreground) 10%, transparent); box-shadow: 0 24px 64px -24px rgb(0 0 0 / 65%); @variant dark { border-color: color-mix(in srgb, var(--color-white) 8%, transparent); box-shadow: inset 0 1px rgb(255 255 255 / 4%), 0 24px 72px -20px rgb(0 0 0 / 90%); } @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { background: var(--popover) !important; } } @utility dialog-backdrop { background: color-mix(in srgb, var(--background) 60%, transparent); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); @variant dark { background: color-mix(in srgb, var(--background) 64%, transparent); } } @utility dropdown-glass { background: color-mix( in srgb, var(--popover) 18%, color-mix(in srgb, var(--popover) var(--glass-opacity), transparent) ); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); border: 1px solid color-mix(in srgb, var(--contrast-foreground) 10%, transparent); @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { background: var(--popover) !important; } } @utility topbar-scroll-fade { --topbar-scroll-fade-height: 2.5rem; -webkit-mask-image: linear-gradient( to bottom, transparent 0%, rgb(0 0 0 / 10%) 10%, rgb(0 0 0 / 30%) 24%, rgb(0 0 0 / 58%) 42%, rgb(0 0 0 / 82%) 62%, rgb(0 0 0 / 96%) 82%, black 100% ), linear-gradient(black, black), linear-gradient(black, black); -webkit-mask-position: top, bottom, right; -webkit-mask-repeat: no-repeat; -webkit-mask-size: 100% var(--topbar-scroll-fade-height), 100% calc(100% - var(--topbar-scroll-fade-height)), var(--app-scrollbar-width) 100%; mask-image: linear-gradient( to bottom, transparent 0%, rgb(0 0 0 / 10%) 10%, rgb(0 0 0 / 30%) 24%, rgb(0 0 0 / 58%) 42%, rgb(0 0 0 / 82%) 62%, rgb(0 0 0 / 96%) 82%, black 100% ), linear-gradient(black, black), linear-gradient(black, black); mask-position: top, bottom, right; mask-repeat: no-repeat; mask-size: 100% var(--topbar-scroll-fade-height), 100% calc(100% - var(--topbar-scroll-fade-height)), var(--app-scrollbar-width) 100%; @variant sm { --topbar-scroll-fade-height: 3rem; } } /* Virtualizers own their native scroll element, so they cannot use ScrollArea's viewport fade. Keep the scrollbar lane opaque while sharing the same fade contract across those lists. */ @utility virtualized-scroll-fade { -webkit-mask-image: var(--virtualized-scroll-fade-mask), linear-gradient(black, black); mask-image: var(--virtualized-scroll-fade-mask), linear-gradient(black, black); -webkit-mask-position: left, right; mask-position: left, right; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: calc(100% - var(--app-scrollbar-width)) 100%, var(--app-scrollbar-width) 100%; mask-size: calc(100% - var(--app-scrollbar-width)) 100%, var(--app-scrollbar-width) 100%; } /* Stage-channel art needs a mask and pseudo-element gradient, so keep the behavior composable without tying it to the global components layer. */ @utility sidebar-stage-backdrop { --stage-fade: var(--sidebar-stage-fade, var(--app-chrome-background)); mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 92%); -webkit-mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 92%); &::after { content: ""; position: absolute; inset: 0; background: linear-gradient( to bottom, transparent 0%, transparent 28%, color-mix(in srgb, var(--stage-fade) 10%, transparent) 40%, color-mix(in srgb, var(--stage-fade) 30%, transparent) 52%, color-mix(in srgb, var(--stage-fade) 58%, transparent) 64%, color-mix(in srgb, var(--stage-fade) 82%, transparent) 75%, color-mix(in srgb, var(--stage-fade) 96%, transparent) 85%, var(--stage-fade) 93% ); } } @keyframes live-activity-focus { 0% { transform: translateX(0); } 100% { transform: translateX(100%); } } @keyframes live-activity-focus-counter { 0% { transform: translateX(0); } 100% { transform: translateX(-100%); } } @utility live-activity-focus { --live-activity-focus-width: 4.5rem; right: auto; left: calc(-1 * var(--live-activity-focus-width)); width: calc(100% + var(--live-activity-focus-width) + var(--live-activity-focus-width)); -webkit-mask-image: linear-gradient( to right, transparent 0, rgb(0 0 0 / 12%) 0.675rem, rgb(0 0 0 / 55%) 1.575rem, black 2.25rem, rgb(0 0 0 / 55%) 2.925rem, rgb(0 0 0 / 12%) 3.825rem, transparent var(--live-activity-focus-width), transparent 100% ); -webkit-mask-repeat: no-repeat; mask-image: linear-gradient( to right, transparent 0, rgb(0 0 0 / 12%) 0.675rem, rgb(0 0 0 / 55%) 1.575rem, black 2.25rem, rgb(0 0 0 / 55%) 2.925rem, rgb(0 0 0 / 12%) 3.825rem, transparent var(--live-activity-focus-width), transparent 100% ); mask-repeat: no-repeat; animation: live-activity-focus 2.2s linear infinite; will-change: transform; @media (prefers-reduced-motion: reduce) { animation: none; opacity: 0; will-change: auto; } }