Apply paper-and-ink design system across all surfaces
Release / build-and-push (push) Has been cancelled

Replaces the generic shadcn/ui gray theme + horizontal top-bar shell with
the paper-and-ink language from the Claude Design handoff bundle: warm
off-white paper, near-black ink, Source Serif 4 + Inter, hairline borders,
muted ink accents (clay/indigo/sage/plum/ochre) used functionally for
calendars and share scopes.

Theme switcher expanded from 2 dimensions (theme × mode) to 7: palette ×
mode × fontPair × density × dashLayout × calView × navStyle. All exposed
in Settings → Appearance and persisted on the users row. Pre-paint script
applies all four data-* attributes from localStorage so reload doesn't
flash.

App shell restructured to a CSS-grid driven by data-nav on <html>: sidebar
on desktop, bottom-nav + FAB under 760px. Four desktop nav modes wired
(sidebar/rail/top/fab-only). Topbar gets a search-→-CommandPalette button,
notification bell, "+ New" quick-add, avatar.

Dashboard, calendar, lists, notes, settings, login, public share viewer,
and quick-add sheet all reskinned. Dashboard editor gains a Preset menu
(classic/split/glance) that fills the layout from the registered widgets.
FullCalendar wrapped in .fc-skin and inherits all paper-and-ink tokens via
CSS variable overrides. Public share viewer (/s/<token>) rebuilt around
ShareFrame: expiration banner, brand strip, eyebrow chip, 38px serif
title, mini-day + mini-map cards, share-rows.

Schema: drops users.theme; adds theme_palette, theme_font_pair,
theme_density, theme_dash_layout, theme_cal_view, theme_nav_style with
defaults that match the design (clay / serif-sans / regular / classic /
month / rail-desktop). Migration 0014_paper_ink_theme.

Middleware sets x-pathname so the AppShell server component can render
bare for /s/* and /login without a route-group refactor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ginnoir
2026-05-07 01:03:21 -05:00
co-authored by Claude Opus 4.7
parent e130cda6c5
commit 9612a54e52
61 changed files with 4173 additions and 894 deletions
+32 -18
View File
@@ -64,35 +64,40 @@ export function NoteEditor({ note }: { note?: NoteDto }) {
}
return (
<div className="mx-auto grid w-full max-w-6xl gap-5 p-4">
<div className="mx-auto grid w-full max-w-6xl gap-4">
<header className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<h1 className="text-2xl font-semibold">{currentNote ? currentNote.title : "New note"}</h1>
<p className="text-sm text-muted-foreground">Markdown notes shared with the household.</p>
<div className="flex items-center gap-2 min-w-0 flex-1">
{pinned && <Pin className="size-3.5 text-[var(--accent)]" />}
<h2 className="serif text-[24px] font-medium tracking-tight truncate">
{currentNote ? currentNote.title : "New note"}
</h2>
</div>
<div className="flex flex-wrap gap-2">
{currentNote ? (
<Button variant="outline" onClick={togglePinned} disabled={isPending}>
{pinned ? <PinOff /> : <Pin />}
{pinned ? "Unpin note" : "Pin note"}
<Button variant="outline" size="sm" onClick={togglePinned} disabled={isPending}>
{pinned ? <PinOff className="size-3.5" /> : <Pin className="size-3.5" />}
{pinned ? "Unpin" : "Pin"}
</Button>
) : null}
{currentNote ? <ShareButton entityType="notes.note" entityId={currentNote.id} /> : null}
{currentNote ? (
<Button variant="destructive" onClick={removeNote} disabled={isPending}>
<Trash2 />
Delete note
<Button variant="destructive" size="sm" onClick={removeNote} disabled={isPending}>
<Trash2 className="size-3.5" />
Delete
</Button>
) : null}
<Button onClick={saveNote} disabled={!title.trim() || isPending}>
<Save />
Save note
<Button size="sm" onClick={saveNote} disabled={!title.trim() || isPending}>
<Save className="size-3.5" />
Save
</Button>
</div>
</header>
<div className="grid gap-5 lg:grid-cols-[minmax(0,1fr)_minmax(280px,420px)]">
<section className="grid gap-4 rounded-lg border bg-background p-4">
<div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(280px,420px)]">
<section
className="grid gap-4 rounded-[var(--r-lg)] border-[0.5px] bg-[var(--card)] p-4 shadow-[var(--shadow-1)]"
style={{ borderColor: "var(--hair)" }}
>
<div className="space-y-1.5">
<Label htmlFor="note-title">Title</Label>
<Input
@@ -106,7 +111,13 @@ export function NoteEditor({ note }: { note?: NoteDto }) {
<textarea
id="note-body"
aria-label="Body"
className="min-h-80 w-full rounded-lg border border-input bg-transparent px-3 py-2 text-sm leading-6 outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50"
className="min-h-80 w-full rounded-[var(--r-md)] border-[0.5px] bg-transparent px-3 py-2 leading-[1.6] outline-none transition-colors placeholder:text-[var(--ink-faint)] focus-visible:border-[var(--ink)] focus-visible:ring-3 focus-visible:ring-[var(--ink)]/8"
style={{
fontFamily: "var(--serif)",
fontSize: "15px",
color: "var(--ink-2)",
borderColor: "var(--hair-2)",
}}
value={body}
onChange={(event) => setBody(event.target.value)}
placeholder="# Dinner ideas&#10;&#10;- Tacos&#10;- Soup"
@@ -123,8 +134,11 @@ export function NoteEditor({ note }: { note?: NoteDto }) {
</div>
</section>
<aside className="rounded-lg border bg-card p-4 text-card-foreground">
<h2 className="mb-3 text-sm font-medium text-muted-foreground">Preview</h2>
<aside
className="rounded-[var(--r-lg)] border-[0.5px] bg-[var(--card)] p-4 text-[var(--ink)] shadow-[var(--shadow-1)]"
style={{ borderColor: "var(--hair)" }}
>
<div className="eyebrow mb-3">Preview</div>
<MarkdownPreview markdown={body} />
</aside>
</div>