Files
narlblog/frontend/src/styles/global.css
T
2026-05-15 16:12:18 +02:00

1580 lines
45 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@import "tailwindcss";
/*
* SALON HANG — gallery theme.
* Aged parchment ground, oxblood ink, ochre+cobalt+vermillion accents.
* Romantic gravity (Friedrich, Dix, Goya) + raw scrawl (Basquiat) + bold cutout (Matisse, Kahlo).
*/
@theme {
--color-crust: var(--crust);
--color-mantle: var(--mantle);
--color-bg: var(--base);
--color-surface0: var(--surface0);
--color-surface1: var(--surface1);
--color-surface2: var(--surface2);
--color-overlay0: var(--overlay0);
--color-overlay1: var(--overlay1);
--color-overlay2: var(--overlay2);
--color-text: var(--text);
--color-subtext0: var(--subtext0);
--color-subtext1: var(--subtext1);
--color-blue: var(--blue);
--color-lavender: var(--lavender);
--color-sapphire: var(--sapphire);
--color-sky: var(--sky);
--color-teal: var(--teal);
--color-green: var(--green);
--color-yellow: var(--yellow);
--color-peach: var(--peach);
--color-maroon: var(--maroon);
--color-red: var(--red);
--color-mauve: var(--mauve);
--color-pink: var(--pink);
--color-flamingo: var(--flamingo);
--color-rosewater: var(--rosewater);
--font-sans: 'EB Garamond Variable', 'EB Garamond', Georgia, 'Times New Roman', serif;
--font-display: 'Fraunces Variable', 'Fraunces', Georgia, 'Times New Roman', serif;
--font-hand: 'Caveat', 'Bradley Hand', cursive;
--font-mono: 'JetBrains Mono Variable', ui-monospace, 'SF Mono', Menlo, monospace;
}
/* SALON — default. Aged parchment with romantic weight. */
:root, .salon {
--crust: #14100C;
--mantle: #2A1F18;
--base: #ECE0C6;
--surface0: #DDCEB0;
--surface1: #B69C70;
--surface2: #826846;
--overlay0: #5C463A;
--overlay1: #463226;
--overlay2: #2E1F17;
--text: #1A120C;
--subtext0: #5C463A;
--subtext1: #3D2B1E;
/* accents — mapped to the original token names so existing UI flows pick them up */
--blue: #1F3A78; /* Kahlo cobalt */
--lavender: #5C4D7A; /* faded violet */
--sapphire: #2B3E5C; /* deep ink-blue */
--sky: #4A6FA0; /* muted azure */
--teal: #4C7264; /* verdigris */
--green: #6A7341; /* olive */
--yellow: #C9882B; /* Friedrich ochre */
--peach: #C26847; /* terracotta */
--maroon: #6B2B2A; /* wine */
--red: #B83A2B; /* Matisse/Goya vermillion */
--mauve: #6B1F1A; /* oxblood — primary accent */
--pink: #B85A6C; /* rosehip */
--flamingo: #C77A6C; /* faded coral */
--rosewater: #E8D9BD; /* bone */
}
/* Salon Noir — black gallery wall variant (Goya black paintings, Abramović stark). */
.salon-noir {
--crust: #050402;
--mantle: #0E0A06;
--base: #16110B;
--surface0: #221A12;
--surface1: #3A2B1E;
--surface2: #5C4530;
--overlay0: #7A5D43;
--overlay1: #93755A;
--overlay2: #B69779;
--text: #ECE0C6;
--subtext0: #B69C70;
--subtext1: #D6C49E;
--blue: #5A7DC4;
--lavender: #9A8DBE;
--sapphire: #87A9D8;
--sky: #B0C4E0;
--teal: #84A89A;
--green: #B9C076;
--yellow: #E9B854;
--peach: #E89570;
--maroon: #A04A47;
--red: #E25940;
--mauve: #C24336; /* lifted oxblood for dark bg contrast */
--pink: #E090A0;
--flamingo: #EBA797;
--rosewater: #F4E5C9;
}
/* BREAKCORE — early-2000s web rot + breakcore. CRT-violet ground, hot
* magenta primary, acid green / electric cyan / hazard yellow accents.
* Glitchy, blown-out, MSN-era saturation. */
.breakcore {
--crust: #02000A;
--mantle: #06031A;
--base: #0A0612;
--surface0: #150929;
--surface1: #22113F;
--surface2: #3A1B62;
--overlay0: #5A2D8E;
--overlay1: #7B45B8;
--overlay2: #A06AD8;
--text: #F2F0FF;
--subtext0: #B9A8E0;
--subtext1: #D8CCFA;
--blue: #00B7FF; /* MSN cyan */
--lavender: #B98CFF; /* CRT violet */
--sapphire: #4B6BFF; /* hyperlink */
--sky: #66E1FF; /* aqua chrome */
--teal: #00F5C8; /* matrix mint */
--green: #B6FF00; /* acid */
--yellow: #FFD400; /* hazard */
--peach: #FF8A3D; /* GIF-era flame */
--maroon: #8B0A4B;
--red: #FF1F4F; /* siren */
--mauve: #FF2EA6; /* hot magenta — primary accent */
--pink: #FF7AD8; /* bubblegum */
--flamingo: #FFA2C4;
--rosewater: #FFE8F6;
}
/* GOTHIC — cathedral nightfall. Midnight violet ground, blood crimson,
* tarnished candle gold, stained-glass indigo. Catholic-gothic + Sisters of
* Mercy + Bauhaus stark. Primary accent: cathedral velvet mauve. */
.gothic {
--crust: #030104;
--mantle: #0A0710;
--base: #110B18;
--surface0: #1A1224;
--surface1: #261A36;
--surface2: #382550;
--overlay0: #4F3970;
--overlay1: #6E5293;
--overlay2: #8D72B1;
--text: #EDE3F2; /* bone, violet wash */
--subtext0: #9B8AB0;
--subtext1: #C0AED2;
--blue: #4239A4; /* stained-glass deep */
--lavender: #9B7BD4; /* candlelight through purple glass */
--sapphire: #5947B2;
--sky: #7C68C9;
--teal: #487B8A; /* verdigris on bronze */
--green: #5E7842; /* cemetery moss */
--yellow: #D4A82B; /* taper / tarnished brass */
--peach: #B45A38; /* rust */
--maroon: #5B1A24;
--red: #A41827; /* arterial */
--mauve: #8B2C9E; /* cathedral velvet — primary accent */
--pink: #B25288; /* dried rose */
--flamingo: #C57B96;
--rosewater: #F0DDE8;
}
html {
font-family: var(--font-sans);
font-feature-settings: "kern", "liga", "calt", "onum";
}
body {
background-color: var(--base);
color: var(--text);
min-height: 100vh;
transition: background-color 0.3s ease, color 0.3s ease;
-webkit-font-smoothing: antialiased;
font-size: 1.0625rem;
line-height: 1.65;
position: relative;
}
/* Paper grain — applied as a fixed overlay so every page gets the texture.
* All three layers sit behind content (negative z-index) so fixed-positioned
* modals (e.g. the search palette) can escape ancestor stacking traps. */
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: -3;
background-color: var(--base);
}
body::after {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: -1;
opacity: 0.32;
mix-blend-mode: multiply;
background-image:
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.10 0 0 0 0 0.07 0 0 0 0 0.04 0 0 0 0.22 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
}
.salon-noir body::after,
html.salon-noir body::after {
opacity: 0.5;
}
.gothic body::after,
html.gothic body::after {
opacity: 0.55;
background-image:
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.08 0 0 0 0 0.05 0 0 0 0 0.10 0 0 0 0.28 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
mix-blend-mode: screen;
}
/* Breakcore: blown-out RGB-tinted digital noise + CRT scanlines. */
.breakcore body::after,
html.breakcore body::after {
opacity: 0.55;
background-image:
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='1.2' numOctaves='3' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 0.18 0 0 0 0 0.65 0 0 0 0.45 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>"),
repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0) 0,
rgba(0, 0, 0, 0) 2px,
rgba(0, 0, 0, 0.28) 3px,
rgba(0, 0, 0, 0) 4px
);
mix-blend-mode: screen;
}
.breakcore .salon-atmosphere::before { opacity: 0.32; }
.breakcore .salon-atmosphere::after { opacity: 0.28; }
/* Floating motes of pigment — far background, very subtle. */
.salon-atmosphere {
position: fixed;
inset: 0;
z-index: -2;
pointer-events: none;
overflow: hidden;
}
.salon-atmosphere::before,
.salon-atmosphere::after {
content: "";
position: absolute;
border-radius: 50%;
filter: blur(120px);
opacity: 0.18;
}
.salon-atmosphere::before {
width: 55vw; height: 55vw;
top: -15vw; left: -10vw;
background: var(--mauve);
}
.salon-atmosphere::after {
width: 45vw; height: 45vw;
bottom: -10vw; right: -10vw;
background: var(--blue);
opacity: 0.12;
}
code, pre, kbd, samp {
font-family: var(--font-mono);
}
/* Selection */
::selection {
background: var(--mauve);
color: var(--rosewater);
}
/* Breakcore: chromatic-aberration glow on display headings + nameplate. */
.breakcore .prose h1,
.breakcore .prose h2,
.breakcore h1.font-display,
.breakcore .nameplate-title {
text-shadow:
-1px 0 0 color-mix(in srgb, var(--teal) 70%, transparent),
1px 0 0 color-mix(in srgb, var(--mauve) 70%, transparent),
0 0 18px color-mix(in srgb, var(--mauve) 35%, transparent);
}
.breakcore ::selection {
background: var(--green);
color: var(--crust);
text-shadow: 0 0 6px var(--mauve);
}
/* ───── Display utilities ───── */
.font-display {
font-family: var(--font-display);
font-feature-settings: "kern", "liga", "calt", "lnum", "ss01";
letter-spacing: -0.01em;
}
.font-hand {
font-family: var(--font-hand);
font-weight: 400;
}
.font-display-italic {
font-family: var(--font-display);
font-style: italic;
font-feature-settings: "kern", "liga", "calt", "ss01";
}
/* Roman numerals get small-caps treatment */
.numeral {
font-family: var(--font-display);
font-variant-numeric: lining-nums;
letter-spacing: 0.08em;
font-weight: 500;
}
/* ───── Salon prose — exhibit plaque body ───── */
.prose {
color: var(--text);
max-width: none;
line-height: 1.75;
font-size: 1.125rem;
font-family: var(--font-sans);
}
@media (min-width: 768px) {
.prose { font-size: 1.1875rem; }
}
.prose > *:first-child { margin-top: 0; }
.prose h1 {
font-family: var(--font-display);
font-size: clamp(2rem, 1.5rem + 2vw, 3rem);
font-weight: 600;
font-style: italic;
color: var(--text);
margin: 0 0 1.25rem;
line-height: 1.15;
letter-spacing: -0.02em;
padding-bottom: 0.06em;
}
.prose h2 {
font-family: var(--font-display);
font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem);
font-weight: 500;
color: var(--text);
margin: 3rem 0 1rem;
padding-bottom: 0.35rem;
line-height: 1.2;
letter-spacing: -0.01em;
border-bottom: 1px solid color-mix(in srgb, var(--mauve) 30%, transparent);
}
.prose h3 {
font-family: var(--font-display);
font-size: 1.4rem;
font-weight: 500;
font-style: italic;
color: var(--text);
margin: 2.25rem 0 0.75rem;
}
.prose h4 {
font-family: var(--font-display);
font-size: 1.2rem;
font-weight: 500;
color: var(--subtext1);
margin: 1.75rem 0 0.5rem;
}
.prose h5 {
font-size: 0.85rem;
font-weight: 600;
color: var(--subtext0);
text-transform: uppercase;
letter-spacing: 0.18em;
margin: 1.5rem 0 0.5rem;
}
.prose h6 {
font-size: 0.85rem;
font-weight: 500;
color: var(--overlay0);
font-style: italic;
margin: 1rem 0 0.5rem;
}
.prose :is(h1, h2, h3, h4, h5, h6) { scroll-margin-top: 5rem; }
.prose p { margin: 0 0 1.15rem; }
.prose blockquote {
border-left: 3px solid var(--mauve);
padding: 0.25rem 0 0.25rem 1.4rem;
margin: 1.75rem 0;
color: var(--subtext1);
font-style: italic;
}
.prose blockquote p { margin: 0 0 0.6rem; }
.prose blockquote p:last-child { margin: 0; }
.prose pre {
padding: 1rem 1.1rem;
border-radius: 0;
border: 1px solid var(--surface2);
border-left-width: 3px;
border-left-color: var(--mauve);
margin: 1.75rem 0;
background-color: color-mix(in srgb, var(--surface0) 70%, transparent);
font-size: 0.875rem;
line-height: 1.55;
overflow-x: auto;
}
.prose code {
background-color: color-mix(in srgb, var(--surface0) 90%, transparent);
padding: 0.1rem 0.4rem;
border-radius: 0;
border-bottom: 1px solid var(--surface1);
font-size: 0.9em;
color: var(--mauve);
}
.prose pre code {
background: transparent;
padding: 0;
border: 0;
color: inherit;
font-size: inherit;
}
.prose a code,
.prose :is(h1, h2, h3, h4) code { color: inherit; }
.prose a {
color: var(--mauve);
text-decoration: underline;
text-decoration-color: var(--surface1);
text-decoration-thickness: 1px;
text-underline-offset: 3px;
transition: color 0.15s, text-decoration-color 0.15s;
}
.prose a:hover {
color: var(--red);
text-decoration-color: var(--red);
}
.prose ul, .prose ol {
margin: 0 0 1.15rem;
padding-left: 1.6rem;
}
.prose ul { list-style: none; }
.prose ul > li { position: relative; padding-left: 0.2rem; }
.prose ul > li::before {
content: "";
position: absolute;
left: -1.1rem;
top: 0.62em;
width: 0.42em;
height: 0.42em;
background: var(--mauve);
transform: rotate(45deg);
}
.prose ol { list-style: decimal-leading-zero; }
.prose ol > li::marker { color: var(--mauve); font-family: var(--font-display); font-style: italic; }
.prose li { margin: 0.3rem 0; }
/* Loose lists wrap items in <p>; drop the paragraph block-margin inside li. */
.prose li > p { margin: 0; }
.prose li > p + p { margin-top: 0.6rem; }
/* GFM task lists — kill the diamond, keep the checkbox. */
.prose ul > li:has(input[type="checkbox"]) { padding-left: 0; }
.prose ul > li:has(input[type="checkbox"])::before { content: none; }
.prose li > input[type="checkbox"] {
margin: 0 0.5rem 0 0;
vertical-align: 0.04em;
accent-color: var(--mauve);
}
.prose hr {
margin: 3rem auto;
border: 0;
height: 1px;
width: 100%;
background: linear-gradient(
90deg,
transparent 0%,
color-mix(in srgb, var(--mauve) 55%, transparent) 22%,
transparent 45%,
transparent 55%,
color-mix(in srgb, var(--mauve) 55%, transparent) 78%,
transparent 100%
);
position: relative;
overflow: visible;
}
.prose hr::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 0.5rem;
height: 0.5rem;
transform: translate(-50%, -50%) rotate(45deg);
background: var(--mauve);
}
.prose strong { color: inherit; font-weight: 700; }
.prose em { color: inherit; font-style: italic; font-family: var(--font-display); }
.prose del { color: var(--overlay0); text-decoration: line-through; }
/* ───── Figure / image plate — the heart of the gallery body ───── */
.prose figure {
display: block;
text-align: center;
margin: 2.5rem 0;
}
.prose figure img,
.prose img {
display: block;
max-width: 100%;
height: auto;
margin: 0 auto;
border: 1px solid var(--surface2);
padding: 6px;
background:
linear-gradient(var(--rosewater), var(--rosewater)) padding-box,
linear-gradient(135deg, var(--surface2), var(--surface1)) border-box;
box-shadow:
0 1px 0 var(--surface0),
0 18px 38px -22px rgba(20, 16, 12, 0.45),
0 2px 6px -2px rgba(20, 16, 12, 0.2);
border-radius: 2px;
}
.salon-noir .prose figure img,
.salon-noir .prose img,
.gothic .prose figure img,
.gothic .prose img,
.breakcore .prose figure img,
.breakcore .prose img {
background:
linear-gradient(var(--surface0), var(--surface0)) padding-box,
linear-gradient(135deg, var(--surface2), var(--surface1)) border-box;
box-shadow:
0 18px 38px -22px rgba(0, 0, 0, 0.7),
0 2px 6px -2px rgba(0, 0, 0, 0.5);
}
.gothic .prose figure img,
.gothic .prose img {
box-shadow:
0 18px 38px -22px rgba(0, 0, 0, 0.85),
0 2px 6px -2px rgba(0, 0, 0, 0.6),
0 0 0 1px color-mix(in srgb, var(--mauve) 22%, transparent);
}
.prose figure figcaption {
font-family: var(--font-display);
font-style: italic;
font-size: 0.9rem;
color: var(--subtext0);
margin-top: 0.85rem;
letter-spacing: 0.02em;
line-height: 1.4;
}
/* Multi-image rows. Consecutive markdown images auto-collapse into a flex
* row; each figure gets `flex: <aspect-ratio>` inline so widths divide
* proportionally and heights line up. Wraps to a column on narrow screens. */
.prose .figure-row {
/* Target row height. Each figure's flex-basis is ratio × this value, so
* rows pack as many figures as fit at roughly --row-h tall, then wrap.
* --row-max caps how tall a sparsely-filled final row can grow. */
--row-h: 16rem;
--row-max: 30rem;
display: flex;
flex-wrap: wrap;
gap: 0.9rem;
align-items: flex-start;
margin: 2.5rem 0;
width: 100%;
}
@media (min-width: 1024px) {
.prose .figure-row {
--row-h: 18rem;
--row-max: 34rem;
}
}
.prose .figure-row figure {
margin: 0;
min-width: 0; /* allow flex children to shrink below content width */
flex-basis: 0;
}
.prose .figure-row figure img {
width: 100%;
max-width: 100%;
height: auto;
margin: 0;
}
.prose .figure-row figure figcaption {
text-align: left;
margin-top: 0.55rem;
font-size: 0.82rem;
}
@media (max-width: 640px) {
.prose .figure-row {
flex-direction: column;
width: 100%;
margin-left: 0;
gap: 1.4rem;
}
.prose .figure-row figure {
flex: 1 1 100% !important;
}
.prose .figure-row figure figcaption {
text-align: center;
}
}
.prose figure figcaption::before {
content: "— ";
color: var(--mauve);
}
/* GFM tables — keep, slightly more editorial */
.prose table {
display: block;
width: 100%;
max-width: 100%;
overflow-x: auto;
margin: 1.75rem 0;
border-collapse: collapse;
border: 1px solid var(--surface2);
font-size: 0.95rem;
font-family: var(--font-sans);
}
.prose thead { background-color: color-mix(in srgb, var(--surface0) 80%, transparent); }
.prose th {
padding: 0.55rem 0.9rem;
text-align: left;
font-weight: 600;
color: var(--text);
border-bottom: 1px solid var(--surface2);
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.75rem;
}
.prose td {
padding: 0.5rem 0.9rem;
border-bottom: 1px solid color-mix(in srgb, var(--surface1) 60%, transparent);
}
.prose tr:last-child td { border-bottom: 0; }
/* ───── Salon plate — a single framed image card used on the gallery index ───── */
.plate {
position: relative;
background: var(--rosewater);
padding: 14px 14px 0 14px;
border: 1px solid var(--surface2);
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--surface1) 50%, transparent),
0 1px 0 var(--surface0),
0 22px 42px -28px rgba(20, 16, 12, 0.5),
0 4px 12px -6px rgba(20, 16, 12, 0.25);
border-radius: 2px;
transition: transform 0.4s cubic-bezier(0.2, 0.6, 0.2, 1),
box-shadow 0.4s cubic-bezier(0.2, 0.6, 0.2, 1);
}
.salon-noir .plate,
.gothic .plate,
.breakcore .plate {
background: var(--surface0);
}
.salon-noir .plate,
.gothic .plate {
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--surface1) 50%, transparent),
0 22px 42px -28px rgba(0, 0, 0, 0.7),
0 4px 12px -6px rgba(0, 0, 0, 0.45);
}
.breakcore .plate {
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--mauve) 35%, transparent),
0 0 0 1px color-mix(in srgb, var(--mauve) 20%, transparent),
0 22px 42px -28px rgba(255, 46, 166, 0.35),
0 0 24px -8px color-mix(in srgb, var(--mauve) 40%, transparent);
}
.plate:hover {
transform: translateY(-4px) rotate(-0.25deg);
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--surface1) 60%, transparent),
0 1px 0 var(--surface0),
0 32px 60px -28px rgba(20, 16, 12, 0.55),
0 8px 20px -8px rgba(20, 16, 12, 0.3);
}
.salon-noir .plate:hover,
.gothic .plate:hover {
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--surface1) 60%, transparent),
0 32px 60px -28px rgba(0, 0, 0, 0.8),
0 8px 20px -8px rgba(0, 0, 0, 0.55);
}
.breakcore .plate:hover {
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--mauve) 45%, transparent),
0 0 0 1px color-mix(in srgb, var(--mauve) 30%, transparent),
0 32px 60px -28px rgba(255, 46, 166, 0.45),
0 0 32px -8px color-mix(in srgb, var(--mauve) 50%, transparent);
}
/* Keyboard focus for the card link — salon-appropriate inset frame + ring. */
.plate:focus-visible {
outline: none;
box-shadow:
inset 0 0 0 2px var(--mauve),
0 0 0 3px color-mix(in srgb, var(--mauve) 40%, transparent),
0 22px 42px -28px rgba(20, 16, 12, 0.5);
}
.breakcore .plate:focus-visible {
box-shadow:
inset 0 0 0 2px var(--mauve),
0 0 0 2px var(--green),
0 0 28px -6px color-mix(in srgb, var(--mauve) 60%, transparent);
}
.plate .plate-image {
position: relative;
overflow: hidden;
background: var(--mantle);
}
.plate .plate-image img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
filter: saturate(0.94) contrast(1.02);
transition: transform 0.8s cubic-bezier(0.2, 0.6, 0.2, 1), filter 0.4s ease;
}
/* Natural mode — container drops fixed aspect so image shows its true ratio. */
.plate .plate-image.is-natural {
height: auto;
}
.plate .plate-image.is-natural img {
height: auto;
object-fit: contain;
}
.plate:hover .plate-image img {
transform: scale(1.03);
filter: saturate(1.05) contrast(1.04);
}
.plate .plate-caption {
padding: 14px 6px 16px 6px;
margin-top: 2px;
border-top: 1px solid color-mix(in srgb, var(--surface2) 50%, transparent);
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.plate .plate-caption-title {
font-family: var(--font-display);
font-style: italic;
font-weight: 500;
font-size: 1.18rem;
line-height: 1.3;
color: var(--text);
letter-spacing: -0.005em;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
/* line-clamp's overflow:hidden clips italic-Fraunces descenders (g, y, p).
* Pad the clip box and pull the layout back with a matching negative
* margin so descenders survive without shifting siblings. */
padding-bottom: 0.16em;
margin-bottom: -0.16em;
transition: color 0.25s ease;
}
.plate:hover .plate-caption-title {
color: var(--mauve);
}
.plate .plate-caption-summary {
font-family: var(--font-sans);
font-style: italic;
font-size: 0.82rem;
line-height: 1.45;
color: var(--subtext0);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
padding-bottom: 0.14em;
margin-bottom: -0.14em;
}
.plate .plate-caption-meta {
font-family: var(--font-sans);
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.22em;
color: var(--subtext0);
white-space: nowrap;
display: flex;
align-items: center;
gap: 0.5rem;
padding-top: 0.25rem;
}
.plate .plate-caption-sep {
color: var(--mauve);
opacity: 0.55;
}
.plate-tag-mini {
position: absolute;
bottom: 18px;
right: 18px;
background: color-mix(in srgb, var(--crust) 78%, transparent);
color: var(--rosewater);
border: 1px solid color-mix(in srgb, var(--rosewater) 22%, transparent);
font-family: var(--font-display);
font-size: 0.7rem;
letter-spacing: 0.16em;
padding: 3px 8px;
text-transform: uppercase;
backdrop-filter: blur(2px);
}
/* Nameplate — the museum-style header used in the site chrome */
.nameplate {
display: inline-flex;
flex-direction: column;
align-items: flex-start;
gap: 2px;
position: relative;
}
.nameplate::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -6px;
height: 2px;
background: linear-gradient(to right,
var(--mauve) 0%,
var(--mauve) 35%,
var(--surface2) 35%,
var(--surface2) 100%);
}
.nameplate-title {
font-family: var(--font-display);
font-weight: 600;
font-style: italic;
font-size: 1.6rem;
letter-spacing: -0.01em;
color: var(--text);
line-height: 1.12;
}
.nameplate-subtitle {
font-family: var(--font-sans);
font-size: 0.65rem;
letter-spacing: 0.32em;
text-transform: uppercase;
color: var(--subtext0);
}
/* Section ornaments */
.section-rule {
display: flex;
align-items: center;
gap: 1rem;
color: var(--subtext0);
font-family: var(--font-display);
font-style: italic;
font-size: 0.95rem;
letter-spacing: 0.04em;
}
.section-rule::before,
.section-rule::after {
content: "";
flex: 1;
height: 1px;
background: var(--surface2);
}
.section-rule .ornament {
color: var(--mauve);
}
/* Scrawled handwritten margin notes */
.scrawl {
font-family: var(--font-hand);
color: var(--mauve);
font-size: 1.4rem;
line-height: 1;
transform: rotate(-6deg);
display: inline-block;
}
.scrawl-mark::before {
content: "✕";
font-family: var(--font-hand);
color: var(--red);
margin-right: 0.35em;
}
/* Stripe (Matisse cutout) chip used for tags */
.chip {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-family: var(--font-display);
font-style: italic;
font-size: 0.78rem;
padding: 0.15rem 0.6rem;
background: color-mix(in srgb, var(--surface0) 80%, transparent);
border: 1px solid var(--surface2);
color: var(--subtext1);
border-radius: 1px;
letter-spacing: 0.02em;
}
.chip-accent {
background: var(--mauve);
color: var(--rosewater);
border-color: var(--mauve);
}
.chip-draft {
background: color-mix(in srgb, var(--peach) 18%, transparent);
color: var(--peach);
border-color: color-mix(in srgb, var(--peach) 50%, transparent);
}
/* Card / glass — keep the name but reinterpret as a paper card */
.glass {
background-color: color-mix(in srgb, var(--surface0) 80%, transparent);
border: 1px solid var(--surface2);
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--surface1) 50%, transparent),
0 10px 30px -20px rgba(20, 16, 12, 0.45);
border-radius: 2px;
}
.salon-noir .glass,
.gothic .glass,
.breakcore .glass {
background-color: color-mix(in srgb, var(--surface0) 70%, transparent);
}
.salon-noir .glass {
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--surface1) 50%, transparent),
0 14px 40px -24px rgba(0, 0, 0, 0.8);
}
.gothic .glass {
border-color: color-mix(in srgb, var(--mauve) 35%, var(--surface2));
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--mauve) 18%, transparent),
0 14px 40px -24px rgba(0, 0, 0, 0.85);
}
.breakcore .glass {
border-color: color-mix(in srgb, var(--mauve) 45%, var(--surface2));
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--mauve) 25%, transparent),
0 0 0 1px color-mix(in srgb, var(--teal) 15%, transparent),
0 14px 40px -24px rgba(0, 0, 0, 0.9);
}
/* ───── Buttons — one system ─────
* Base .btn = layout + size + focus/disabled. One variant for color
* (--primary / --ghost / --danger), one size modifier (--sm / --lg),
* shape modifiers (--icon / --block). Never restyle buttons ad-hoc. */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
height: 2.5rem;
padding: 0 1.2rem;
font-family: var(--font-display);
font-style: italic;
font-weight: 500;
font-size: 0.95rem;
line-height: 1;
letter-spacing: 0.02em;
background: transparent;
color: var(--text);
border: 1px solid transparent;
border-radius: 1px;
text-decoration: none;
white-space: nowrap;
cursor: pointer;
transition: transform 0.15s ease, background 0.15s ease, color 0.15s ease,
border-color 0.15s ease, box-shadow 0.15s ease;
}
.btn:focus-visible {
outline: none;
border-color: var(--mauve);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--mauve) 35%, transparent);
}
.btn:disabled,
.btn[aria-disabled="true"] {
opacity: 0.55;
cursor: default;
pointer-events: none;
}
.btn svg { width: 1.05em; height: 1.05em; flex-shrink: 0; }
/* Variants */
.btn--primary {
background: var(--mauve);
color: var(--rosewater);
border-color: color-mix(in srgb, var(--mauve) 80%, var(--crust));
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.22);
}
.btn--primary:hover {
transform: translateY(-1px);
background: var(--red);
border-color: color-mix(in srgb, var(--red) 80%, var(--crust));
box-shadow: 0 7px 16px -7px color-mix(in srgb, var(--red) 65%, transparent);
}
.btn--primary:active {
transform: translateY(0);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
}
.btn--ghost {
color: var(--subtext1);
border-color: var(--surface2);
background: color-mix(in srgb, var(--surface0) 45%, transparent);
}
.btn--ghost:hover {
color: var(--mauve);
border-color: color-mix(in srgb, var(--mauve) 50%, var(--surface2));
background: color-mix(in srgb, var(--surface0) 80%, transparent);
}
.btn--danger {
color: var(--red);
border-color: color-mix(in srgb, var(--red) 55%, var(--surface2));
background: color-mix(in srgb, var(--red) 12%, transparent);
}
.btn--danger:hover {
color: var(--rosewater);
background: var(--red);
border-color: var(--red);
}
/* Pressed/selected state for toggle & tab buttons */
.btn.is-active {
color: var(--mauve);
border-color: color-mix(in srgb, var(--mauve) 55%, var(--surface2));
background: color-mix(in srgb, var(--mauve) 14%, transparent);
}
/* Sizes */
.btn--sm { height: 2rem; padding: 0 0.85rem; font-size: 0.85rem; gap: 0.35rem; }
.btn--lg { height: 3rem; padding: 0 1.6rem; font-size: 1.05rem; }
/* Shapes */
.btn--icon { padding: 0; width: 2.5rem; }
.btn--icon.btn--sm { width: 2rem; }
.btn--block { width: 100%; }
/* ───── Top-bar controls — one height, one language ─────
* `.topbar-cluster` lays the chrome controls out as one tidy, right-aligned
* group that wraps as a unit (never a ragged full-width column on mobile).
* Every control is the same 2rem height; icon-only variants are exact
* squares so they line up cleanly next to each other. */
.topbar-cluster {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.4rem;
justify-content: flex-start;
}
@media (min-width: 768px) {
.topbar-cluster { justify-content: flex-end; }
}
/* A hairline divider between the public controls and the admin group. */
.topbar-divider {
align-self: stretch;
width: 1px;
margin: 0.15rem 0.15rem;
background: var(--surface2);
flex: none;
}
.topbar-control {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
height: 2rem;
padding: 0 0.7rem;
flex: none;
font-family: var(--font-display);
font-style: italic;
font-size: 0.85rem;
line-height: 1;
color: var(--subtext1);
background: color-mix(in srgb, var(--surface0) 55%, transparent);
border: 1px solid var(--surface2);
border-radius: 1px;
text-decoration: none;
cursor: pointer;
white-space: nowrap;
transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.topbar-control:hover {
color: var(--mauve);
background: color-mix(in srgb, var(--surface0) 85%, transparent);
border-color: color-mix(in srgb, var(--mauve) 45%, var(--surface2));
}
.topbar-control:focus-visible {
outline: none;
border-color: var(--mauve);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--mauve) 40%, transparent);
}
.topbar-control:disabled { opacity: 0.5; cursor: default; }
.topbar-control svg { width: 15px; height: 15px; flex-shrink: 0; }
/* Exact-square icon-only variant — keeps the row aligned. */
.topbar-control--icon { width: 2rem; padding: 0; }
.topbar-control--danger:hover {
color: var(--red);
border-color: color-mix(in srgb, var(--red) 55%, var(--surface2));
}
/* Native <select> variant — leave room for the chevron overlay.
* Fixed width so switching themes never resizes the whole top bar. */
select.topbar-control {
appearance: none;
-webkit-appearance: none;
padding-right: 1.9rem;
}
select.topbar-control.theme-select {
width: 8.75rem;
justify-content: flex-start;
text-align: left;
}
.topbar-control kbd {
display: inline-flex;
align-items: center;
gap: 0.15rem;
font-family: var(--font-mono, monospace);
font-style: normal;
font-size: 0.62rem;
padding: 0.05rem 0.3rem;
border: 1px solid var(--surface2);
border-radius: 1px;
color: var(--subtext0);
}
/* Responsive collapse — below a breakpoint a control drops its label and
* becomes an exact 2rem square so the cluster stays a tidy aligned row on
* phones. Written unlayered (not Tailwind utilities) so it reliably wins
* over the `.topbar-control` base in the Tailwind v4 cascade. */
.topbar-control .tc-label { display: inline; }
@media (max-width: 767px) {
.topbar-control.tc-collapse-md { width: 2rem; padding: 0; }
.topbar-control.tc-collapse-md .tc-label { display: none; }
}
@media (max-width: 639px) {
.topbar-control.tc-collapse-sm { width: 2rem; padding: 0; }
.topbar-control.tc-collapse-sm .tc-label { display: none; }
}
/* Form input look */
.field-input {
width: 100%;
background: color-mix(in srgb, var(--surface0) 60%, transparent);
border: 1px solid var(--surface2);
border-radius: 1px;
padding: 0.65rem 0.9rem;
color: var(--text);
font-family: var(--font-sans);
font-size: 1rem;
transition: border-color 0.15s, background 0.15s;
}
.field-input:focus {
outline: none;
border-color: var(--mauve);
background: color-mix(in srgb, var(--surface0) 85%, var(--mauve) 8%);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--mauve) 22%, transparent);
}
.field-label {
display: block;
font-family: var(--font-sans);
font-size: 0.72rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--subtext0);
margin-bottom: 0.4rem;
}
/* hljs token colors — driven by theme tokens, slightly muted for parchment bg */
.hljs { color: var(--text); background: transparent; }
.hljs-keyword, .hljs-selector-tag, .hljs-built_in, .hljs-operator { color: var(--mauve); font-weight: 600; }
.hljs-string, .hljs-attr, .hljs-regexp, .hljs-addition { color: var(--green); }
.hljs-number, .hljs-literal, .hljs-symbol, .hljs-bullet { color: var(--peach); }
.hljs-comment, .hljs-quote { color: var(--overlay0); font-style: italic; }
.hljs-title, .hljs-section, .hljs-name, .hljs-title.function_ { color: var(--blue); }
.hljs-type, .hljs-class .hljs-title, .hljs-title.class_ { color: var(--yellow); }
.hljs-variable, .hljs-template-variable, .hljs-params, .hljs-property { color: var(--red); }
.hljs-attribute, .hljs-meta, .hljs-meta .hljs-keyword { color: var(--subtext0); }
.hljs-deletion { color: var(--red); }
.hljs-emphasis { font-style: italic; }
.hljs-strong { font-weight: 700; }
/* KaTeX */
.katex { color: var(--text); }
/* Skeleton loader */
.skeleton {
background: linear-gradient(
90deg,
color-mix(in srgb, var(--surface0) 50%, transparent) 0%,
color-mix(in srgb, var(--surface1) 50%, transparent) 50%,
color-mix(in srgb, var(--surface0) 50%, transparent) 100%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s ease-in-out infinite;
border-radius: 1px;
}
@keyframes skeleton-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Toast */
.toast {
position: fixed;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
background: var(--mantle);
border: 1px solid var(--surface2);
color: var(--rosewater);
padding: 0.65rem 1.1rem;
border-radius: 1px;
box-shadow: 0 12px 30px -10px rgba(0, 0, 0, 0.45);
font-family: var(--font-display);
font-style: italic;
font-size: 0.9rem;
z-index: 200;
animation: toast-in 0.2s ease;
}
@keyframes toast-in {
from { opacity: 0; transform: translate(-50%, 8px); }
to { opacity: 1; transform: translate(-50%, 0); }
}
/* Salon grid spans driven by --col-span custom prop (avoids Tailwind dynamic class issue). */
@media (min-width: 768px) {
.md-col-span {
grid-column: span var(--col-span, 6) / span var(--col-span, 6);
}
}
/* Subtle page enter animation for gallery / plaque */
@keyframes plate-fade-up {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.plate-enter {
opacity: 0;
animation: plate-fade-up 0.6s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
}
/* Custom checkbox accent for form bits inside the salon */
input[type="checkbox"] { accent-color: var(--mauve); }
input[type="date"] { color-scheme: light; }
.salon-noir input[type="date"] { color-scheme: dark; }
.gothic input[type="date"] { color-scheme: dark; }
.breakcore input[type="date"] { color-scheme: dark; }
/* Reading progress bar - thin terracotta line */
.reading-progress {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 2px;
background: var(--mauve);
z-index: 150;
transform-origin: left;
transform: scaleX(0);
transition: transform 80ms linear;
}
/* ═══════════════════════════════════════════════════════════════════════
* BREAKCORE — refined-neon layer.
* Everything below is scoped to `.breakcore`; salon / salon-noir / gothic
* are untouched. Aesthetic: editorial serif body in deliberate tension with
* hard-edged web-rot chrome — RGB split, hazard tape, neon outline, hard
* offset shadows. Motion is *reactive only* (hover / focus / one-shot on
* load) and settles fast. All motion is killed by prefers-reduced-motion
* at the very end of this file.
* ═══════════════════════════════════════════════════════════════════════ */
/* CRT tube depth — static vignette layered on the existing base fill. */
.breakcore body::before {
background-image: radial-gradient(
ellipse at center,
transparent 52%,
color-mix(in srgb, var(--crust) 75%, transparent) 100%
);
}
/* Nameplate — striped datamosh underline + glitch-shear burst on hover. */
.breakcore .nameplate::after {
height: 3px;
bottom: -6px;
opacity: 0.9;
background: repeating-linear-gradient(
90deg,
var(--mauve) 0 6px,
var(--green) 6px 12px,
var(--blue) 12px 18px
);
}
@keyframes bc-shear {
0% { clip-path: inset(0 0 0 0); transform: translateX(0);
text-shadow: -1px 0 0 var(--teal), 1px 0 0 var(--mauve); }
20% { clip-path: inset(16% 0 56% 0); transform: translateX(-5px);
text-shadow: -5px 0 0 var(--green), 5px 0 0 var(--mauve); }
40% { clip-path: inset(62% 0 10% 0); transform: translateX(5px);
text-shadow: 5px 0 0 var(--teal), -5px 0 0 var(--red); }
60% { clip-path: inset(30% 0 42% 0); transform: translateX(-3px);
text-shadow: -3px 0 0 var(--mauve), 3px 0 0 var(--green); }
80% { clip-path: inset(6% 0 78% 0); transform: translateX(2px);
text-shadow: 2px 0 0 var(--teal), -2px 0 0 var(--mauve); }
100% { clip-path: inset(0 0 0 0); transform: translateX(0);
text-shadow: -1px 0 0 var(--teal), 1px 0 0 var(--mauve); }
}
.breakcore .nameplate:hover .nameplate-title {
animation: bc-shear 200ms steps(3, jump-none) 1;
}
/* Display headings — one-shot glitch-in on page load. The static chromatic
* text-shadow (defined earlier) remains as the resting state. */
@keyframes bc-load-glitch {
0% { opacity: 0; clip-path: inset(46% 0 46% 0); transform: translateX(-9px); }
20% { opacity: 1; clip-path: inset(8% 0 70% 0); transform: translateX(7px); }
40% { clip-path: inset(68% 0 8% 0); transform: translateX(-5px); }
60% { clip-path: inset(24% 0 36% 0); transform: translateX(3px); }
80% { clip-path: inset(4% 0 84% 0); transform: translateX(-2px); }
100% { opacity: 1; clip-path: inset(0 0 0 0); transform: translateX(0); }
}
.breakcore .prose h1,
.breakcore h1.font-display {
animation: bc-load-glitch 460ms steps(5, jump-none) both;
}
/* Plate — hard hover (no soft lift), RGB-split image, scanline sweep. */
.breakcore .plate:hover {
transform: translateY(-3px);
}
.breakcore .plate:hover .plate-caption-title {
text-shadow: -1px 0 0 var(--teal), 1px 0 0 var(--mauve);
}
.breakcore .plate:hover .plate-image img {
filter:
drop-shadow(-3px 0 0 color-mix(in srgb, var(--mauve) 70%, transparent))
drop-shadow(3px 0 0 color-mix(in srgb, var(--teal) 70%, transparent))
saturate(1.12) contrast(1.06);
}
.breakcore .plate .plate-image::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0;
transform: translateY(-110%);
mix-blend-mode: screen;
background: linear-gradient(
180deg,
transparent 0%,
color-mix(in srgb, var(--sky) 28%, transparent) 46%,
color-mix(in srgb, var(--mauve) 70%, transparent) 49%,
color-mix(in srgb, var(--green) 55%, transparent) 51%,
color-mix(in srgb, var(--sky) 28%, transparent) 54%,
transparent 100%
);
}
@keyframes bc-scan {
0% { transform: translateY(-110%); opacity: 0; }
12% { opacity: 1; }
88% { opacity: 1; }
100% { transform: translateY(110%); opacity: 0; }
}
.breakcore .plate:hover .plate-image::after,
.breakcore .plate:focus-visible .plate-image::after {
animation: bc-scan 0.62s cubic-bezier(0.4, 0, 0.2, 1) 1;
}
/* Section rule — hazard tape. Used on footer, post header, 404. */
.breakcore .section-rule {
color: var(--green);
font-family: var(--font-mono);
font-style: normal;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.22em;
}
.breakcore .section-rule::before,
.breakcore .section-rule::after {
height: 1px;
opacity: 0.85;
background: linear-gradient(
to right,
transparent,
color-mix(in srgb, var(--mauve) 70%, transparent) 45%,
color-mix(in srgb, var(--teal) 70%, transparent) 55%,
transparent
);
}
.breakcore .section-rule .ornament {
color: var(--mauve);
}
/* Readability — `--overlay0` (#5A2D8E) is near-invisible on the breakcore
* ground. Lift the spots that use it as actual copy to the readable
* subtext ramp. */
.breakcore .prose h6,
.breakcore .prose del,
.breakcore .hljs-comment,
.breakcore .hljs-quote,
.breakcore .site-copyright,
.breakcore .slug-hint {
color: var(--subtext0);
}
/* Chips — neon outline, monospace caps. */
.breakcore .chip {
background: transparent;
border-color: color-mix(in srgb, var(--teal) 55%, transparent);
color: var(--teal);
font-family: var(--font-mono);
font-style: normal;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.1em;
border-radius: 0;
}
.breakcore .chip-accent {
background: var(--mauve);
color: var(--crust);
border-color: var(--mauve);
}
.breakcore .chip-draft {
background: transparent;
border-color: color-mix(in srgb, var(--green) 60%, transparent);
color: var(--green);
}
/* Plate caption meta — bracketed mono coordinates. */
.breakcore .plate-caption-meta {
font-family: var(--font-mono);
letter-spacing: 0.16em;
}
.breakcore .plate-caption-sep {
color: var(--green);
opacity: 1;
}
/* Buttons & inputs — square, hard offset block-shadow, neon focus. */
.breakcore .btn,
.breakcore .field-input,
.breakcore .topbar-control,
.breakcore .topbar-control kbd { border-radius: 0; }
.breakcore .btn--primary {
color: var(--crust);
border-color: var(--mauve);
box-shadow: 3px 3px 0 0 var(--green);
}
.breakcore .btn--primary:hover {
background: var(--green);
border-color: var(--green);
color: var(--crust);
box-shadow: 3px 3px 0 0 var(--mauve);
}
.breakcore .btn--primary:active {
transform: translate(2px, 2px);
box-shadow: 1px 1px 0 0 var(--mauve);
}
.breakcore .btn--danger {
box-shadow: 3px 3px 0 0 color-mix(in srgb, var(--red) 60%, var(--crust));
}
.breakcore .btn--danger:hover {
box-shadow: 3px 3px 0 0 var(--mauve);
}
.breakcore .btn--danger:active {
transform: translate(2px, 2px);
box-shadow: 1px 1px 0 0 var(--mauve);
}
.breakcore .btn:focus-visible {
border-color: var(--green);
box-shadow: 0 0 0 2px var(--green);
}
.breakcore .field-input:focus {
border-color: var(--green);
background: color-mix(in srgb, var(--surface0) 85%, var(--green) 8%);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--green) 35%, transparent);
}
/* Prose links — magenta resting, acid-green on hover. */
.breakcore .prose a {
text-decoration-color: color-mix(in srgb, var(--mauve) 55%, transparent);
}
.breakcore .prose a:hover {
color: var(--green);
text-decoration-color: var(--green);
}
/* Reading progress — acid scan with bloom. */
.breakcore .reading-progress {
background: var(--green);
box-shadow: 0 0 8px var(--green), 0 0 3px var(--mauve);
}
/* ───── Confirm dialog (replaces window.confirm) ───── */
.cdialog-overlay {
position: fixed;
inset: 0;
z-index: 300;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.cdialog-backdrop {
position: absolute;
inset: 0;
background: color-mix(in srgb, var(--crust) 60%, transparent);
backdrop-filter: blur(8px);
}
.cdialog-panel {
position: relative;
width: 100%;
max-width: 26rem;
padding: 1.6rem 1.6rem 1.4rem;
animation: cdialog-in 0.18s cubic-bezier(0.2, 0.7, 0.2, 1);
}
@keyframes cdialog-in {
from { opacity: 0; transform: translateY(10px) scale(0.98); }
to { opacity: 1; transform: none; }
}
.cdialog-title {
font-family: var(--font-display);
font-style: italic;
font-weight: 600;
font-size: 1.4rem;
line-height: 1.15;
color: var(--text);
letter-spacing: -0.01em;
}
.cdialog-msg {
font-family: var(--font-sans);
font-size: 0.98rem;
line-height: 1.55;
color: var(--subtext1);
margin-top: 0.6rem;
}
.cdialog-actions {
display: flex;
justify-content: flex-end;
gap: 0.6rem;
margin-top: 1.5rem;
}
/* Breakcore: hard edges + neon cap + chromatic title. */
.breakcore .cdialog-panel {
border-radius: 0;
padding-top: 1.85rem;
}
.breakcore .cdialog-panel::before {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 2px;
background: linear-gradient(90deg, var(--mauve), var(--teal));
}
.breakcore .cdialog-title {
text-shadow: -1px 0 0 var(--teal), 1px 0 0 var(--mauve);
}
/* Toast error variant (replaces window.alert). */
.toast--error {
border-left: 3px solid var(--red);
color: var(--rosewater);
cursor: pointer;
}
.toast--error::before {
content: "⚠ ";
color: var(--red);
}
/* ═══ Reduced motion — universal kill-switch. Final word in the file so it
* overrides every animation/transition above, all themes. Content still
* resolves to its final state (forwards-filled keyframes complete). ═══ */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
}