The Site preferences dialog: theme, text size, animations, reader mode and analytics.
How it fits together
- Any element with
data-open-prefs(the gear buttons in the header and footer, on every page) opens the dialog. - The dialog's markup is built here, so every page gets the same one without repeating it in the HTML. It only exists with JavaScript on, which is also the only way to open it.
- The settings themselves live in
theme.js, which applies them before first paint. This file reads and changes them throughwindow.chaPrefs, and listens forcha:prefsso the controls stay in step with the header / footer theme toggles. - The Analytics switch goes through
window.chaConsent(consent.js). Pages without it simply don't show that row.
Every control applies its change at once — the page behind the dialog is the preview — so there is no Save button.
Why showModal() here
Unlike the contact dialog (modal.js, built around the page-level
challenge overlay Google's reCAPTCHA once needed), this one has no such
constraint, so the native modal dialog is fine: the browser makes the page
inert and handles Escape. Focus goes back to the opening button on close.
Loaded as an ES module.
Members
(inner, constant) CLOSE_ICON :string
Close icon, inlined because the secondary pages' sprite
(src/partials/icons.html) doesn't carry #i-close.
Type:
- string
(inner, constant) MOTION_HINT
Hint under Animations when the device already asks for reduced motion.
(inner) dialog
The dialog, built on first open. @type {HTMLDialogElement|null}
(inner, constant) osReducedMotion
The device's reduced-motion setting. @type {MediaQueryList}
(inner) trigger
The button that opened the dialog, refocused on close. @type {HTMLElement|null}
Methods
(inner) build() → {HTMLDialogElement}
Builds the dialog and wires its controls. Runs once.
Returns:
The dialog.
- Type
- HTMLDialogElement
(inner) markup() → {string}
The whole dialog. Built when first opened, by which time consent.js has
run and window.chaConsent shows whether to offer the Analytics row.
Returns:
The dialog's HTML.
- Type
- string
(inner) open(opener) → {void}
Opens the dialog, building it first if needed.
Parameters:
| Name | Type | Description |
|---|---|---|
opener |
HTMLElement | The element that was clicked. |
Returns:
- Type
- void
(inner) segmented(name, legend, cls, options) → {string}
Builds one radio group as a row of segmented buttons.
Parameters:
| Name | Type | Description |
|---|---|---|
name |
string | The preference name ( |
legend |
string | The group's visible label. |
cls |
string | Class list for the option row, written out in full at the call site so Tailwind's content scan keeps its rules. |
options |
Array.<Array.<string>> |
|
Returns:
The fieldset's HTML.
- Type
- string
(inner) sync() → {void}
Makes every control match the saved preferences.
Returns:
- Type
- void
(inner) toggleRow(name, label, hint) → {string}
Builds one on/off row: a label and hint on the left, a switch on the right.
Parameters:
| Name | Type | Description |
|---|---|---|
name |
string | The preference name (used in the ids). |
label |
string | The visible label. |
hint |
string | The one-line explanation under it. |
Returns:
The row's HTML.
- Type
- string