Commit graph

6 commits

Author SHA1 Message Date
mithral
e55686bd21 Apply the brand: charcoal, blue-violet, purple, and the real logo
Replaces the gold experiment with the actual brand.

- Palette: brand charcoal (#303030, sampled from the logo's own background)
  with the logo's glow as the primary accent (#6d7cff) and its purple
  (#c084fc) as a secondary. Text is the palette's white / light gray. Light
  mode re-tuned to the same hues.
- Logo: the source mark keyed off its charcoal background to RGBA, cropped to
  its content bounds, and added as web/public/logo.png. It now appears in the
  sidebar and on the login screen; a square favicon is derived from it and
  wired into index.html.
- The featured kicker uses the purple secondary, so both brand hues are present
  without adding a second competing accent.

Verified: logo loads in the app, accent resolves to #6d7cff, secondary to
#c084fc, surface to #303030.
2026-10-10 17:41:49 -04:00
mithral
83c59522e7 UI: make it move like it is alive
Not new features — motion, focus, and depth, kept restrained.

- Motion tokens: one easing curve and three durations, used everywhere, so the
  app moves as a single thing rather than component by component. Honours
  prefers-reduced-motion by disabling all of it.
- View transitions: navigating fades and rises the content instead of swapping
  it, and each view starts scrolled to the top.
- Topbar frosts once the content scrolls, so the header separates from the page
  without a permanent bar of chrome.
- Featured area on Home: the item you are most likely to act on (something in
  progress, else the newest), with the cover as an ambient blurred wash. The
  colour comes from the artwork, not a decorative gradient, so it belongs to
  whatever is on screen.
- Cards: lift and scale on hover, a scrim so the action glyph always has
  contrast, a kind-appropriate glyph (play / read / expand), press feedback,
  and a visible keyboard focus ring.
- Shelves: edge fades and arrow buttons that appear only when the row actually
  continues that way; the scrollbar is hidden and the row snaps.
- Item page: ambient wash from its own cover.
- Global :focus-visible ring.

Verified in the built bundle: featured renders the in-progress item, hover
scales a 192px card to 197.8px with the glyph visible, and the item page tints
from its artwork.
2026-10-10 17:16:49 -04:00
mithral
8178502b73 Appearance settings: text size and card size, and a larger default
- All 50 font-size declarations converted from px to rem, so a single root
  value scales headings, labels and body together. The root is now 15px, a
  deliberate ~7% increase over the previous 14px.
- Card width and grid column width are now --card-w / --grid-min variables,
  driven by --card-scale independently of text. Defaults bumped too: shelf
  cards 150 -> 160px, grid columns 148 -> 158px.
- Appearance panel (nav item, visible to everyone): Text size and Card size
  as segmented controls, a live preview, and a reset. Settings are stored in
  localStorage, per device rather than per account, because a TV across the
  room wants bigger text than a laptop and the same person uses both.
- nav: 'settings' was missing from the View union after the home rewrite;
  added back along with the Appearance entry.

Verified in the built bundle: root font 15px by default; "Larger" scales it to
19.2px; "Large" cards scale 160 to 192px; both persist across a reload and are
independent of each other.
2026-10-10 17:07:43 -04:00
mithral
dc95a9f78a Redesign the user-facing experience: home, browse, item pages
The app looked like an admin panel. This is the product people actually see
when they open their library.

- Home: a landing screen with a Continue shelf (progress bars, straight from
  the progress table) and per-type shelves of recent additions. One request
  feeds it, so it does not pop in piece by piece.
- Browse: cover grid per media type with type chips, plus browse-all. Powered
  by a new GET /items that lists across every visible library, filtered by
  kind, through the same access chokepoint.
- Item page: cover, kind, title, authors, progress, a primary action that says
  Continue when you have started, description, and the file list for
  multi-file items. Cards now open the item page rather than jumping straight
  into a reader.
- Search: results grouped by media type, cover-forward.
- Navigation moved from "libraries" to "media types" — you browse Ebooks, not
  "the Ebooks folder". Libraries became an admin concern.
- Design system: dark by default, cover-forward, one accent, tighter type
  scale, horizontal shelves, skeleton loaders.
- media: GET /home, GET /items, and authors on item detail.

Verified by screenshot in a live instance: home shelves, browse grid, and item
page all render correctly.
2026-10-09 21:38:11 -04:00
mithral
a2c2f7a7c1 Phase 2b: FTS5 search
- migration 0003: an FTS5 index over media_items as an external-content table,
  so the index stores no copy of the text. Triggers keep it in sync for every
  write path, including the scanner's bulk inserts.
- migrate: splitStatements now tracks BEGIN...END depth, so CREATE TRIGGER
  bodies survive as one statement. Without this, the trigger's internal
  semicolons would tear it apart.
- search: every whitespace term is quoted before it reaches FTS5. Unquoted
  input like a bare quote, a hyphen, or the word OR is valid FTS5 syntax and
  would either error or silently change the query's meaning. Tested against a
  list of hostile inputs, including a DROP TABLE attempt.
- scoped through the same VisibleLibraryIDs chokepoint; an empty library set
  returns nothing, never everything.
- web: search box in the topbar and a results view.

Verified live: finds items across libraries, hostile input returns 200 with no
results, index stays in sync on insert/update/delete.
2026-10-09 21:10:44 -04:00
mithral
5283e073f5 Web UI: Svelte app, embedded into the binary
The frontend the app needs to be usable at all.

- web/: Svelte 5 + Vite + TypeScript SPA. Login, app shell with per-type nav,
  library list/create/scan/delete, item grid with covers, admin user
  management, and an epub.js reader that syncs progress.
- Reader resumes by spine index — an integer — which is why the polymorphic
  progress table needs no text column.
- Session state uses classic stores in a plain .ts module rather than runes in
  a .svelte.ts module, avoiding special module resolution.
- Embedding: web/embed.go embeds web/dist. A committed .gitkeep keeps the
  directive valid before the frontend is built, so `go build ./...` never
  fails. Unknown non-API paths serve index.html for client routes; /api/* and
  /healthz always return JSON and never fall back to HTML.
- Dockerfile builds the UI in a node stage and overlays dist into the Go stage.
- pnpm 12 needs allowBuilds in pnpm-workspace.yaml; esbuild approved, the two
  unused transitive scripts explicitly denied so install exits 0.

svelte-check: 0 errors, 0 warnings. Verified served: SPA shell at /, route
fallback, hashed asset, and JSON 404 on /api/*.
2026-10-09 16:40:58 -04:00