/* Fonts are loaded dynamically via layout.tsx based on theme config */ /* --text-muted missed the 4.5:1 that WCAG AA requires for text of this size in every preset, ranging from 2.33:1 (monograph) to 3.50:1 (studio-modern). It is not a decorative colour: it carries the hero subtitle, section labels, album metadata and the footer links, all between 0.6 and 0.9 rem. Only lightness was raised; hue and saturation are unchanged, so the warm greys of editorial, classic and noir stay warm instead of drifting to neutral. Each value is checked against --bg-primary AND --bg-card, and the worse of the two counts. In minimal and monograph this pushed --text-muted so close to --text-secondary that the two steps became indistinguishable (#828282 vs #888888). --text-secondary moved along there, restoring two distinct levels: about 7:1 for secondary, about 4.7:1 for muted. */ /* ── Design Tokens (Fallback Defaults) ───────────── */ /* These are overridden at runtime by CSS vars injected from gallery.yaml theme config. */ :root { --bg-primary: #1a1a1a; --bg-secondary: #141414; --bg-card: #1e1e1e; --bg-card-hover: #242424; --bg-overlay: rgba(0, 0, 0, 0.98); --text-primary: #f5f5f0; --text-secondary: #a0a09a; --text-muted: #86857f; --border-subtle: #2a2a28; --border-hover: #3a3a38; /* The layout sets --accent-dark/--accent-light (and --on-accent-*) from the theme; the fallbacks only cover a page rendered without it. */ --accent: var(--accent-dark, #e60012); --accent-dim: color-mix(in srgb, var(--accent) 12%, transparent); --on-accent: var(--on-accent-dark, #ffffff); /* The accent as a text colour: the layout lifts it to 4.5:1 per mode (accentText() in lib/config/theme.ts). Fills, borders and underlines keep --accent; anything that is read uses this. The fallbacks are that function's output for the default red. */ --accent-text: var(--accent-text-dark, #ef5965); /* One focus ring for everything a visitor can focus, inputs included. The contact form and the password field drew an accent ring over an accent border while every link and button drew this one. */ --focus-ring: var(--text-primary); /* The border that marks an input as an input (WCAG 1.4.11, 3:1). The --border-subtle hairline measured 1.15–1.29:1 against the page; --text-muted is checked at 4.5:1 against both page surfaces in every preset, so borrowing it clears the bar with margin. */ --input-border: var(--text-muted); /* Status that is not an error: the journal's draft badge. Per mode, like --error; #eab308 read 1.4:1 on the light pages. */ --warning: #eab308; color-scheme: dark; /* Error and destructive text. One value per mode: no single red clears 4.5:1 on both a near-black and a near-white page (the fixed reds these replace measured 3.2–3.8:1 on the light presets). #ff6b6b is at least 5.6:1 on every dark surface token, #c62828 (below) at least 4.66:1 on every light one. */ --error: #ff6b6b; /* The lightbox paints its own dark overlay in both modes, so its controls cannot use --text-*: in light mode those are near-black, and the close button, arrows and counter came out at 1.1–2:1. Lightbox.module.css points the text tokens at these inside the overlay in light mode; dark mode keeps each preset's tinted greys. */ --lightbox-text: rgba(255, 255, 255, 0.92); --lightbox-text-secondary: rgba(255, 255, 255, 0.72); --lightbox-text-muted: rgba(255, 255, 255, 0.62); --lightbox-border: rgba(255, 255, 255, 0.12); /* Round controls float over the photo on narrow screens; a dark fill keeps them visible on a white sky, where the old white-alpha disc vanished. */ --lightbox-control-bg: rgba(0, 0, 0, 0.35); --lightbox-control-bg-hover: rgba(0, 0, 0, 0.55); --lightbox-control-border: rgba(255, 255, 255, 0.18); /* A saved favourite. Not the accent: minimal's is white and a custom one may be dark navy, neither of which marks anything on the overlay. */ --lightbox-fav: #ff6b6b; /* The passepartout photo frame: the mat board, its edge, and the EXIF line printed on it. --mat-text is checked at 4.5:1 against --mat-bg for every preset and mode (lib/__tests__/passepartout-mat.test.ts). */ --mat-bg: #111111; --mat-border: rgba(255, 255, 255, 0.12); --mat-text: var(--text-muted); --font-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; --font-serif: 'Playfair Display', Georgia, 'Times New Roman', serif; --font-caption: 'EB Garamond', Georgia, serif; --radius-sm: 0px; --radius-md: 0px; --radius-lg: 0px; --transition-fast: 200ms ease; --transition-normal: 400ms ease; --transition-slow: 600ms ease; --max-width: 1400px; --header-height: 64px; } /* ── Light Theme (Studio Light) ──────────────────── */ [data-theme='light'] { --bg-primary: #f5f5f0; --bg-secondary: #eaeae5; --bg-card: #ffffff; --bg-card-hover: #f0f0eb; --bg-overlay: rgba(245, 245, 240, 0.96); --text-primary: #1a1a18; --text-secondary: #4a4a45; --text-muted: #6a6a65; --border-subtle: #ddddd8; --border-hover: #c5c5c0; --accent: var(--accent-light, #c8000f); --accent-dim: color-mix(in srgb, var(--accent) 12%, transparent); --on-accent: var(--on-accent-light, #ffffff); --accent-text: var(--accent-text-light, #c8000f); --error: #c62828; --warning: #7a5600; --mat-bg: #ffffff; --mat-border: rgba(0, 0, 0, 0.1); color-scheme: light; } /* ── Studio Modern: Leica Precision Palette ──────── */ [data-preset='studio-modern'] { --bg-primary: #121212; --bg-secondary: #0d0d0d; --bg-card: #191919; --bg-card-hover: #202020; --bg-overlay: rgba(10, 10, 10, 0.92); --text-primary: #f4f4f2; --text-secondary: #9c9c96; --text-muted: #82827c; --border-subtle: rgba(255, 255, 255, 0.08); --border-hover: rgba(255, 255, 255, 0.2); --radius-sm: 0px; --radius-md: 0px; --radius-lg: 0px; --max-width: 1440px; --header-height: 64px; /* Signal red. Light mode darkens whatever accent is configured so it still carries on a near-white background (#e60012 -> ~#c4000f). */ --sm-accent: var(--accent); /* Gutter + hairline, reused across app/themes/studio-modern.css */ --sm-gutter: 48px; --sm-hairline: 1px solid var(--border-subtle); --sm-chip-bg: rgba(0, 0, 0, 0.75); } [data-preset='studio-modern'][data-theme='light'] { --sm-accent: color-mix(in srgb, var(--accent) 86%, #000); --bg-primary: #fafaf8; --bg-secondary: #f0f0ec; --bg-card: #ffffff; --bg-card-hover: #f5f5f1; --bg-overlay: rgba(250, 250, 248, 0.96); --text-primary: #161614; --text-secondary: #5a5a55; /* #73736e passed on the page and on cards but not on --bg-secondary (4.17:1) or --bg-card-hover (4.36:1). Darkened in equal steps so the warm tint stays: 4.55:1 on --bg-secondary now. */ --text-muted: #6d6d68; --border-subtle: rgba(0, 0, 0, 0.09); --border-hover: rgba(0, 0, 0, 0.25); } /* ── Minimal: Swiss Brutalist Palette ────────────── */ [data-preset='minimal'] { --bg-primary: #000000; --bg-secondary: #0a0a0a; --bg-card: #111111; --bg-card-hover: #1a1a1a; --bg-overlay: rgba(0, 0, 0, 0.98); --text-primary: #ffffff; --text-secondary: #9e9e9e; --text-muted: #7c7c7c; --border-subtle: #1a1a1a; --border-hover: #333333; } [data-preset='minimal'][data-theme='light'] { --bg-primary: #ffffff; --bg-secondary: #f7f7f7; --bg-card: #ffffff; --bg-card-hover: #fafafa; --bg-overlay: rgba(255, 255, 255, 0.98); --text-primary: #000000; --text-secondary: #475569; --text-muted: #627083; /* 4.65:1 on the tinted header (#f6f6f1), not just on white */ --border-subtle: #eeeeee; --border-hover: #cccccc; } /* ── Editorial: Cinematic Magazine Palette ────────── */ [data-preset='editorial'] { --bg-primary: #1a1714; --bg-secondary: #141210; --bg-card: #1e1b17; --bg-card-hover: #252119; --bg-overlay: rgba(20, 18, 16, 0.98); --text-primary: #ede8e0; --text-secondary: #9a9086; --text-muted: #8c8275; --border-subtle: #2a2620; --border-hover: #3a3530; } [data-preset='editorial'][data-theme='light'] { --bg-primary: #faf8f4; --bg-secondary: #f0ece4; --bg-card: #ffffff; --bg-card-hover: #f5f2ec; --bg-overlay: rgba(250, 248, 244, 0.96); --text-primary: #1a1714; --text-secondary: #4a4540; --text-muted: #6a655f; --border-subtle: #e5e0d8; --border-hover: #ccc7c0; } /* ── Classic: Gilded Gallery Palette ─────────────── */ [data-preset='classic'] { --bg-primary: #121210; --bg-secondary: #0e0e0c; --bg-card: #181816; --bg-card-hover: #1e1e1a; --bg-overlay: rgba(14, 14, 12, 0.98); --text-primary: #f0ece4; --text-secondary: #a09888; --text-muted: #897f6c; --border-subtle: #2a2822; --border-hover: #3a3830; /* A warm mat. Both modes set it: this block comes after the generic light one, so a light value left out here would lose to the dark one. */ --mat-bg: #1a1816; --mat-border: rgba(196, 154, 60, 0.12); /* --text-muted, a step lighter: it is 4.48:1 on this mat. */ --mat-text: #8c826f; } [data-preset='classic'][data-theme='light'] { --bg-primary: #fdfbf5; --bg-secondary: #f5f0e6; --bg-card: #ffffff; --bg-card-hover: #faf6ee; --bg-overlay: rgba(253, 251, 245, 0.96); --text-primary: #1a1810; --text-secondary: #4a4535; --text-muted: #6a6555; --border-subtle: #e5dfcf; --border-hover: #d0c8b5; --mat-bg: #fffdf5; --mat-border: rgba(196, 154, 60, 0.2); --mat-text: var(--text-muted); } /* ── Noir: Darkroom Analog Palette ───────────────── */ [data-preset='noir'] { --bg-primary: #111014; --bg-secondary: #0c0b0f; --bg-card: #16141a; --bg-card-hover: #1e1c22; --bg-overlay: rgba(11, 10, 15, 0.98); --text-primary: #f0ebe0; --text-secondary: #998f80; --text-muted: #8a7c6c; --border-subtle: #252228; --border-hover: #3a3540; } [data-preset='noir'][data-theme='light'] { --bg-primary: #faf5ef; --bg-secondary: #f0ebe3; --bg-card: #ffffff; --bg-card-hover: #f5f0e8; --bg-overlay: rgba(250, 245, 239, 0.96); --text-primary: #1a1610; --text-secondary: #4a4538; --text-muted: #6a6558; --border-subtle: #e5dfd5; --border-hover: #ccc5b8; } /* ── Monograph: Typographic / Book Palette ────────── */ [data-preset='monograph'] { --bg-primary: #151515; --bg-secondary: #0f0f0f; --bg-card: #1a1a1a; --bg-card-hover: #222222; --bg-overlay: rgba(15, 15, 15, 0.98); --text-primary: #e8e8e8; --text-secondary: #a5a5a5; --text-muted: #828282; --border-subtle: #222222; --border-hover: #3a3a3a; } [data-preset='monograph'][data-theme='light'] { --bg-primary: #fafafa; --bg-secondary: #f2f2f2; --bg-card: #ffffff; --bg-card-hover: #f7f7f7; --bg-overlay: rgba(250, 250, 250, 0.98); --text-primary: #111111; --text-secondary: #475569; --text-muted: #627083; /* 4.65:1 on the tinted header (#f6f6f1), not just on white */ --border-subtle: #e5e5e5; --border-hover: #cccccc; } /* ── Kunsthalle: Gallery Wall Palette ──────────── */ [data-preset='kunsthalle'] { --bg-primary: #2b2b29; --bg-secondary: #242422; --bg-card: #323230; --bg-card-hover: #393937; --bg-overlay: rgba(36, 36, 34, 0.98); --text-primary: #f0efeb; --text-secondary: #c6c4bd; --text-muted: #aba9a2; --border-subtle: #3d3d3a; --border-hover: #55554f; --max-width: 1240px; /* The default #ff6b6b reaches only 4.2:1 on the lighter grey card; this is 5.1:1 on every surface. */ --error: #ff8a80; /* A grey board a step lighter than the wall, not the default black mat. Both modes set it: this block comes after the generic light one. The caption on it is --text-muted, 5.07:1. */ --mat-bg: #373735; --mat-border: rgba(255, 255, 255, 0.08); } [data-preset='kunsthalle'][data-theme='light'] { --bg-primary: #dddcd8; --bg-secondary: #d4d3ce; --bg-card: #e8e7e3; --bg-card-hover: #e2e1dd; --bg-overlay: rgba(221, 220, 216, 0.96); --text-primary: #1b1b19; --text-secondary: #3f3e3a; --text-muted: #52514c; --border-subtle: #c9c8c3; --border-hover: #aeada7; /* The default #c62828 reaches only 4.1:1 on the grey wall; this is 5.0:1 on every surface. */ --error: #a61b1b; /* Warning text on the grey wall: 5.3:1, where an amber of #7a5600 is 4.4:1. */ --warning: #6b4b00; /* A pale grey board on the grey wall. Caption (--text-muted): 6.79:1. */ --mat-bg: #eeede9; --mat-border: rgba(0, 0, 0, 0.12); } /* ── Ma: Washi / Sumi Palette ──────────────────── */ [data-preset='ma'] { --bg-primary: #1a1917; --bg-secondary: #141311; --bg-card: #21201d; --bg-card-hover: #282623; --bg-overlay: rgba(20, 19, 17, 0.98); --text-primary: #ece7dd; --text-secondary: #b5ad9f; --text-muted: #978f82; --border-subtle: #2c2a26; --border-hover: #403d37; --max-width: 1120px; } [data-preset='ma'][data-theme='light'] { --bg-primary: #f0ede6; --bg-secondary: #e7e3da; --bg-card: #f6f4ef; --bg-card-hover: #ebe7df; --bg-overlay: rgba(240, 237, 230, 0.96); --text-primary: #1f1d1a; --text-secondary: #4d4943; --text-muted: #66615a; --border-subtle: #dcd7cc; --border-hover: #c5bfb2; /* The default #c62828 reaches only 4.4:1 on the washi secondary; this is 5.3:1 on every surface. */ --error: #b02020; } /* ── Cyanotype: Prussian Blue Palette ──────────── */ [data-preset='cyanotype'] { --bg-primary: #0e1621; --bg-secondary: #0a111a; --bg-card: #132030; --bg-card-hover: #192839; --bg-overlay: rgba(10, 17, 26, 0.98); --text-primary: #e3eaf0; --text-secondary: #a8b7c6; --text-muted: #8898ab; --border-subtle: #1d2a3a; --border-hover: #2e4056; --max-width: 1440px; } [data-preset='cyanotype'][data-theme='light'] { --bg-primary: #eef2f4; --bg-secondary: #e3e9ec; --bg-card: #f7f9fa; --bg-card-hover: #e9eef1; --bg-overlay: rgba(238, 242, 244, 0.96); --text-primary: #10243a; --text-secondary: #34495f; --text-muted: #4f6379; --border-subtle: #d2dce2; --border-hover: #b3c3cd; } /* ── Salon: Velvet / Plaster Palette ───────────── */ [data-preset='salon'] { --bg-primary: #1c1315; --bg-secondary: #160f11; --bg-card: #241a1c; --bg-card-hover: #2c2023; --bg-overlay: rgba(22, 15, 17, 0.98); --text-primary: #f3eae7; --text-secondary: #c5b1ad; --text-muted: #aa938f; --border-subtle: #34262a; --border-hover: #4a383c; --max-width: 1320px; /* An oxblood board on the velvet wall, edged in the blush accent. Both modes set it: this block comes after the generic light one. Caption (--text-muted): 5.68:1. */ --mat-bg: #2e1a1e; --mat-border: rgba(227, 163, 154, 0.16); } [data-preset='salon'][data-theme='light'] { --bg-primary: #f5efec; --bg-secondary: #ece3df; --bg-card: #fbf8f6; --bg-card-hover: #f1e9e5; --bg-overlay: rgba(245, 239, 236, 0.96); --text-primary: #271618; --text-secondary: #573f42; --text-muted: #6f5659; --border-subtle: #e3d5d1; --border-hover: #ccb8b3; /* The default #c62828 reaches only 4.4:1 on the plaster secondary; this is 5.4:1 on every surface. */ --error: #b02020; /* A plaster-rose board, rosier than the wall, edged in oxblood. Caption (--text-muted): 5.38:1. */ --mat-bg: #f2e4df; --mat-border: rgba(125, 31, 44, 0.16); } /* ── Birch: Birch / Pine Palette ───────────────── */ [data-preset='birch'] { --bg-primary: #121613; --bg-secondary: #0e110f; --bg-card: #181d19; --bg-card-hover: #1f2520; --bg-overlay: rgba(14, 17, 15, 0.98); --text-primary: #e9eee9; --text-secondary: #aab5ac; --text-muted: #8d9a90; --border-subtle: #232b25; --border-hover: #364039; --max-width: 1400px; } [data-preset='birch'][data-theme='light'] { --bg-primary: #f3f5f1; --bg-secondary: #e9ece6; --bg-card: #fbfcfa; --bg-card-hover: #eef1eb; --bg-overlay: rgba(243, 245, 241, 0.96); --text-primary: #19201b; --text-secondary: #434e46; --text-muted: #5b675e; --border-subtle: #dce1d9; --border-hover: #c1c8be; } /* ── Reset ────────────────────────────────────────── */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { font-family: var(--font-sans); background: var(--bg-primary); color: var(--text-primary); min-height: 100vh; line-height: 1.6; } a { color: inherit; text-decoration: none; } button { cursor: pointer; font-family: inherit; } img { display: block; max-width: 100%; } /* [tabindex='-1'] is left out: those are focus targets for scripts and skip links (
, the lightbox dialog), and outlining a whole region there helps nobody. */ a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible, summary:focus-visible, [role='button']:focus-visible, [tabindex]:not([tabindex='-1']):focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }