diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..f1035b9 --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,547 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + /* Porcelain light theme */ + --background: 220 20% 96%; + --foreground: 224 18% 12%; + --card: 0 0% 100%; + --card-foreground: 224 18% 12%; + --primary: 224 88% 53%; + --primary-foreground: 0 0% 100%; + --secondary: 220 14% 92%; + --secondary-foreground: 224 18% 14%; + --muted: 220 14% 93%; + --muted-foreground: 224 8% 44%; + --accent: 30 100% 48%; + --accent-foreground: 224 18% 12%; + --destructive: 4 74% 49%; + --destructive-foreground: 0 0% 100%; + --success: 154 84% 30%; + --warning: 31 95% 42%; + --border: 220 12% 86%; + --input: 220 12% 86%; + --ring: 224 88% 53%; + + /* Aurora companions (hue triplets) */ + --aurora-a: 224 88% 60%; + --aurora-b: 200 80% 55%; + --aurora-c: 0 0% 70%; + --aurora-opacity: 0.18; + + /* Liquid glass */ + --glass-alpha: 0.78; + --glass-blur: 3px; + --glass-saturate: 112%; + --glass-edge-alpha: 0.7; + + --radius: 12px; + --sidebar-width: 256px; + --header-height: 56px; +} + +/* Pitch-black dark theme — strictly neutral grays, zero hue cast. + * Color enters only through the user-selected accent. */ +.dark { + --background: 0 0% 0%; + --foreground: 0 0% 96%; + --card: 0 0% 10%; + --card-foreground: 0 0% 95%; + --primary: 222 95% 63%; + --primary-foreground: 0 0% 100%; + --secondary: 0 0% 14%; + --secondary-foreground: 0 0% 95%; + --muted: 0 0% 12%; + --muted-foreground: 0 0% 64%; + --accent: 31 100% 54%; + --accent-foreground: 0 0% 0%; + --destructive: 4 78% 56%; + --destructive-foreground: 0 0% 100%; + --success: 154 70% 44%; + --warning: 36 95% 56%; + --border: 0 0% 17%; + --input: 0 0% 17%; + --ring: 222 95% 63%; + + --aurora-opacity: 0.16; + --glass-alpha: 0.62; + --glass-edge-alpha: 0.14; +} + +/* --------------------------------------------------------------------------- + * Accent presets — applied as data-accent on . + * ------------------------------------------------------------------------- */ +[data-accent="violet"] { + --primary: 261 78% 56%; + --ring: 261 78% 56%; + --aurora-a: 261 80% 62%; +} +.dark[data-accent="violet"] { + --primary: 260 90% 67%; + --ring: 260 90% 67%; +} + +[data-accent="emerald"] { + --primary: 161 92% 30%; + --ring: 161 92% 30%; + --aurora-a: 160 80% 45%; +} +.dark[data-accent="emerald"] { + --primary: 158 68% 44%; + --ring: 158 68% 44%; +} + +[data-accent="sunset"] { + --primary: 24 94% 50%; + --primary-foreground: 24 90% 98%; + --ring: 24 94% 50%; + --aurora-a: 24 94% 55%; +} +.dark[data-accent="sunset"] { + --primary: 26 96% 56%; + --primary-foreground: 24 95% 7%; + --ring: 26 96% 56%; +} + +[data-accent="rose"] { + --primary: 345 82% 51%; + --ring: 345 82% 51%; + --aurora-a: 345 85% 60%; +} +.dark[data-accent="rose"] { + --primary: 347 90% 64%; + --ring: 347 90% 64%; +} + +[data-accent="cyan"] { + --primary: 196 95% 38%; + --ring: 196 95% 38%; + --aurora-a: 196 95% 50%; +} +.dark[data-accent="cyan"] { + --primary: 194 92% 52%; + --primary-foreground: 200 95% 6%; + --ring: 194 92% 52%; +} + +/* In dark mode the ambient companions are strictly monochrome — only the + * primary blob carries the accent hue. Declared after the accent presets so + * it wins regardless of the selected accent. */ +.dark { + --aurora-b: 0 0% 55%; + --aurora-c: 0 0% 35%; +} + +/* --------------------------------------------------------------------------- + * Glass intensity — applied as data-glass on . + * ------------------------------------------------------------------------- */ +[data-glass="off"] { + --glass-alpha: 1; + --glass-blur: 0px; + --glass-saturate: 100%; +} +[data-glass="subtle"] { + --glass-alpha: 0.86; + --glass-blur: 2px; + --glass-saturate: 108%; +} +[data-glass="heavy"] { + --glass-alpha: 0.54; + --glass-blur: 10px; + --glass-saturate: 135%; +} +.dark[data-glass="subtle"] { + --glass-alpha: 0.76; +} +.dark[data-glass="heavy"] { + --glass-alpha: 0.34; +} + +/* --------------------------------------------------------------------------- + * Corner radius — applied as data-radius on . + * ------------------------------------------------------------------------- */ +[data-radius="sharp"] { + --radius: 6px; +} +[data-radius="soft"] { + --radius: 18px; +} + +/* --------------------------------------------------------------------------- + * Base + * ------------------------------------------------------------------------- */ +* { + border-color: hsl(var(--border)); +} + +* { + scrollbar-width: thin; + scrollbar-color: hsl(var(--border)) transparent; +} + +::-webkit-scrollbar { + width: 9px; + height: 9px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: hsl(var(--border)); + border: 3px solid transparent; + border-radius: 999px; + background-clip: content-box; +} + +::-webkit-scrollbar-thumb:hover { + background: hsl(var(--muted-foreground) / 0.55); + border: 3px solid transparent; + background-clip: content-box; +} + +html { + min-height: 100%; +} + +body { + min-height: 100%; + background: hsl(var(--background)); + color: hsl(var(--foreground)); + font-feature-settings: + "rlig" 1, + "calt" 1; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +button, +input, +select, +textarea { + font: inherit; +} + +::selection { + background: hsl(var(--primary) / 0.22); +} + +kbd { + display: inline-flex; + align-items: center; + gap: 2px; + border: 1px solid hsl(var(--border)); + border-bottom-width: 2px; + border-radius: 6px; + background: hsl(var(--muted) / 0.8); + padding: 1px 6px; + font-family: var(--font-mono), ui-monospace, monospace; + font-size: 11px; + color: hsl(var(--muted-foreground)); +} + +@media (prefers-reduced-motion: reduce) { + *, + ::before, + ::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + } +} + +.focus-ring { + @apply outline-none ring-offset-2 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring; +} + +.density-compact { + --header-height: 56px; +} + +.density-comfortable { + --header-height: 64px; +} + +/* --------------------------------------------------------------------------- + * Liquid glass surfaces — iOS-style: translucent fill, saturated backdrop + * blur, and a refraction ring made of four inset specular edges (bright top, + * faint sides and bottom). + * ------------------------------------------------------------------------- */ +@layer components { + .glass-panel { + position: relative; + background: + linear-gradient( + 180deg, + hsl(0 0% 100% / 0.09), + hsl(0 0% 100% / 0.02) 38%, + transparent 70% + ), + hsl(var(--card) / var(--glass-alpha)); + -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)); + backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)); + border: 1px solid hsl(var(--border) / 0.7); + box-shadow: + inset 0 1px 0 0 hsl(0 0% 100% / var(--glass-edge-alpha)), + inset 0 -1px 0 0 hsl(0 0% 100% / calc(var(--glass-edge-alpha) * 0.3)), + inset 1px 0 0 0 hsl(0 0% 100% / calc(var(--glass-edge-alpha) * 0.2)), + inset -1px 0 0 0 hsl(0 0% 100% / calc(var(--glass-edge-alpha) * 0.2)), + 0 1px 2px rgb(0 0 0 / 0.06), + 0 16px 40px -24px rgb(0 0 0 / 0.3); + } + + .dark .glass-panel { + border-color: hsl(0 0% 100% / 0.1); + box-shadow: + inset 0 1px 0 0 hsl(0 0% 100% / var(--glass-edge-alpha)), + inset 0 -1px 0 0 hsl(0 0% 100% / calc(var(--glass-edge-alpha) * 0.3)), + inset 1px 0 0 0 hsl(0 0% 100% / calc(var(--glass-edge-alpha) * 0.2)), + inset -1px 0 0 0 hsl(0 0% 100% / calc(var(--glass-edge-alpha) * 0.2)), + 0 1px 2px rgb(0 0 0 / 0.5), + 0 20px 48px -28px rgb(0 0 0 / 0.85); + } + + /* Chrome surfaces: floating header + sidebar rails. */ + .glass-bar { + background: + linear-gradient( + 180deg, + hsl(0 0% 100% / 0.08), + hsl(0 0% 100% / 0.02) 45%, + transparent 75% + ), + hsl(var(--card) / min(calc(var(--glass-alpha) + 0.12), 0.96)); + -webkit-backdrop-filter: blur(calc(var(--glass-blur) + 1px)) saturate(var(--glass-saturate)); + backdrop-filter: blur(calc(var(--glass-blur) + 1px)) saturate(var(--glass-saturate)); + box-shadow: + inset 0 1px 0 0 hsl(0 0% 100% / var(--glass-edge-alpha)), + inset 0 -1px 0 0 hsl(0 0% 100% / calc(var(--glass-edge-alpha) * 0.25)), + 0 12px 32px -20px rgb(0 0 0 / 0.45); + } + + .dark .glass-bar { + box-shadow: + inset 0 1px 0 0 hsl(0 0% 100% / var(--glass-edge-alpha)), + inset 0 -1px 0 0 hsl(0 0% 100% / calc(var(--glass-edge-alpha) * 0.25)), + 0 12px 36px -18px rgb(0 0 0 / 0.9); + } + + /* Floating surfaces: dialogs, popovers, menus. Most opaque. */ + .glass-modal { + background: + linear-gradient(180deg, hsl(0 0% 100% / 0.08), transparent 40%), + hsl(var(--card) / min(calc(var(--glass-alpha) + 0.28), 1)); + -webkit-backdrop-filter: blur(calc(var(--glass-blur) + 2px)) saturate(var(--glass-saturate)); + backdrop-filter: blur(calc(var(--glass-blur) + 2px)) saturate(var(--glass-saturate)); + border: 1px solid hsl(var(--border) / 0.75); + box-shadow: + inset 0 1px 0 0 hsl(0 0% 100% / var(--glass-edge-alpha)), + inset 0 -1px 0 0 hsl(0 0% 100% / calc(var(--glass-edge-alpha) * 0.3)), + 0 24px 64px -24px rgb(0 0 0 / 0.5); + } + + .dark .glass-modal { + border-color: hsl(0 0% 100% / 0.12); + box-shadow: + inset 0 1px 0 0 hsl(0 0% 100% / var(--glass-edge-alpha)), + inset 0 -1px 0 0 hsl(0 0% 100% / calc(var(--glass-edge-alpha) * 0.3)), + 0 24px 72px -20px rgb(0 0 0 / 0.95); + } + + .text-gradient { + background: linear-gradient(100deg, hsl(var(--foreground)) 35%, hsl(var(--primary)) 140%); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + + .skeleton { + position: relative; + overflow: hidden; + background: hsl(var(--muted) / 0.7); + } + + .skeleton::after { + content: ""; + position: absolute; + inset: 0; + transform: translateX(-100%); + background: linear-gradient(90deg, transparent, hsl(var(--foreground) / 0.07), transparent); + animation: shimmer 1.6s infinite; + } +} + +/* Legacy shadow helpers, softened to match the glass language. */ +.dark .shadow-panel { + box-shadow: 0 1px 0 hsl(var(--border) / 0.7); +} + +.dark .shadow-raised { + box-shadow: 0 18px 48px rgba(0, 0, 0, 0.72); +} + +/* --------------------------------------------------------------------------- + * Ambient background: aurora blobs + grid + grain. + * Hidden entirely when data-ambiance="off". + * ------------------------------------------------------------------------- */ +.aurora { + position: absolute; + border-radius: 9999px; + filter: blur(52px); + opacity: var(--aurora-opacity); +} + +.aurora-1 { + top: -22vh; + left: -12vw; + height: 60vh; + width: 46vw; + min-width: 420px; + min-height: 420px; + background: radial-gradient(closest-side, hsl(var(--aurora-a) / 0.7), transparent 70%); + animation: aurora-drift-1 48s ease-in-out infinite alternate; +} + +.aurora-2 { + top: 4vh; + right: -16vw; + height: 56vh; + width: 42vw; + min-width: 380px; + min-height: 380px; + background: radial-gradient(closest-side, hsl(var(--aurora-b) / 0.4), transparent 70%); + animation: aurora-drift-2 58s ease-in-out infinite alternate; +} + +.aurora-3 { + bottom: -26vh; + left: 24vw; + height: 54vh; + width: 44vw; + min-width: 380px; + min-height: 380px; + background: radial-gradient(closest-side, hsl(var(--aurora-c) / 0.3), transparent 70%); + animation: aurora-drift-3 64s ease-in-out infinite alternate; +} + +.ambient-grid { + position: absolute; + inset: 0; + background-image: + linear-gradient(hsl(var(--foreground) / 0.04) 1px, transparent 1px), + linear-gradient(90deg, hsl(var(--foreground) / 0.04) 1px, transparent 1px); + background-size: 56px 56px; + -webkit-mask-image: radial-gradient(ellipse 90% 64% at 50% 0%, black 30%, transparent 75%); + mask-image: radial-gradient(ellipse 90% 64% at 50% 0%, black 30%, transparent 75%); +} + +.grain { + position: absolute; + inset: 0; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E"); + background-size: 160px 160px; + opacity: 0.028; + mix-blend-mode: overlay; +} + +.dark .grain { + opacity: 0.06; +} + +[data-ambiance="off"] .aurora, +[data-ambiance="off"] .ambient-grid, +[data-ambiance="off"] .grain { + display: none; +} + +@keyframes aurora-drift-1 { + from { + transform: translate3d(0, 0, 0) scale(1); + } + to { + transform: translate3d(10vw, 8vh, 0) scale(1.15); + } +} + +@keyframes aurora-drift-2 { + from { + transform: translate3d(0, 0, 0) scale(1.1) rotate(0deg); + } + to { + transform: translate3d(-8vw, 12vh, 0) scale(0.95) rotate(20deg); + } +} + +@keyframes aurora-drift-3 { + from { + transform: translate3d(0, 0, 0) scale(1); + } + to { + transform: translate3d(-10vw, -8vh, 0) scale(1.2); + } +} + +/* --------------------------------------------------------------------------- + * Motion: entrance choreography + * ------------------------------------------------------------------------- */ +@keyframes rise { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: none; + } +} + +@keyframes shimmer { + 100% { + transform: translateX(100%); + } +} + +.anim-rise { + animation: rise 0.16s cubic-bezier(0.22, 0.61, 0.36, 1) both; +} + +.stagger > * { + animation: rise 0.16s cubic-bezier(0.22, 0.61, 0.36, 1) both; +} + +.stagger > *:nth-child(1) { + animation-delay: 0.02s; +} +.stagger > *:nth-child(2) { + animation-delay: 0.03s; +} +.stagger > *:nth-child(3) { + animation-delay: 0.05s; +} +.stagger > *:nth-child(4) { + animation-delay: 0.07s; +} +.stagger > *:nth-child(5) { + animation-delay: 0.08s; +} +.stagger > *:nth-child(6) { + animation-delay: 0.09s; +} +.stagger > *:nth-child(7) { + animation-delay: 0.1s; +} +.stagger > *:nth-child(8) { + animation-delay: 0.11s; +} +.stagger > *:nth-child(9) { + animation-delay: 0.12s; +} +.stagger > *:nth-child(10) { + animation-delay: 0.13s; +} +.stagger > *:nth-child(n + 11) { + animation-delay: 0.14s; +}