Add an 18+ age gate and plain-language reader copy
Two lanes that are entangled in +page.svelte and library.ts, so they land together rather than as contorted partial commits. Age gate. Sexualized and explicit titles are hidden for everyone by default, signed in or not, and can only be revealed by a reader who has confirmed a date of birth showing 18+ and has then opted in. GitHub's OAuth API exposes no date of birth, age, or age bracket in any scope, so the claim is self-attested; this is an honest record of what the reader told us, not age assurance. The unlock is never a trusted stored boolean. It is recomputed from the date in parseAdultConsent, in the Svelte derived state, and again server-side before every write, so a request claiming allowAdult on a minor's date is persisted as a refusal. The date itself is stored rather than a derived flag so a reader who confirms at 17 gains the option on their birthday without being asked again. isAdult computes the 18th birthday as a calendar date rather than elapsed days, so leap days and time zones cannot move it. Only sexualized and explicit sit behind the gate. Harem is a story structure and AI disclosure is a production fact, so both stay ordinary preferences. Unknown signals still show: forcing them out would empty the grid at current cover-assessment coverage. Every consumer reads effectiveFilters rather than raw filters, so a stale localStorage value, another tab, or an imported settings blob cannot reveal anything on its own. Gated controls are not rendered while locked and setFilter refuses gated keys as a second line of defence. Attestation lives in D1 for signed-in readers and localStorage for guests, and a guest's claim is carried up on sign-in rather than asked for twice. Reader copy. Internal pipeline and rubric vocabulary was surfacing in the UI. Filter labels, catalog-completeness notes, series and book drawer notes, and the Jev and cover assessment note strings now read as plain language for readers. Also fixes workers/accounts.test.ts, which only ever executed migration 0001. It now applies every migration in sorted order with named-column seed inserts, so the next ALTER TABLE does not silently break it.
Parentf261171
21 files changed+533-77
StackedSplit