import type { Kysely } from 'kysely'; import type { Database } from '../db/schema.js'; import { type ThemeMode } from './color.js'; /** * What the CMS calls itself, and what colour it is. * * One row, because there is one deployment and one site — the same decision recorded in SCOPE under * "no multi-site". A key/value table would carry a JSON blob nobody can constrain and a read that * cannot say which keys exist; named columns get the shape checked by the same schema everything * else here is checked by. * * Every value is nullable and every null means "the default", which is not the same as an empty * string. That distinction is what lets the settings screen show the built-in title greyed out as a * placeholder rather than as a value someone has to delete, and it is why clearing the field puts * "Taproot" back instead of leaving a nameless admin. */ /** The name in the sidebar and the browser tab when nobody has chosen one. */ export declare const DEFAULT_TITLE = "Taproot"; /** * The accent as it is written in `admin.css`, in the form a colour input speaks. * * The stylesheet's own values are `oklch(52% 0.15 155)` and `oklch(70% 0.15 155)`, and **neither is * inside sRGB** — so these hexes are the nearest displayable colour rather than the same one. That * costs nothing where it matters: choosing the default is stored as null and emits no override, so * the CSS keeps its exact value; a browser on an ordinary display has already clipped it to the * same place these hexes name. What they buy is a picker that opens showing the green the admin * actually is, rather than an arbitrary one. */ export declare const DEFAULT_ACCENT: Record; /** * A starting point per hue, each pair measured rather than chosen by eye. * * Presets exist because the honest answer to "will this colour work?" is a contrast table, and * reading one before you have anything to compare it against is not how anybody picks a colour. * These are somewhere to start from and then adjust. * * Every one of them **passes every check in both palettes**, with at least the margin the built-in * green has — `branding.test.ts` asserts it against the same `accentContrast` the settings screen * renders, so a change to the derivation cannot quietly leave a preset failing. They were found by * searching lightness and chroma per hue rather than picked, and validated on the *hex*: a colour * that passes in OKLCh and clips out of sRGB on the way to a hex has not passed. * * A pair, not a hue, because light and dark need genuinely different lightnesses — the same reason * the accent is two settings rather than one. */ export interface AccentPreset { name: string; light: string; dark: string; } export declare const ACCENT_PRESETS: readonly AccentPreset[]; /** The single row, as stored. Null everywhere means nothing has ever been configured. */ export interface BrandingSettings { title: string | null; logoMediaId: string | null; accentLight: string | null; accentDark: string | null; } export interface ResolvedBranding extends BrandingSettings { /** Never empty — the default stands in. */ resolvedTitle: string; resolvedAccent: Record; /** * True while both accents are the stylesheet's own. * * The layout emits no override at all in that case, so an admin nobody has themed renders exactly * the CSS as written — no generated custom properties to read past in devtools, and no rounding * between a hex and the `oklch()` it came from. */ usesDefaultAccent: boolean; } export declare function getBranding(db: Kysely): Promise; /** * Fill in the defaults, and drop anything unusable. * * A stored accent that is not a hex colour resolves to the default rather than reaching the * stylesheet: the value goes into a `