@import "tailwindcss"; :root { --background: #0a0a0a; --foreground: #e8dfcc; --foreground-muted: #a89e8a; --surface: #141414; --surface-elevated: #1c1c1c; --border: #2a2a2a; --accent: #c4b896; /* Two curves for the whole site. `expo` for anything that enters or leaves (it lands hard, then settles), `soft` for hover states that must feel immediate. Framer Motion uses the same values as arrays. */ --ease-out-expo: cubic-bezier(0.22, 1, 0.36, 1); --ease-out-soft: cubic-bezier(0.33, 1, 0.68, 1); } @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --color-foreground-muted: var(--foreground-muted); --color-surface: var(--surface); --color-surface-elevated: var(--surface-elevated); --color-border: var(--border); --color-accent: var(--accent); --font-display: var(--font-bebas), "Bebas Neue", "Arial Narrow", sans-serif; --font-sans: var(--font-inter), system-ui, -apple-system, sans-serif; } * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } html { scroll-behavior: smooth; } /* In-page anchors must clear the fixed navbar. */ [id] { scroll-margin-top: 6rem; } html, body { background: var(--background); color: var(--foreground); font-family: var(--font-sans); } p { text-wrap: pretty; } ::selection { background: var(--accent); color: var(--background); } ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-track { background: var(--background); } ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: var(--foreground-muted); } .font-display { font-family: var(--font-display); letter-spacing: 0.02em; } .text-balance { text-wrap: balance; } @keyframes orb-pulse { 0%, 100% { opacity: 0.55; transform: translate(-50%, -50%) scale(1); } 50% { opacity: 0.85; transform: translate(-50%, -50%) scale(1.08); } } .animate-orb-pulse { animation: orb-pulse 6s ease-in-out infinite; } /* --------------------------------------------------------------------------- Micro-interactions Four primitives Tailwind cannot express in a utility. Everything else (lift, press, arrow nudge) stays inline as `hover:` / `active:` classes. --------------------------------------------------------------------------- */ /* A single light sweep across a filled button on hover. Transition rather than an animation, so moving the pointer away reverses it instead of finishing. */ .sheen::after { content: ""; position: absolute; inset: 0; pointer-events: none; transform: translateX(-135%) skewX(-18deg); background: linear-gradient( 90deg, transparent, rgba(255, 255, 255, 0.35), transparent ); transition: transform 750ms var(--ease-out-soft); } .sheen:hover::after, .sheen:focus-visible::after { transform: translateX(135%) skewX(-18deg); } /* Pointer-following glow on cards. `Spotlight` writes the two custom properties; the paint is entirely CSS, so it costs nothing when idle. */ .spotlight { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity 400ms var(--ease-out-soft); background: radial-gradient( 20rem circle at var(--spot-x, 50%) var(--spot-y, 50%), rgba(196, 184, 150, 0.1), transparent 68% ); } /* Only where a pointer exists: on a touch screen `:hover` sticks after a tap and the glow would stay lit on the last card the visitor touched. */ @media (hover: hover) { .group:hover > .spotlight, .group:focus-visible > .spotlight { opacity: 1; } } /* Underline drawn from the left. Used for text links that have no other hover affordance than a colour change. */ .underline-grow { position: relative; } .underline-grow::after { content: ""; position: absolute; left: 0; bottom: -2px; width: 100%; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: left; transition: transform 300ms var(--ease-out-expo); } .underline-grow:hover::after, .underline-grow:focus-visible::after { transform: scaleX(1); } /* Rejected-submission feedback. Short and small — a form that jumps reads as broken rather than as invalid. */ @keyframes shake { 20% { transform: translateX(-5px); } 40% { transform: translateX(5px); } 60% { transform: translateX(-3px); } 80% { transform: translateX(3px); } } .animate-shake { animation: shake 420ms var(--ease-out-soft); } /* Framer Motion honours the preference on its own (`useReducedMotion`); this covers the CSS-only animations and transitions. */ @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } }