Files
such.software/apps/web/src/app/globals.css
T
jwinterm a238412103 feat(bento): quiet tiles at rest — chrome fades, title + render float
At rest the card chrome (opaque bg, 64px frost, border, shadow) is now nearly
invisible so tiles read as just the title + the 3D render/image floating on the
page (especially clean on mobile, where every tile is 'at rest'). The glass
lights up + the colour tint paints on hover / focus / tap. Title and image keep
their visibility.
2026-07-08 11:15:01 -04:00

284 lines
7.7 KiB
CSS

@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
@layer components {
/* High-contrast content masking for accessibility and streamer protection */
.mask-backdrop {
background-color: hsl(var(--card) / 0.98);
backdrop-filter: blur(64px);
-webkit-backdrop-filter: blur(64px);
border-width: 2px;
border-color: hsl(var(--border) / 0.5);
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
position: relative;
overflow: hidden;
transition: transform 300ms cubic-bezier(0.16, 1, 0.3, 1),
box-shadow 300ms cubic-bezier(0.16, 1, 0.3, 1),
border-color 300ms ease;
}
.section-container {
@apply mask-backdrop rounded-[2.5rem] p-12 md:p-20 text-card-foreground;
}
.glass-card {
@apply mask-backdrop rounded-[2rem] p-10 text-card-foreground;
}
.glass-card:hover {
border-color: hsl(var(--primary) / 0.3);
}
/* Bento grid entrance animation */
@keyframes bento-enter {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.bento-item {
animation: bento-enter 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* Quiet bento: at rest the card chrome (opaque bg, 64px frost, border, shadow) is
nearly invisible so only the title + the 3D render/image float on the page; the
glass lights up on hover / focus / tap. Title + image keep their own visibility.
Higher specificity than .mask-backdrop, so it wins at rest without !important. */
.bento-item .glass-card {
background-color: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border-color: transparent;
box-shadow: none;
transition: background-color 0.3s ease, backdrop-filter 0.3s ease,
border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease;
}
.bento-item:hover .glass-card,
.bento-item:focus-within .glass-card {
backdrop-filter: blur(64px);
-webkit-backdrop-filter: blur(64px);
transform: translateY(-4px);
box-shadow: 0 32px 64px -16px rgb(0 0 0 / 0.3);
}
/* Focus-visible ring for keyboard navigation */
.bento-item:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 4px;
border-radius: 2.5rem;
}
/* Respect reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
.bento-item {
animation: none;
}
.bento-item:hover .glass-card {
transform: none;
}
.glass-card {
transition: border-color 300ms;
}
.glass-card .object-contain,
.glass-card .object-cover {
transition: none !important;
}
}
/* Typography customization for blog posts */
.prose {
--tw-prose-body: hsl(var(--foreground));
--tw-prose-headings: hsl(var(--foreground));
--tw-prose-lead: hsl(var(--muted-foreground));
--tw-prose-links: hsl(var(--primary));
--tw-prose-bold: hsl(var(--foreground));
--tw-prose-counters: hsl(var(--muted-foreground));
--tw-prose-bullets: hsl(var(--muted-foreground));
--tw-prose-hr: hsl(var(--border));
--tw-prose-quotes: hsl(var(--foreground));
--tw-prose-quote-borders: hsl(var(--border));
--tw-prose-captions: hsl(var(--muted-foreground));
--tw-prose-code: hsl(var(--foreground));
--tw-prose-pre-code: hsl(var(--foreground));
--tw-prose-pre-bg: hsl(var(--muted));
--tw-prose-th-borders: hsl(var(--border));
--tw-prose-td-borders: hsl(var(--border));
}
.prose h2 {
@apply text-xl font-semibold mt-10 mb-4 pb-2 border-b border-border;
}
.prose h3 {
@apply text-lg font-medium mt-6 mb-3;
}
.prose p {
@apply leading-relaxed mb-4;
}
.prose ul {
@apply my-4 ml-4 space-y-2;
}
.prose li {
@apply leading-relaxed;
}
.prose a {
@apply text-emerald-600 dark:text-emerald-400 underline underline-offset-2 hover:text-emerald-700 dark:hover:text-emerald-300 transition-colors;
}
.prose strong {
@apply font-semibold;
}
.prose hr {
@apply my-8 border-border;
}
.prose table {
@apply w-full my-6 text-sm;
}
.prose thead th {
@apply text-left font-semibold p-3 bg-muted/50 border-b border-border;
}
.prose tbody td {
@apply p-3 border-b border-border;
}
.prose tbody tr:hover {
@apply bg-muted/30;
}
}
/* Respect users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Splash logo zoom on click-to-enter (CSS, so the logo needs no framer-motion
wrapper and stays a clean LCP candidate). */
@keyframes splash-zoom { 0% { transform: scale(1); } 50% { transform: scale(1.06); } 100% { transform: scale(1.14); } }
.splash-zoom { animation: splash-zoom 0.8s ease-in-out forwards; }
/* Click-to-enter Cherenkov wash (was framer-motion; now CSS so the splash ships no
JS animation library). */
@keyframes splash-wash { 0% { opacity: 0; } 50% { opacity: 0.9; } 100% { opacity: 0; } }
.splash-wash { animation: splash-wash 0.8s ease-in-out forwards; }
/* Tiny terminal inside the nebula monitor: type "such" once, backspace, then a blinking
caret on the left. width via steps() is the typewriter; border-right is the caret. */
.nebula-term {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 9px;
line-height: 1;
letter-spacing: 0.5px;
white-space: nowrap;
overflow: hidden;
width: 0;
border-right: 1.5px solid currentColor;
/* type "such", hold, backspace (nebula-type, once); blink a finite number of times
(nebula-caret); then fade the whole thing out so it stops being distracting. */
/* blink ~4 times, type "such", backspace, blink ~4 more, then fade away. */
animation:
nebula-type 2.2s steps(4, end) 3.2s forwards,
nebula-caret 0.8s steps(1, end) 11,
nebula-term-out 0.6s ease-in 8.4s forwards;
}
@keyframes nebula-type {
0% { width: 0; }
36% { width: 4ch; }
64% { width: 4ch; }
90% { width: 0; }
100% { width: 0; }
}
@keyframes nebula-caret { 0%, 50% { border-color: currentColor; } 50.01%, 100% { border-color: transparent; } }
@keyframes nebula-term-out { to { opacity: 0; } }