@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); /* * Single stacking scale for every fixed/overlay layer on the page. * Highest wins, so the custom cursor stays above all real UI, and the * cookie banner (a third-party widget) is pinned below it explicitly — * see the vanilla-cookieconsent override further down. */ --z-header: 80; --z-mobile-menu: 85; --z-cookie-banner: 90; --z-cursor: 95; --z-preloader: 100; } @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; } /* ── Cookie Consent Banner ─────────────────────────────── */ :root.cc--elegant-black { color-scheme: dark; --cc-bg: #000; --cc-primary-color: rgb(239, 244, 246); --cc-secondary-color: #b1bdc3; --cc-btn-primary-bg: #ffffff; --cc-btn-primary-color: #000; --cc-btn-primary-hover-bg: #ccd4d8; --cc-btn-primary-hover-color: #000; --cc-btn-secondary-bg: rgba(255, 255, 255, 0.039); --cc-btn-secondary-color: var(--cc-primary-color); --cc-btn-secondary-border-color: #252729; --cc-btn-secondary-hover-bg: #252729; --cc-btn-secondary-hover-color: #fff; --cc-btn-secondary-hover-border-color: #252729; --cc-cookie-category-block-bg: #101111; --cc-cookie-category-block-border: #1d1e1f; --cc-cookie-category-block-hover-bg: #151516; --cc-cookie-category-block-hover-border: #1d1e1f; --cc-cookie-category-expanded-block-hover-bg: #1d1e1f; --cc-cookie-category-expanded-block-bg: #101111; --cc-toggle-readonly-bg: #2f3132; --cc-overlay-bg: rgba(0, 0, 0, 0.9)!important; --cc-toggle-on-knob-bg: var(--cc-bg); --cc-toggle-readonly-knob-bg: var( --cc-cookie-category-block-bg); --cc-separator-border-color: #252729; --cc-footer-border-color: #212529; --cc-footer-bg: #000; } .cc--elegant-black #cc-main .cm, .cc--elegant-black #cc-main .pm { border: 1px solid var(--cc-separator-border-color); } /* ── Cookie consent (vanilla-cookieconsent) ───────────────────────────── */ /* * The library defaults --cc-z-index to 2147483647 (max signed 32-bit int), * which sits above literally everything, including the custom cursor. * Pin it into our own stacking scale instead. * * Its stylesheet is imported by as a separate CSS module, so * load order relative to this file isn't guaranteed. Setting the variable on * #cc-main itself (the library's own root selector) beats a plain :root * declaration on specificity regardless of which file loads last. */ #cc-main { --cc-z-index: var(--z-cookie-banner); } /* ── Legal pages (Impressum, Datenschutz) ─────────────────────────────── */ /* Plain, readable long-form text. No effects, just hierarchy and rhythm. */ @layer components { .legal-prose { hyphens: auto; overflow-wrap: break-word; } .legal-prose h1 { font-size: clamp(2rem, 8vw, 4.5rem); font-variation-settings: "wght" 800, "wdth" 75; line-height: 0.9; letter-spacing: -0.01em; text-transform: uppercase; } .legal-prose h2 { margin-top: 3.5rem; padding-top: 2rem; border-top: 1px solid var(--line); font-size: var(--text-title); font-variation-settings: "wght" 600, "wdth" 90; line-height: 1.15; letter-spacing: -0.02em; text-wrap: balance; } .legal-prose h1 + h2, .legal-prose h1 + * { margin-top: 2.5rem; } .legal-prose h3 { margin-top: 2.5rem; font-size: 1.1875rem; font-weight: 600; line-height: 1.3; text-wrap: balance; } .legal-prose h4 { margin-top: 1.75rem; font-weight: 600; line-height: 1.4; } .legal-prose :is(h2, h3) + :is(h3, h4) { margin-top: 1.25rem; } .legal-prose :is(p, ul, address) { margin-top: 0.875rem; line-height: 1.7; color: color-mix(in oklch, var(--fg) 78%, var(--bg)); } .legal-prose address { font-style: normal; } .legal-prose ul { padding-left: 1.25rem; list-style: disc; } .legal-prose li + li { margin-top: 0.5rem; } .legal-prose a { color: var(--fg); text-decoration: underline; text-underline-offset: 3px; } .legal-prose a:hover { text-decoration-thickness: 2px; } /* The legally required all-caps paragraph (Art. 21 DSGVO), slightly calmer */ .legal-prose .legal-caps { font-size: 0.9375rem; letter-spacing: 0.01em; } .legal-prose .legal-source { margin-top: 4rem; font-family: var(--font-mono); font-size: var(--text-label); color: var(--fg-muted); } } /* ── 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; } }