Files
movies-diary/spa/src/aero-theme.css
Gabriel Kaszewski 9dd9df1f73
All checks were successful
CI / Check / Test (push) Successful in 1h11m34s
perf(spa): drop backdrop blur on cards, fixed wallpaper layer, auto reduced effects on low-end (#24)
Reviewed-on: #24
2026-10-12 18:58:50 +00:00

394 lines
12 KiB
CSS

/*
* Frutiger Aero Theme for shadcn + Tailwind v4
* Drop this file + a background image into any shadcn project.
* Import it in your index.css: @import "./aero-theme.css";
*/
/* ── Aero color overrides ─────────────────────────────────────── */
:root {
--background: transparent;
--foreground: oklch(0.985 0 0);
--card: rgba(255, 255, 255, 0.08);
--card-foreground: oklch(0.985 0 0);
--popover: rgba(20, 20, 30, 0.85);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.852 0.199 91.936);
--primary-foreground: #fff;
--secondary: rgba(255, 255, 255, 0.08);
--secondary-foreground: oklch(0.985 0 0);
--muted: rgba(255, 255, 255, 0.06);
--muted-foreground: rgba(255, 255, 255, 0.6);
--accent: rgba(255, 255, 255, 0.1);
--accent-foreground: oklch(0.985 0 0);
--destructive: rgba(200, 60, 60, 0.65);
--border: rgba(255, 255, 255, 0.15);
--input: rgba(255, 255, 255, 0.1);
--ring: oklch(0.852 0.199 91.936 / 0.4);
--chart-1: oklch(0.852 0.199 91.936);
--chart-2: oklch(0.89 0.13 91.936);
--chart-3: oklch(0.93 0.07 91.936);
--chart-4: rgba(255, 255, 255, 0.4);
--chart-5: rgba(255, 255, 255, 0.2);
--sidebar: rgba(0, 0, 0, 0.4);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.852 0.199 91.936);
--sidebar-primary-foreground: #fff;
--sidebar-accent: rgba(255, 255, 255, 0.1);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: rgba(255, 255, 255, 0.1);
--sidebar-ring: oklch(0.852 0.199 91.936 / 0.4);
--aero-primary: oklch(0.852 0.199 91.936);
--aero-primary-mid: oklch(0.89 0.13 91.936);
--aero-primary-light: oklch(0.93 0.07 91.936);
--aero-primary-glow: oklch(0.852 0.199 91.936 / 0.3);
--aero-glass-bg: rgba(255, 255, 255, 0.12);
/* Blur-free surface for the many in-flow elements (cards, list rows): a dark
base keeps text readable over the crisp wallpaper, the white tint keeps the
glass tone. */
--aero-surface-bg: linear-gradient(rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1)),
rgba(12, 12, 20, 0.35);
--aero-glass-border: rgba(255, 255, 255, 0.2);
--aero-glass-shadow: 0 8px 32px oklch(0.852 0.199 91.936 / 0.1);
--aero-glass-inset: inset 0 1px 0 rgba(255, 255, 255, 0.4);
--aero-blur: 12px;
}
/* Force dark — override any .dark block from shadcn defaults */
.dark {
--background: transparent;
--foreground: oklch(0.985 0 0);
--card: rgba(255, 255, 255, 0.08);
--card-foreground: oklch(0.985 0 0);
--popover: rgba(20, 20, 30, 0.85);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.852 0.199 91.936);
--primary-foreground: #fff;
--secondary: rgba(255, 255, 255, 0.08);
--secondary-foreground: oklch(0.985 0 0);
--muted: rgba(255, 255, 255, 0.06);
--muted-foreground: rgba(255, 255, 255, 0.6);
--accent: rgba(255, 255, 255, 0.1);
--accent-foreground: oklch(0.985 0 0);
--destructive: rgba(200, 60, 60, 0.65);
--border: rgba(255, 255, 255, 0.15);
--input: rgba(255, 255, 255, 0.1);
--ring: oklch(0.852 0.199 91.936 / 0.4);
--chart-1: oklch(0.852 0.199 91.936);
--chart-2: oklch(0.89 0.13 91.936);
--chart-3: oklch(0.93 0.07 91.936);
--chart-4: rgba(255, 255, 255, 0.4);
--chart-5: rgba(255, 255, 255, 0.2);
--sidebar: rgba(0, 0, 0, 0.4);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.852 0.199 91.936);
--sidebar-primary-foreground: #fff;
--sidebar-accent: rgba(255, 255, 255, 0.1);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: rgba(255, 255, 255, 0.1);
--sidebar-ring: oklch(0.852 0.199 91.936 / 0.4);
}
/* ── Background treatment ─────────────────────────────────────── */
/* Wallpaper lives on its own fixed layer rather than `background-attachment:
fixed`, which repaints the whole page on every scroll frame (and is ignored
by iOS). A fixed layer is painted once and composited. */
body {
background: #0a0a0f;
}
body::before {
content: "";
position: fixed;
inset: 0;
background:
linear-gradient(rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0.25)),
url("./assets/background.avif") center / cover no-repeat;
z-index: 0;
pointer-events: none;
}
body > #root {
position: relative;
z-index: 1;
}
/* ── Utility classes ──────────────────────────────────────────── */
/* Performance rule: `backdrop-filter` is reserved for few, floating surfaces
(bottom bar, overlays, toasts, popovers). In-flow elements that repeat down
a list (`glass`, `glass-card`, cards) only ever sit on the static wallpaper,
so they use the blur-free `--aero-surface-bg` — dozens of live blurs
re-rendered per scroll frame is what makes low-end devices lag. */
@utility glass {
background: var(--aero-surface-bg);
border: 1px solid var(--aero-glass-border);
}
@utility glass-card {
background: var(--aero-surface-bg);
border: 1px solid var(--aero-glass-border);
box-shadow: var(--aero-glass-shadow), var(--aero-glass-inset);
position: relative;
overflow: hidden;
}
@utility glass-heavy {
backdrop-filter: blur(var(--aero-blur));
-webkit-backdrop-filter: blur(var(--aero-blur));
background: rgba(0, 0, 0, 0.55);
border: 1px solid rgba(255, 255, 255, 0.1);
}
@utility aero-glow {
text-shadow:
0 0 8px var(--aero-primary-glow),
0 0 2px var(--aero-primary);
}
@utility aero-pill {
border-radius: 999px;
border: 1px solid var(--aero-glass-border);
background: transparent;
}
/* ── Glass card gradient overlay ──────────────────────────────── */
.glass-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 50%;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.1), transparent);
border-radius: inherit;
pointer-events: none;
z-index: 1;
}
/* ── Hover lift (pointer devices only) ────────────────────────── */
@media (hover: hover) {
.aero-lift {
transition: transform 0.2s, box-shadow 0.2s;
}
.aero-lift:hover {
transform: translateY(-2px);
box-shadow:
0 12px 40px var(--aero-primary-glow),
var(--aero-glass-inset);
}
}
/* ── shadcn data-slot overrides ───────────────────────────────── */
[data-slot="card"] {
background: var(--aero-surface-bg);
border-color: var(--aero-glass-border);
box-shadow: var(--aero-glass-shadow), var(--aero-glass-inset);
--tw-ring-color: transparent;
--tw-ring-shadow: none;
}
[data-slot="dialog-overlay"] {
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
/* Panels sit on an already-blurred overlay and are near-opaque, so a second
full-screen blur pass on the panel itself buys nothing visible. */
[data-slot="dialog-content"] {
background: rgba(20, 20, 30, 0.9);
border: 1px solid rgba(255, 255, 255, 0.15);
--tw-ring-color: transparent;
--tw-ring-shadow: none;
}
[data-slot="sheet-overlay"] {
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
[data-slot="sheet-content"] {
background: rgba(20, 20, 30, 0.9);
border-color: rgba(255, 255, 255, 0.15);
}
[data-slot="input"] {
background: rgba(255, 255, 255, 0.08);
border-color: var(--aero-glass-border);
}
[data-slot="input"]:focus {
border-color: var(--aero-primary);
box-shadow: 0 0 0 3px oklch(0.852 0.199 91.936 / 0.2);
}
/* Primary button — gold gradient with Aero bevel */
[data-slot="button"][data-variant="default"] {
background: linear-gradient(
135deg,
var(--aero-primary-mid) 0%,
var(--aero-primary) 60%,
oklch(0.72 0.199 91.936) 100%
);
color: #fff;
border: none;
box-shadow:
0 4px 16px var(--aero-primary-glow),
inset 0 1px 0 rgba(255, 255, 255, 0.35);
position: relative;
overflow: hidden;
}
[data-slot="button"][data-variant="default"]::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 50%;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.25), transparent);
border-radius: inherit;
pointer-events: none;
}
@media (hover: hover) {
[data-slot="button"][data-variant="default"]:hover {
transform: translateY(-1px);
box-shadow:
0 6px 24px var(--aero-primary-glow),
inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
}
/* Destructive button — glassmorphic red */
[data-slot="button"][data-variant="destructive"] {
background: rgba(200, 60, 60, 0.65);
border: 1px solid rgba(220, 80, 80, 0.3);
}
/* Ghost/outline buttons — subtle glass on hover */
[data-slot="button"][data-variant="ghost"]:hover,
[data-slot="button"][data-variant="outline"]:hover {
background: rgba(255, 255, 255, 0.1);
}
/* Drawer content (used by log-sheet, wrapup, etc.) */
[data-slot="drawer-content"] {
background: rgba(20, 20, 30, 0.9);
}
[data-slot="drawer-overlay"] {
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
/* Sonner toast */
[data-sonner-toaster] [data-sonner-toast] {
background: rgba(20, 20, 30, 0.85);
backdrop-filter: blur(var(--aero-blur));
-webkit-backdrop-filter: blur(var(--aero-blur));
border-color: rgba(255, 255, 255, 0.15);
color: oklch(0.985 0 0);
}
/* Chart label readability */
[data-slot="chart"] .recharts-cartesian-axis-tick text {
fill: rgba(255, 255, 255, 0.85) !important;
}
/* Tooltip — default shadcn uses bg-foreground which is white in this theme */
[data-slot="tooltip-content"] {
background: var(--popover);
color: var(--popover-foreground);
backdrop-filter: blur(var(--aero-blur));
-webkit-backdrop-filter: blur(var(--aero-blur));
border: 1px solid rgba(255, 255, 255, 0.15);
}
[data-slot="tooltip-content"] .lucide,
[data-slot="tooltip-content"] [data-slot="tooltip-arrow"] {
color: var(--popover-foreground);
}
/* Context menu */
[data-slot="context-menu-content"] {
background: var(--popover);
backdrop-filter: blur(var(--aero-blur));
-webkit-backdrop-filter: blur(var(--aero-blur));
border-color: rgba(255, 255, 255, 0.15);
}
/* Star glow for filled amber stars */
.aero-star-filled {
filter: drop-shadow(0 0 4px var(--aero-primary-glow)) drop-shadow(0 0 1px var(--aero-primary));
}
/* ── Reduced effects ──────────────────────────────────────────
No live blur at all: set by the OS preference, or by `data-effects="reduced"`
which `lib/effects.ts` puts on <html> for low-memory / low-core devices.
Floating surfaces get more opaque to stay readable without it. */
@media (prefers-reduced-transparency: reduce) {
:root {
--aero-blur: 0px;
}
* {
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
}
:root[data-effects="reduced"] {
--aero-blur: 0px;
}
:root[data-effects="reduced"] * {
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
:root[data-effects="reduced"] .glass-heavy {
background: rgba(10, 10, 16, 0.92);
}
:root[data-effects="reduced"] [data-slot$="-overlay"] {
background: rgba(0, 0, 0, 0.6);
}
/* ── Keyframe animations ────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
@keyframes aero-page-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.aero-page-in {
animation: aero-page-in 0.25s ease-out both;
}
@keyframes aero-star-pop {
0% { transform: scale(1); }
40% { transform: scale(1.35); }
100% { transform: scale(1); }
}
.aero-star-pop {
animation: aero-star-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.aero-poster-hover {
transition: transform 0.2s ease-out;
}
.aero-poster-hover:hover {
transform: scale(1.04);
}
}