Commit Graph

503 Commits

Author SHA1 Message Date
Alexander 8c75a2ddda style(fitness/period): make end-period button full-width
Flat nord11 fill, full-width CTA with check icon and uppercase label
replaces the muted tertiary-bg pill so ending an ongoing period is
the clear primary action on the status card.
2026-04-23 15:57:03 +02:00
Alexander c01dff197f perf(recipes/search): memoise per-recipe normalized search string
Every keystroke the filter rebuilt the lowercased + diacritic-stripped
+ soft-hyphen-stripped concat of name/description/tags per recipe. For
a 200+ recipe catalogue that's a lot of regex work on the hot path.

Cache the normalised string in a WeakMap keyed by the recipe object;
first keystroke still pays the full cost, every subsequent one is a
single indexOf per recipe.

Picked client-side memoisation over the audit's suggested server-side
`_searchKey` to avoid duplicating every recipe's text over the wire.
2026-04-23 15:50:08 +02:00
Alexander 076c6efb38 perf(faith/calendar): trim yearDays, send pre-filtered feastDots
yearDays was a 365-entry array (one per day in the LY window) with
{iso, name, rank, color, seasonKey} on each — the client only needed
the color (for the needle pin on the currently-selected day; RingView
re-did the feast filter itself). Split into:

- yearDays: {iso, color} — unchanged count, but ~60% smaller per entry
  (drops name, rank, seasonKey)
- feastDots: {iso, name, rank, color} — new, pre-filtered to
  rank > ferial server-side (~150 entries instead of 365)

RingView's `feastDots` derivation shrinks to filtering out just the
currently-selected day, and `activeFeasts` filters `feastDots` by arc
bounds instead of re-scanning yearDays. needleDay's color lookup still
works with the trimmed YearDay.

Also collapses a stray `locals.session ?? (locals.session ?? …)` the
earlier #5 sweep introduced in both calendar page loaders.
2026-04-23 15:37:38 +02:00
Alexander c912afd46a perf: reuse locals.session from hook in all remaining routes
Extends the previous loader-only sweep across the full tree: every
remaining `await locals.auth()` now falls back through
`locals.session ?? await locals.auth()`, so the hook's cached result
is reused.

68 files, 107 sites touched — loaders, form actions, and API
endpoints across cospend / tasks / fitness / faith / recipe / admin.
hooks.server.ts is intentionally left alone since it's the originating
call that populates locals.session in the first place.
2026-04-23 15:08:10 +02:00
Alexander 800a544190 perf: reuse locals.session from hook instead of re-awaiting locals.auth()
hooks.server.ts already awaits auth() once and stores the result on
locals.session. In-scope loaders (recipe list + filter views, rosary,
prayers, calendar — already done — and fitness stats) were awaiting
locals.auth() a second time per request.

Switched to the existing `locals.session ?? await locals.auth()` pattern
so the hook's result is reused. Also pulls session out of Promise.all
legs since it's now synchronous when the hook ran.

Scope: loaders only — actions, /admin, /edit, /add intentionally skipped.
2026-04-23 15:06:05 +02:00
Alexander 3b4318206d perf: dynamic-import chart.js in FitnessChart
Chart.js (~244 KB) was a top-level import, so every route that referenced
FitnessChart.svelte transitively pulled it. Defer it to an async block
inside onMount so non-stats fitness routes (workout, check-in, nutrition,
history list) no longer ship chart.js.

- `ChartCtor` holds the async-loaded constructor
- `disposed` guard handles unmount during the import
- theme MutationObserver / matchMedia wiring moved inside the async
  block so it only attaches once the chart actually exists
2026-04-23 14:56:19 +02:00
Alexander abb59f46a6 perf: Lucide subpath imports to split 748 KB icon chunk
Barrel `from '@lucide/svelte'` imports pulled every referenced icon into
one shared 748 KB client chunk. Switch every call site to per-icon
subpaths (`@lucide/svelte/icons/<kebab-name>`) so Vite tree-shakes each
icon independently. Also logs the TODO list for the perf audit so we
don't lose track.

- 46 files, 106 unique icons
- single `Minus as MinusIcon` alias preserved
- Lucide-internal aliases (`AlertTriangle`, `BarChart3`) resolve through
  Lucide's own re-export shims; no behavioral change
2026-04-23 14:52:39 +02:00
Alexander 5638913b1d feat(fitness/stats): 4 cm minimum y-axis range on body-part history charts
Body-measurement variation of <4 cm used to stretch the full chart
height, making normal weekly noise look dramatic. Now the y-axis
enforces a 4 cm floor centered on the data's midpoint; wider swings
render at their actual range as before.

- FitnessChart: new optional `yMin` / `yMax` props mapped to Chart.js
  `suggestedMin` / `suggestedMax` — soft bounds, so data that exceeds
  them still widens the axis.
- `/fitness/stats/history/[part]`: computes min/max across available
  values (both sides if paired), enforces the 4 cm floor, passes to
  FitnessChart. Tick distance stays on Chart.js auto — small ranges
  get 0.5 cm ticks, wider ones scale up naturally.
2026-04-23 14:21:47 +02:00
Alexander 9a15779a44 feat(fitness): rename Measure route to Check-in / Erfassung (NotebookPen icon)
CI / update (push) Successful in 3m47s
Route slugs and nav label rename only — storage, API endpoints
(`/api/fitness/measurements`), and the `BodyMeasurement` Mongo model
keep their technical names.

- `/fitness/measure` → `/fitness/check-in` (EN)
- `/fitness/messen` → `/fitness/erfassung` (DE)
- Folder `[measure=fitnessMeasure]` → `[checkin=fitnessCheckIn]`
  (git rename; history preserved).
- Param matcher `fitnessMeasure.ts` → `fitnessCheckIn.ts`, accepts
  `check-in` / `erfassung`.
- `fitnessSlugs(lang).measure` and `fitnessLabels(lang).measure` code
  keys are unchanged — value returns "check-in"/"erfassung" and
  "Check-in"/"Erfassung" respectively, so no call site needs touching.
- slugMap language-detection updated to `erfassung ↔ check-in`.
- Service-worker cache list + the layout regex that gates the wider
  content width now reference the new slugs.
- Nav icon swapped from `Ruler` to `NotebookPen` — reads as "logging
  entries" and spans weight / composition / period better.

Bookmarks on the old URLs will 404; no redirect added.
2026-04-23 14:12:54 +02:00
Alexander f807a43d58 feat(fitness/stats): body-fat trend chart as Δ from baseline
Mirrors the weight chart pipeline (SMA + ±1σ confidence band) for
body-fat %, but emits deltas from the first displayed measurement so
the y-axis shows change instead of raw numbers. Title surfaces the
baseline (e.g. "Body Fat · Δ from 18.2%"), y-unit is "pp" (percentage
points), colours are purple trend on top of an orange raw-data line
so it reads differently from weight's green+blue at a glance.

FitnessChart gained two shared upgrades: `interaction.mode = 'index'`
on line charts so hovering the x-axis shows tooltips for every dataset
(including the trend line whose pointRadius is 0), and a `σ` dataset
filter so the confidence band doesn't clutter the tooltip. A new
optional `tooltipFormatter` prop lets callers format the hover label;
the BF chart uses it to show the signed delta + reconstructed
absolute % for raw points and additionally the ±1σ window for trend
points (e.g. "+0.30 ±0.45 pp · 18.5% (18.0–18.9%)").
2026-04-23 13:57:47 +02:00
Alexander 8611275bca feat(fitness/body-parts): "Same as last" button + larger Copy L→R pill
- New "Same as last" pill below each step's stepper. Clicking fills
  the input(s) with the prior recorded value(s) — for paired steps
  in split mode, both L and R — and advances to the next step.
  Only rendered when a previous measurement exists; the placeholder
  already surfaces the exact number so the button text stays terse.
- Copy L→R button resized to match the same-as-last pill (0.88 rem
  text, 0.55 × 1.1 rem padding) and given top margin. Unicode →
  swapped for a proper ArrowRight icon between L and R.
- i18n: added `same_as_last` and split `copy_l_to_r` into
  `copy_l_to_r_before` / `copy_l_to_r_after` so each language keeps
  its natural wrapping around the arrow (EN "Copy L / R",
  DE "L / R übernehmen").
2026-04-23 13:45:16 +02:00
Alexander 91e1efda6f feat(fitness/measure): consolidate entries by day + richer past-measurements summary
- Server POST now upserts by (user, calendar day). Non-conflicting
  fields merge silently; real overwrites (new non-empty value ≠
  stored value) return 409 with the conflict list. Client retries
  with `?overwrite=1` after a confirm dialog naming each field and
  its old→new value. Null/empty payload fields are skipped, so
  logging a body-fat entry on a day that already has weight merges
  cleanly without flagging a phantom weight conflict.
- `summaryParts` in the history now includes a body-parts count,
  e.g. "86 kg · 0.1% bf · 5 body parts" or "5 body parts" instead
  of the flat "Body measurements only" fallback. Pluralised in EN
  and DE.
- Inline quick-edit: "Full edit →" text replaced by a dashed primary
  pill `Pencil · Edit all fields · ChevronRight`, inlined with the
  X / ✓ action buttons on the same row. The label collapses to
  icons only at ≤480px so the three controls stay on one line.
- Quick-edit date input swapped from native `<input type="date">`
  to the site's `DatePicker` component.
- New i18n: `overwrite_title`, `overwrite_message`, `overwrite_confirm`.
- TODO.md marks features #2 and #3 done. CLAUDE.md carries a
  policy note (no AI-attribution trailers on commits).
2026-04-23 13:35:41 +02:00
Alexander 6d3165f405 feat(fitness/measure): paginate past measurements (SSR 10, "Show more" pulls 20)
SSR now ships only the 10 most recent measurements (down from 200) to
cut initial page weight. A "Show more (N/total)" pill appears below
the list when more are available; clicking fetches the next 20 via
the existing GET endpoint (offset/limit already supported) and
appends with dedupe by `_id`.

`measurementsTotal` is seeded from the API's `total` field and kept
in sync on save (+1) / delete (−1). The button is hidden when the
history is collapsed or when `measurements.length >= total`.

Added `show_more` i18n string.
2026-04-23 13:18:32 +02:00
Alexander e9ebe492fb chore: clear all svelte-check errors and warnings repo-wide (454 → 0)
Mostly additive JSDoc/TS type annotations and null/undefined guards —
no runtime behavior changes. Starting baseline: 454 errors + 1 warning
across ~50 files. After: 0/0, build is clean.

Highlights:
- Duplicate object-literal keys fixed: 11 in cospendI18n.ts, 2 in
  fitnessI18n.ts (dropped second `loading`; renamed `protein_per_kg`
  stats-card label to reuse `protein`), 1 in shoppingCategorizer.
- `bind:this` state declared with `HTMLDivElement | null` across
  DatePicker + Muscle{Map,Filter,Heatmap}.
- SaveFab's required `onclick` made optional (type="submit" handles
  form submission in most callsites).
- Implicit any on ~200 callback parameters replaced with concrete
  JSDoc/TS types. Chart.js generics and one mongoose query chain cast
  are the only `any` / `unknown as any[]` uses introduced.
- Stats history discriminated union (`paired: true | false`) lets the
  template narrow `series` and `stats` properly.
- Food page server guards use `throw new Error('unreachable')` after
  `errorWithVerse(...)` awaits so TS narrows `entry`/`recipe`/`meal`
  below. Same pattern applied to cospend payments, calendar detail,
  and prayers server loads.
- Mongo `Date → string` serialization helper in cospend list so
  `IShoppingItem[]` fits `ShoppingItem[]` at the boundary.
- Recipe category/tag pages use a local `RecipeItem` alias (derived
  from `BriefRecipeType`) so `rand_array`/filter callbacks type.
- `web-haptics/svelte` has no bundled `.d.ts`; added a local
  `@ts-expect-error` shim on the one import line.

Files touched: ~50 across fitness, cospend, faith, recipe, and shared
lib components / API routes.
2026-04-23 13:12:07 +02:00
Alexander 0a188ad4ab fix(fitness): measure page polish — stable steppers, narrow history, body-parts chrome tweaks
CI / update (push) Successful in 4m5s
- Lock +/- button positions by normalizing stepped weight/body-fat
  values to .toFixed(1) so trailing zeros stay; placeholders also
  normalized. Input width no longer jitters through a step sequence.
- Cap .history-section width on mobile/tablet to match .main-col
  (480px / 760px) so "Past measurements" aligns with the metric cards.
- Body-parts page:
  - Remove the "Running totals" list from the right panel.
  - Hide the keyboard-shortcut legend by default; show on `?` (toggle)
    or Escape (dismiss), with a small `?` pill hint in its place.
    Added kbd_hint i18n string.
  - Push skip + back/next toward the edges of the bottombar; pull
    progress dots + close button inward symmetrically.
  - Center the keyboard legend / hint on the screen width rather than
    between the skip and nav buttons (position: absolute + translate).
2026-04-23 11:31:25 +02:00
Alexander def176db4d feat(fitness): redesign measure page with muscle-man map, inline edit, and desktop 2-col layout
- Weight + body fat cards share a unified .metric-card component with wheel
  + keyboard (Arrow/Shift+Arrow) stepping. Side-by-side on tablet and up.
- Replaced body-parts accordion with a prominent card showing a cropped
  muscle-front silhouette and overlay dots/bands marking which regions
  have measurements. Shoulders + chest render as dotted tape-measure
  bands; other parts as dots. "Last measured" now relative (N days ago).
- Desktop layout: .main-col (form + period tracker) left, history on
  right. Two columns center together at wider widths instead of drifting
  apart. Fitness layout detects measure index and bumps max-width to
  1400px, matching nutrition.
- Inline history edit: pencil swaps the row for a compact date/kg/%
  form (Enter saves, Escape cancels) via PUT /api/fitness/measurements.
  Full-edit link preserved for body-parts tweaks.
- Body-parts history heading renamed to "Past measurements" /
  "Frühere Messungen" to avoid collision with the period tracker's
  own history.
- "Profil bearbeiten" moved to the top-left of the main column.
- Same-sides toggle in the body-parts flow now uses the shared Toggle
  component.
2026-04-23 11:08:41 +02:00
Alexander ae8c699640 feat(fitness): forearm measurement illustration + hips asset refresh
CI / update (push) Successful in 3m56s
Adds a forearm SVG in the same currentColor-stroked style as thigh.svg and
wires it into both the body-parts wizard and BODY_PART_CARDS so the step
no longer falls back to the ruler placeholder. Also refreshes the hips PNG.
2026-04-22 01:35:10 +02:00
Alexander a056618696 fix(fitness): request ACTIVITY_RECOGNITION for cadence
Android step detector silently returns no events on API 29+
when ACTIVITY_RECOGNITION is ungranted, so cadence was always
absent from recorded tracks. Declare the permission, request
it at GPS start, guard sensor registration and retry it from
MainActivity.onRequestPermissionsResult when the user grants
mid-session, and toast a hint if they deny.
2026-04-21 19:21:25 +02:00
Alexander cf5ac96fc3 feat(fitness): download GPX from history detail
Export each cardio exercise's stored GPS track from the history
detail page. Cadence is emitted per-point via Garmin's
TrackPointExtension v1 so Strava/Garmin Connect preserve it.
Filename: YYYY-MM-DD-<workout> <mins>min <Activity>.gpx.
2026-04-21 18:53:19 +02:00
Alexander 5b35c9e63b feat(cospend): edit name and amount in list edit modal
Long-press modal on /cospend/list now lets you change the item's name
and quantity (e.g. "500g", "3x") alongside category and icon. The
quantity is re-prepended to the name so the existing parser keeps
picking it up.
2026-04-21 16:41:21 +02:00
Alexander 67700c0e75 fix(faith): resolve bible TSVs relative to module, not CWD
CI / update (push) Successful in 21s
Server runs from build output dir where CWD-relative `static/*.tsv`
misses — adapter-node ships static assets at build/client/. New
resolveStaticAsset() helper uses import.meta.url to find the bundled
location, falls back to <cwd>/static/ in dev.

Fixes ENOENT on drb.tsv/allioli.tsv after deploy.
2026-04-21 14:25:40 +02:00
Alexander 5b7f23b8be feat(fitness): surface period projection on stats page
PeriodTracker gains an optional mode prop ('entry' | 'projection' |
'full') that gates which sections render. The measure page keeps the
full tracker for the user's own cycle (logging plus calendar). The
stats page now mirrors it in projection mode and is the sole home
for shared cycles, which used to clutter the measure page.
2026-04-21 12:53:11 +02:00
Alexander 56d438631b feat(fitness): tinted body-part icons with semantic accents
Unifies PNG and SVG body-part images behind a single CSS-mask
render path, so both now colorize with a --accent CSS variable.
Accent splits by measurement type: --blue for proportion parts
(chest, shoulders, waist, hips) and --nord8 for muscle parts
(neck, biceps, forearms, thighs, calves). Stats cards gain a
matching 8%-tint fill and accent-colored hover border. History
page header image enlarged. Thigh SVG stroke-width bumped to 11
for better mask legibility.
2026-04-21 12:37:46 +02:00
Alexander 7f4b65f009 fix(faith): angelus slot selection sticks after praying
Replaced the auto-selecting $effect that was clobbering manual slot
picks with explicit init in onMount and advancement inside pray().
Selecting lunch/evening after praying morning now works.
2026-04-21 12:02:17 +02:00
Alexander 2087970f46 fix: clear build warnings across svelte components
Resolves 16 vite-plugin-svelte warnings: state_referenced_locally
(wrap prop reads in untrack), two a11y issues (ConfirmDialog tabindex
+ key handler, meal entry role/list), and nine unused CSS selectors.
Bump to 1.40.2.
2026-04-21 11:22:08 +02:00
Alexander 38824cc054 fix(faith): calendar-slug language swap + gate detail to 1962
- LanguageSelector: add kalender/calendar/calendarium mappings so swapping
  language from /glaube/kalender/... produces /faith/calendar/... instead
  of the broken /faith/kalender/... URL.
- HeroCard: move margin-bottom off the anchor so the plain (1969) variant
  keeps the same bottom spacing as the linked (1962) variant.
- Calendar overview: omit detail href on 1969 so the hover chevron /
  hover elevation don't appear when no detail page exists.
- Detail route: 404 any /detail/... under the 1969 rite — only 1962 has
  day-detail pages.
2026-04-21 11:10:32 +02:00
Alexander 57cd16085c feat(faith): Bible fallback for 1962 propers with propers view toggle
- Auto-fill missing vernacular propers from Allioli (DE) or DRB (EN)
  when the 1962 missal bundle lacks a translation, mapped per Latin slot
  via romcal's scriptureRef blocks (compound refs split 1-to-1 when
  segment count matches slot count).
- Strip Psalm superscriptions and trailing periods so lookups parse and
  Bible text aligns with the Latin antiphon.
- Localize the section reference header (Marc → Mk, Vulgate→Hebrew
  psalm shift for DE) instead of showing raw Latin.
- Add Latin / Parallel / Vernacular view toggle with localStorage
  persistence; hide Allioli/DRB badge in Latin-only view.
- Latin column now takes primary text color; vernacular secondary,
  matching the Prayer.svelte convention.
2026-04-21 11:01:45 +02:00
Alexander fd4753905e refactor(ui): drop redundant page headers, rework measure profile UX
Removes decorative route-label h1s across fitness, recipe and cospend
pages — replaced with sr-only h1s for assistive tech and a shared
.sr-only utility in app.css. On the measure page, the tucked-away
profile chip becomes a dismissible setup banner that only appears
when sex/height/birth year are missing, with a permanent "Edit profile"
link at the foot of the page.
2026-04-21 09:15:32 +02:00
Alexander d93006a319 refactor(ui): SaveFab shares ActionButton shell
ActionButton now renders as <a> (href) or <button> (onclick), so
SaveFab wraps it to inherit the shake/hover/focus behavior already
used by AddButton/EditButton. Body-parts review replaces its inline
save button with SaveFab for consistency.
2026-04-21 08:48:26 +02:00
Alexander c45585baa5 feat(fitness): body-part cards link to per-part history pages
Latest measurements render as a 3x3 card grid (vertical mobile,
horizontal on ≥768px) linking to `/fitness/{measure}/{history}/{part}`
with summary stats + chart. Slugs localize (hals, oberschenkel, …)
when on the German route.
2026-04-21 08:32:08 +02:00
Alexander 2b1a415ab6 perf(faith): warm liturgical cache + fix 1962 rendering
CI / update (push) Successful in 3m59s
Pre-compute romcal year maps on server boot for current + next civil year
across en/de/la in each rite's default diocese, non-blocking so startup is
unaffected.

Also fixes several 1962-rite rendering bugs: commemorations previously
leaked 1969-shape ids (e.g. andrew_apostle) next to proper 1962 sancti;
station church names came through unresolved because RomcalConfig's
internal i18next has no bundle loaded; season names arrived as raw keys
(advent.season) for the same reason. All three now resolve locally via
the shipped 1962 bundle with Latin as fallback. ClassIV ferias get a
small dot on the grid.
2026-04-21 08:04:20 +02:00
Alexander dd612f6535 fix(fitness): persist activityType when picking GPS activity
The active-workout activity picker updated local state + workout.name
but never synced workout.activityType, so every hiking/walking/cycling
workout was saved with an inner exercise of 'running'. That silently
applied the wrong kcal model — running Minetti for hiking/walking
(~43% overestimate) and running instead of the cycling physics model
(large overestimate for cycling).

Adds an activityType setter on the workout store and invokes it from
selectActivity() so the saved session's exercise matches the picked
activity.
2026-04-21 07:54:31 +02:00
Alexander ad154bf914 fix(errors): surface Bible verses on section error pages
SvelteKit's handleError hook is skipped for expected `error()` throws,
so verses set there never reached `$page.error` for server-thrown 404s
and auth denials. Introduce `errorWithVerse()` in `$lib/server/errorQuote`
that fetches a random verse first, then throws `error(status, body)`
with `{ message, bibleQuote, lang }`, making the quote available in
every `SectionError`. Convert all page load throws (catchalls, layout
validators, calendar, prayers, recipes, fitness, cospend, admin) and
hooks.server auth gates to the helper. Add `src/error.html` as a
branded last-resort fallback.
2026-04-20 22:39:39 +02:00
Alexander fbd09fbdae refactor(errors): redesign error pages in editorial style
Replace the emoji/gradient card with an editorial layout: small lucide
glyph, oversized error code, hairline-divided serif bible quote.

Extract shared ErrorView + SectionError components and a bilingual
string helper. Add +error.svelte at each section root (faith, recipes,
fitness, tasks, cospend) so errors render inside the correct layout and
inherit the section-specific header/nav. Catch-all [...rest]/+page.ts
stubs route unmatched URLs through the section layout so the right
error page catches them.
2026-04-20 19:54:33 +02:00
Alexander 3aa8f38f92 fix(faith): locate romcal root by walking up from main entry
romcal's package.json is not listed in its exports map, so
require.resolve('romcal/package.json') throws ERR_PACKAGE_PATH_NOT_EXPORTED
under Node's strict exports enforcement. Resolve the main entry instead
and walk up until we find the package.json belonging to romcal itself.
2026-04-20 16:50:12 +02:00
Alexander f447520aaa fix(faith): resolve 1962 bundles via createRequire and add deploy pre-push hook
CI / update (push) Successful in 1m27s
romcal's 1962 bundle files live outside the package's exports map and were
being loaded via a cwd-relative path. Under systemd the server runs with
cwd /usr/share/webapps/homepage/dist/, so node_modules/romcal/... resolved
against dist/ and hit ERR_MODULE_NOT_FOUND. Switch to createRequire +
require.resolve('romcal/package.json') so the bundle path is anchored to
the actual package root regardless of cwd.

Also track scripts/hooks/pre-push which runs scripts/deploy.sh after a
master push to origin. Git has no native post-push hook; pre-push is the
closest client-side equivalent — if deploy fails the push is aborted.
Install with: ln -sf ../../scripts/hooks/pre-push .git/hooks/pre-push
2026-04-20 16:44:59 +02:00
Alexander 9c119b8df2 feat(fitness): add guided body-parts measurement wizard
New /fitness/measure/body-parts route with step-by-step tape-measure
flow, per-step tips, L/R paired inputs, inline history chart, and
review-before-save summary. Measure page replaces the old body-parts
accordion with a launch button. Fitness layout goes full-bleed and
hides the site footer for this route via a :has() attribute selector,
and the desktop 3-column grid now extends rail and side panel up past
the floating nav while the middle column compensates with padding.
2026-04-20 16:28:09 +02:00
Alexander b2b69301aa feat(fitness): add how-to-measure hints to body-parts form
Inline italic captions under each single-sided input (neck, shoulders,
chest, waist, hips) and row-spanning captions with a primary accent bar
under each paired row (biceps, forearms, thighs, calves) — one tip per
pair, not per side. EN + DE copy.
2026-04-20 13:42:31 +02:00
Alexander e37d41b180 feat(faith): adopt flat-id romcal fork and simplify 1962 calendar rendering
Switch the romcal dependency to AlexBocken/romcal (monorepo fork with
collapsed bucket prefixes) and strip the runtime prefix-fallback chain
from liturgicalCalendar.ts — name/propers lookups now use a single
flat id. The 1962 data model shrinks to just what the rendering uses
(commem {id,name}, detail carrying propers as {key, la[], local[]})
and the detail + overview pages drop the rubrics/octave/properSource
fields that never got wired in.
2026-04-18 22:28:48 +02:00
Alexander e036588795 feat(faith): animate ring rotation and restyle calendar month/detail
- Rotate the ring smoothly to put the selected day under a static vertical
  needle pin; pivot uses a shortest-arc Tween, respects prefers-reduced-motion,
  and falls back to today when no selection. Pin + bar cross-fade color in
  lockstep (650ms cubicOut) to the selected day's liturgical color (gold when
  selected == today).
- Split the overview into an inline hero (selected day) and a dedicated
  /detail/{yyyy}/{mm}/{dd} route that opens on hero click; drop the old
  inline detail block.
- Restyle the month grid to a minimalist card-grid: taupe feria fills,
  rounded cells, gold today-ring + dot, Roman-numeral rank badges, and
  equal-width columns via minmax(0, 1fr) so long feast names no longer
  stretch a column.
- Default the calendar view to the ring, reorder the view switcher
  (ring first), and match hero-card color transition to the ring timing.
- Extract shared calendar types to $lib/calendarTypes.ts and server helpers
  to $lib/server/liturgicalCalendar.ts so the overview + detail routes share
  one source of truth. Bump romcal dep to the dev branch, alias the Swiss
  1969 bundle so its exports resolve.
- Bump version to 1.35.0.
2026-04-18 17:54:41 +02:00
Alexander 2970dc0451 feat(faith): render 1962 Mass propers with scripture refs and Bible fallback
Show propers text for each 1962 celebration with scripture reference pills
grouping each block. When a translated proper is missing, fall back to the
local-language Bible (Douay-Rheims for en, Allioli for de), showing a note
above the translated column. Handles multi-segment refs (e.g. "Ps 118:85;
118:46") with inherited book/chapter, and shifts Vulgate→Hebrew psalm
numbering for Allioli.

Also restructures date navigation as folder-based optional params
(/yyyy/mm/dd) with the rite forced as a required path segment so day/month
navigation stays within the active rite.
2026-04-14 22:44:03 +02:00
Alexander af0f50abb6 feat(stickers): expand blobcat catalog with Tirifto set and drop mismatched art
Replace four tastytea-style SVGs (blanket, hammer, teapot, heart_tastytea)
whose 800px viewBox clashed with the 33.866666 viewBox of the rest, and add
the full Tirifto gutkato_ set. Catalog grows from 54 to 113 positive stickers
(new foods/drinks, rose and cat colors, professions, cute expressions, signs).
2026-04-13 18:56:25 +02:00
Alexander 4dba5ea4fd feat(nutrition): reactive quick-log recents and favorites
Derive recents from current-day entries merged with historical
server data so logging a food updates the quick-log bar instantly.
FoodSearch now emits onfavoritechange so toggling a heart keeps
the quick-log favorites tab in sync without reloading.
2026-04-13 09:52:31 +02:00
Alexander 465bd9b25a feat(fitness): replace ExercisePicker dropdowns with icon pills
Align muscle group and equipment filters in the exercise picker
with the rest of the fitness UI — horizontal pill scrollers, multi-
select, with Lucide icons for each equipment type.
2026-04-13 09:52:25 +02:00
Alexander 7c3e90933d feat(fitness): support repeat groups in interval training templates
Allow users to nest steps inside a repeat group (e.g. "5×: 30s sprint,
60s recovery") when building GPS interval templates. Groups are tagged
{ type: 'group', repeat, steps } alongside flat { type: 'step' } entries
and capped at one nesting level. Entries are expanded into a flat list
before handing off to the native Android TTS/interval service, so the
runtime state machine is unchanged.
2026-04-13 09:17:55 +02:00
Alexander 0ef8ee38c5 feat(fitness): classify exercises by type and redesign filter UI
Distinguish stretches, strength, cardio, and plyometric exercises
with a curated `exerciseType` field that overrides mislabels in
ExerciseDB for the hand-curated stretch set. Surface the type as
pills on the detail page and as a filter toggle on the list page.

Replace the muscle-group and equipment dropdowns with horizontally
scrolling pill rows; equipment pills carry Lucide icons, and the
type toggle shows a flexed-bicep / person / layers glyph. Selected
muscle groups hoist to the front of the scroller.
2026-04-13 08:59:12 +02:00
Alexander 4692bf9bf7 feat(recipes): redesign cake-form and baking info with collapsible card pattern
Viewer: cake-form adjust now collapses into a summary trigger with live
factor badge; shape picker replaced with icon-only tiles that flex to
fill the row; numeric inputs gain inline cm suffix; restore-default link
appears when user deviates from the default. Editor: default-Backform
config mirrors the same card + tile pattern (adds "none" tile), plus
inline cm suffixes. Baking info row in instruction editor becomes a
click-to-reveal card with summary chips, mode presets, and editable
fields behind the chevron.
2026-04-12 21:46:51 +02:00
Alexander f108e9ceaa feat: align recipe edit page with viewer design
Replace CardAdd with EditTitleImgParallax so /rezepte/edit/[name]
mirrors the hero-parallax layout of /rezepte/[name]. Add Lucide icons
and a width-constrained info-card grid to CreateStepList's additional
info section. Refactor the English translation view to use semantic
theme vars, side-by-side German/English field comparison, and a card
aesthetic matching the rest of the site.

Fix portions binding bug where the shared portions store was being
written by both language ingredient lists. CreateIngredientList now
accepts a bindable portions prop; the English list uses it with
useStore=false to stay isolated from the German value.
2026-04-12 21:22:53 +02:00
Alexander 69c2e05462 feat: add tactile haptic feedback to rosary prayer cards
Every prayer card now vibrates on tap — non-decade cards advance to the
next section, decade cards increment the Ave Maria counter with auto-scroll
at 10. Two profiles (bead vs card) give distinct tactile feel; the 10th
bead fires the heavier card haptic to mark decade completion.

Native Android path via AndroidBridge.forceVibrate uses VibrationAttributes
USAGE_ACCESSIBILITY so vibration bypasses silent / Do-Not-Disturb inside
the Tauri app. Browser falls back to the web-haptics npm package. Haptic
fires on pointerdown with touch-action: manipulation for near-zero tap
latency; state change stays on click so scroll gestures don't advance.

- Remove CounterButton (whole card is now the tap target)
- Replace emoji with Lucide BookOpen icon, restyle citation as an
  understated inline typographic link (no background chip)
- Drop decade min-height leftover from the pre-auto-advance layout

Bumps site to 1.27.0 and Tauri app to 0.5.0 (new Android capability).
2026-04-12 20:15:40 +02:00
Alexander cfd1d953fb fix: adjust LinksGrid nth-child offsets for earlier, more frequent color pops
Shift pop-b and pop-c selectors so accent colors appear sooner in the
grid and the light/white pop-c repeats more frequently (every 5th
instead of every 7th item).
2026-04-11 21:34:23 +02:00