since3-website/app/globals.css
2026-09-25 00:45:33 +02:00

475 lines
13 KiB
CSS

@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
/* ── Design tokens ─────────────────────────────────────────────────────── */
@theme {
/* Ink scale: luminance only, no hue */
--color-ink-0: oklch(1 0 0);
--color-ink-50: oklch(0.975 0 0);
--color-ink-100: oklch(0.945 0 0);
--color-ink-200: oklch(0.885 0 0);
--color-ink-300: oklch(0.785 0 0);
--color-ink-400: oklch(0.665 0 0);
--color-ink-500: oklch(0.555 0 0);
--color-ink-600: oklch(0.45 0 0);
--color-ink-700: oklch(0.35 0 0);
--color-ink-800: oklch(0.245 0 0);
--color-ink-900: oklch(0.155 0 0);
--color-ink-950: oklch(0.085 0 0);
--color-ink-1000: oklch(0 0 0);
--color-signal-error: oklch(0.63 0.21 25);
--color-signal-success: oklch(0.72 0.17 150);
/* Type scale */
--text-mega: clamp(3.25rem, 12.5vw, 14rem);
--text-mega--line-height: 0.84;
--text-display: clamp(2.75rem, 8.5vw, 8.5rem);
--text-display--line-height: 0.86;
--text-statement: clamp(2rem, 5vw, 4.75rem);
--text-statement--line-height: 1;
--text-title: clamp(1.5rem, 2.6vw, 2.5rem);
--text-title--line-height: 1.1;
--text-lead: clamp(1.125rem, 1.5vw, 1.375rem);
--text-lead--line-height: 1.5;
--text-body: 1.0625rem;
--text-body--line-height: 1.6;
--text-label: 0.8125rem;
--text-label--line-height: 1.4;
/* Space and layout */
--spacing-section: clamp(7rem, 16vh, 14rem);
--spacing-gutter: clamp(1.25rem, 4vw, 4rem);
--container-site: 90rem;
--radius-glass: 1.5rem;
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out-quart: cubic-bezier(0.76, 0, 0.24, 1);
}
@theme inline {
--font-sans: var(--font-mona), ui-sans-serif, system-ui, sans-serif;
--font-mono: var(--font-jetbrains), ui-monospace, monospace;
--font-heading: var(--font-sans);
--color-bg: var(--bg);
--color-fg: var(--fg);
--color-fg-muted: var(--fg-muted);
--color-fg-subtle: var(--fg-subtle);
--color-line: var(--line);
--color-surface: var(--surface);
/* shadcn/ui bridge */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
}
/* ── Themes ────────────────────────────────────────────────────────────── */
:root,
[data-theme="light"] {
--bg: var(--color-ink-0);
--fg: var(--color-ink-1000);
--fg-muted: var(--color-ink-600);
--fg-subtle: var(--color-ink-400);
--line: var(--color-ink-200);
--surface: var(--color-ink-100);
}
[data-theme="dark"] {
--bg: var(--color-ink-1000);
--fg: var(--color-ink-0);
--fg-muted: var(--color-ink-400);
--fg-subtle: var(--color-ink-600);
--line: var(--color-ink-800);
--surface: var(--color-ink-900);
}
:root {
--radius: 0.75rem;
/* Static light position for the glass edge highlight */
--light-x: 30%;
--light-y: 0%;
}
/*
* Custom properties resolve where they are declared, so the shadcn mapping
* is re-declared on every themed element.
*/
:root,
[data-theme] {
--background: var(--bg);
--foreground: var(--fg);
--card: var(--bg);
--card-foreground: var(--fg);
--popover: var(--bg);
--popover-foreground: var(--fg);
--primary: var(--fg);
--primary-foreground: var(--bg);
--secondary: var(--surface);
--secondary-foreground: var(--fg);
--muted: var(--surface);
--muted-foreground: var(--fg-muted);
--accent: var(--surface);
--accent-foreground: var(--fg);
--destructive: var(--color-signal-error);
--border: var(--line);
--input: var(--color-ink-500);
--ring: var(--fg);
}
@layer base {
* {
@apply border-border;
}
html {
@apply font-sans;
scroll-padding-top: 6rem;
}
body {
@apply bg-background text-foreground;
font-size: var(--text-body);
line-height: 1.6;
}
[data-theme] {
background-color: var(--bg);
color: var(--fg);
}
::selection {
background: var(--fg);
color: var(--bg);
}
}
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
outline: 2px solid var(--fg);
outline-offset: 3px;
box-shadow: 0 0 0 5px var(--bg);
}
/* ── Smooth scroll (Lenis) ────────────────────────────────────────────── */
html.lenis,
html.lenis body {
height: auto;
}
.lenis:not(.lenis-autoToggle).lenis-stopped {
overflow: clip;
}
.lenis [data-lenis-prevent] {
overscroll-behavior: contain;
}
.lenis.lenis-smooth iframe {
pointer-events: none;
}
/* ── Layout & type utilities ──────────────────────────────────────────── */
@utility container-site {
width: 100%;
max-width: var(--container-site);
margin-inline: auto;
padding-inline: var(--spacing-gutter);
}
/* Poster type: heavy, condensed, uppercase */
@utility type-poster {
font-variation-settings: "wght" 800, "wdth" 75;
text-transform: uppercase;
letter-spacing: -0.01em;
text-wrap: balance;
}
@utility type-statement {
font-size: var(--text-statement);
font-variation-settings: "wght" 560, "wdth" 90;
line-height: 1;
letter-spacing: -0.025em;
text-wrap: balance;
}
@utility type-title {
font-size: var(--text-title);
font-variation-settings: "wght" 600, "wdth" 90;
line-height: 1.1;
letter-spacing: -0.02em;
text-wrap: balance;
}
@utility type-body {
line-height: 1.6;
max-width: 62ch;
text-wrap: pretty;
}
@utility type-label {
font-family: var(--font-mono);
font-size: var(--text-label);
color: var(--fg-muted);
}
/*
* Outlined type. Variable fonts have overlapping contours that a plain stroke
* would draw inside the letters, so the stroke is painted first at double
* width and the fill (in the background color) covers its inner half.
*/
@utility text-outline {
color: var(--bg);
-webkit-text-stroke: max(2px, 0.024em) var(--stroke-color, var(--fg));
paint-order: stroke fill;
}
/* ── Grain over everything ────────────────────────────────────────────── */
body::after {
content: "";
position: fixed;
inset: -50%;
z-index: 90;
pointer-events: none;
opacity: 0.06;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 1.2 -0.1'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* ── Glass ────────────────────────────────────────────────────────────── */
.glass {
position: relative;
isolation: isolate;
background: linear-gradient(
135deg,
rgb(255 255 255 / 0.08),
rgb(255 255 255 / 0.02)
);
backdrop-filter: blur(24px) saturate(1.2);
border: 1px solid rgb(255 255 255 / 0.12);
border-radius: var(--radius-glass);
box-shadow:
0 1px 0 0 rgb(255 255 255 / 0.14) inset,
0 30px 80px -30px rgb(0 0 0 / 0.6);
}
.glass::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
pointer-events: none;
background: radial-gradient(
120% 90% at var(--light-x) var(--light-y),
rgb(255 255 255 / 0.1),
transparent 60%
);
}
@media (prefers-reduced-transparency: reduce) {
.glass {
backdrop-filter: none;
background: var(--color-ink-900);
}
}
/* ── Intro gating ─────────────────────────────────────────────────────── */
/*
* An inline script adds `is-intro` before the first paint when the preloader
* should play. Everything that animates in after the preloader is paused while
* that class is present. Without JavaScript the class never appears and the
* page simply animates in.
*/
.preloader {
display: none;
clip-path: inset(0 0 0 0);
transition: clip-path 1000ms var(--ease-in-out-quart);
}
html.is-intro .preloader,
.preloader[data-state="leaving"] {
display: flex;
}
.preloader[data-state="leaving"] {
clip-path: inset(0 0 100% 0);
}
html.is-intro {
overflow: hidden;
}
html.is-intro :is(.hero-char, .hero-fade) {
animation-play-state: paused;
}
@keyframes char-in {
from {
translate: 0 105%;
rotate: 6deg;
}
to {
translate: 0 0;
rotate: 0deg;
}
}
@keyframes fade-up {
from {
opacity: 0;
translate: 0 1.5rem;
}
}
.hero-char {
display: inline-block;
transform-origin: 0 100%;
animation: char-in 1100ms var(--ease-out-expo) calc(150ms + var(--i) * 28ms) both;
}
.hero-fade {
animation: fade-up 1200ms var(--ease-out-expo) calc(700ms + var(--d, 0ms)) both;
}
/* ── Header: inverts itself over light and dark sections ──────────────── */
.site-header {
mix-blend-mode: difference;
color: white;
transition: translate 500ms var(--ease-in-out-quart);
}
.site-header[data-hidden="true"] {
translate: 0 -120%;
}
/* ── Sound toggle equalizer ───────────────────────────────────────────── */
@keyframes equalizer {
0%,
100% {
scale: 1 0.25;
}
50% {
scale: 1 1;
}
}
@utility animate-equalizer {
animation: equalizer 0.9s ease-in-out infinite;
}
/* ── Custom cursor ────────────────────────────────────────────────────── */
@media (hover: hover) and (pointer: fine) {
html.has-cursor,
html.has-cursor * {
cursor: none !important;
}
html.has-cursor :is(input, textarea, select) {
cursor: text !important;
}
}
/* ── Services: rows wipe to inverted on hover ─────────────────────────── */
.service-row {
position: relative;
isolation: isolate;
}
.service-row::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background: var(--fg);
scale: 1 0;
transform-origin: 50% 100%;
transition: scale 600ms var(--ease-in-out-quart);
}
.service-row :is(.service-name, .service-meta) {
transition:
color 400ms var(--ease-in-out-quart),
translate 600ms var(--ease-in-out-quart);
}
.service-row .service-details {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 600ms var(--ease-in-out-quart);
}
@media (hover: hover) and (pointer: fine) {
.service-row:hover::before,
.service-row:focus-within::before {
scale: 1 1;
transform-origin: 50% 0%;
}
.service-row:is(:hover, :focus-within) :is(.service-name, .service-meta) {
color: var(--bg);
}
.service-row:is(:hover, :focus-within) .service-name {
translate: 1.5rem 0;
}
.service-row:is(:hover, :focus-within) .service-details {
grid-template-rows: 1fr;
}
}
/* Touch devices: details are always visible */
@media not ((hover: hover) and (pointer: fine)) {
.service-row .service-details {
grid-template-rows: 1fr;
}
}
/* ── Scroll-driven reveal (progressive enhancement) ──────────────────── */
@supports (animation-timeline: view()) {
.reveal {
animation: fade-up linear both;
animation-timeline: view();
animation-range: entry 5% cover 30%;
}
.clip-reveal {
animation: clip-reveal linear both;
animation-timeline: view();
animation-range: entry 0% cover 40%;
}
@keyframes clip-reveal {
from {
clip-path: inset(18% 8% 18% 8% round 3rem);
}
to {
clip-path: inset(0 0 0 0 round var(--radius-glass));
}
}
}
/* ── Reduced motion ───────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-delay: 0ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
.reveal,
.clip-reveal {
animation: none !important;
}
}