Build the SvelteKit frontend: calm home with mood modes
- New frontend/ SvelteKit static SPA (Svelte 5), served by FastAPI from frontend/build (falls back to the legacy page if unbuilt). - Calm design system: cream/sage palette, serif headlines, generous space, no urgency colors, gentle motion (respects prefers-reduced-motion). - Home screen: mood-mode nav (Today/Wonder/People Helping/Solutions/Light Only/Grounded), the daily brief as a hero + remaining four, browsable mood lanes, an explicit calm end-state, inline Not today / Less like this / Hide affordances, and device-local Calm Filters mirroring goodnews/filters.py. - Backend: moods.py + GET /api/moods (single source of truth for the modes); FilterPrefs gains max_cortisol/max_ragebait ceilings (for Light Only). - Push categorical filters (include/mute topics+flavors, ceilings) into SQL in queries.feed so low-ranked-but-matching items (e.g. discovery for Wonder) are not truncated by ranking; only avoid-terms stay a Python pass. - PWA manifest + icon (installable; offline deferred per plan). - Multi-stage Dockerfile builds the site then serves it from the API. - Tests: queries.feed categorical filters (63 total). README updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
"""Mood modes — the humane front door over the topic/flavor taxonomy.
|
||||
|
||||
A reader thinks "I want wonder," not "animals/discovery". Each mood resolves to
|
||||
a filter preset (include_topics / include_flavors / a cortisol ceiling) that the
|
||||
feed already understands via FilterPrefs. Topic/flavor remain available as the
|
||||
secondary "browse more precisely" controls; moods don't replace them.
|
||||
|
||||
Single source of truth so the website and any future companion app agree.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# "today" is special: it has no filter — it's the daily brief view.
|
||||
MOODS: list[dict] = [
|
||||
{
|
||||
"key": "today",
|
||||
"label": "Today",
|
||||
"description": "The day's five good things.",
|
||||
"filter": {},
|
||||
},
|
||||
{
|
||||
"key": "wonder",
|
||||
"label": "Wonder",
|
||||
"description": "Awe and discovery.",
|
||||
"filter": {"include_topics": ["science", "animals", "culture"], "include_flavors": ["discovery"]},
|
||||
},
|
||||
{
|
||||
"key": "people-helping",
|
||||
"label": "People Helping",
|
||||
"description": "Community, kindness, and repair.",
|
||||
"filter": {"include_topics": ["community"], "include_flavors": ["solution", "feelgood"]},
|
||||
},
|
||||
{
|
||||
"key": "solutions",
|
||||
"label": "Solutions",
|
||||
"description": "Problems being solved.",
|
||||
"filter": {
|
||||
"include_topics": ["environment", "community", "health"],
|
||||
"include_flavors": ["solution", "breakthrough"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"key": "light",
|
||||
"label": "Light Only",
|
||||
"description": "Just the gentle stuff.",
|
||||
"filter": {"include_flavors": ["feelgood", "discovery"], "max_cortisol": 2},
|
||||
},
|
||||
{
|
||||
"key": "grounded",
|
||||
"label": "Grounded",
|
||||
"description": "Useful, calm perspective.",
|
||||
"filter": {"include_flavors": ["perspective", "solution"]},
|
||||
},
|
||||
]
|
||||
|
||||
_BY_KEY = {m["key"]: m for m in MOODS}
|
||||
|
||||
|
||||
def mood_filter(key: str) -> dict:
|
||||
"""Return the filter preset for a mood key (empty dict if unknown/today)."""
|
||||
mood = _BY_KEY.get(key)
|
||||
return dict(mood["filter"]) if mood else {}
|
||||
Reference in New Issue
Block a user