Home: Today shows only the five; categories behind mood selection; big view heading

- Today is just the day's five highlights (hero + four) — the preview lanes are
  gone; other categories appear only when their mood is selected.
- Each view leads with a large serif heading (Today / Wonder / ...) and a
  subtitle, with a quiet sage rule — switching moods retitles the page.
- Drop the now-unused Lane usage from the home.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-05-31 00:22:34 +00:00
parent e8c4bb6e8b
commit 06c2704ae0
2 changed files with 66 additions and 54 deletions
+53 -52
View File
@@ -3,7 +3,6 @@
import { getJSON } from '$lib/api.js';
import * as P from '$lib/prefs.js';
import MoodNav from '$lib/components/MoodNav.svelte';
import Lane from '$lib/components/Lane.svelte';
import ArticleCard from '$lib/components/ArticleCard.svelte';
import BoundariesPanel from '$lib/components/BoundariesPanel.svelte';
@@ -11,18 +10,25 @@
let selected = $state('today');
let brief = $state(null);
let feed = $state([]);
let lanes = $state([]);
let userPrefs = $state(P.blank());
let showBoundaries = $state(false);
let loading = $state(true);
let error = $state('');
let filtersOn = $derived(P.active(userPrefs));
const moodByKey = (k) => moods.find((m) => m.key === k);
let current = $derived(moods.find((m) => m.key === selected));
let viewLabel = $derived(current?.label ?? 'Today');
let viewSubtitle = $derived(
selected === 'today'
? brief?.brief_date
? `Five good things · ${brief.brief_date}`
: 'Five good things today'
: (current?.description ?? '')
);
function feedUrl(moodKey, limit) {
const merged = P.merge(userPrefs, moodByKey(moodKey)?.filter ?? {});
const mood = moods.find((m) => m.key === moodKey);
const merged = P.merge(userPrefs, mood?.filter ?? {});
const q = P.param(merged);
return `/api/feed?limit=${limit}${q ? '&' + q : ''}`;
}
@@ -31,23 +37,14 @@
return `/api/brief?limit=5${q ? '&' + q : ''}`;
}
async function loadToday() {
brief = await getJSON(briefUrl());
const keys = ['wonder', 'people-helping', 'solutions'];
lanes = await Promise.all(
keys.map(async (k) => ({ ...moodByKey(k), items: (await getJSON(feedUrl(k, 4))).items }))
);
}
async function loadMood(key) {
feed = (await getJSON(feedUrl(key, 24))).items;
}
async function select(key) {
selected = key;
error = '';
try {
if (key === 'today') await loadToday();
else await loadMood(key);
// Today = just the day's highlights. Other moods reveal that category only
// when chosen (categories live behind their selection, not on the home).
if (key === 'today') brief = await getJSON(briefUrl());
else feed = (await getJSON(feedUrl(key, 24))).items;
} catch (e) {
error = 'Something went quiet — could not reach the feed.';
}
@@ -94,43 +91,39 @@
<p class="muted center pad">Gathering the good news…</p>
{:else if error}
<p class="muted center pad">{error}</p>
{:else if selected === 'today'}
{#if brief?.items?.length}
<section class="rise">
<h2 class="kicker">{brief.title ?? 'Five Good Things Today'}</h2>
<ArticleCard article={brief.items[0]} hero onaction={applyAction} />
{#if brief.items.length > 1}
<div class="grid rest">
{#each brief.items.slice(1) as a (a.id)}
<ArticleCard article={a} onaction={applyAction} />
{/each}
</div>
{/if}
</section>
{:else}
<p class="muted center pad">No brief yet today — try a calmer filter, or check back soon.</p>
{/if}
{#each lanes as lane (lane.key)}
<Lane title={lane.label} description={lane.description} items={lane.items}
onaction={applyAction} onmore={() => select(lane.key)} />
{/each}
<p class="endcap rise">✦ that's the good news for today ✦</p>
{:else}
<section class="rise">
<h2 class="kicker">{moodByKey(selected)?.label}</h2>
<p class="muted lede">{moodByKey(selected)?.description}</p>
{#if feed.length}
<div class="grid">
{#key selected}
<header class="view-head rise">
<h1>{viewLabel}</h1>
{#if viewSubtitle}<p class="sub">{viewSubtitle}</p>{/if}
</header>
{#if selected === 'today'}
{#if brief?.items?.length}
<section class="rise">
<ArticleCard article={brief.items[0]} hero onaction={applyAction} />
{#if brief.items.length > 1}
<div class="grid rest">
{#each brief.items.slice(1) as a (a.id)}
<ArticleCard article={a} onaction={applyAction} />
{/each}
</div>
{/if}
</section>
<p class="endcap rise">✦ that's the good news for today ✦</p>
{:else}
<p class="muted center pad">No brief yet today — try a calmer filter, or check back soon.</p>
{/if}
{:else if feed.length}
<div class="grid rise">
{#each feed as a (a.id)}
<ArticleCard article={a} onaction={applyAction} />
{/each}
</div>
{:else}
<p class="muted center pad">Nothing in this lane right now — try another mood or ease a filter.</p>
<p class="muted center pad">Nothing in this mood right now — try another, or ease a boundary.</p>
{/if}
</section>
{/key}
{/if}
<style>
@@ -141,11 +134,19 @@
}
.boundaries:hover { color: var(--sage-deep); }
.boundaries.on { color: var(--sage-deep); font-weight: 600; }
.kicker {
font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.12em;
color: var(--gold); margin: 22px 0 14px; font-family: var(--sans); font-weight: 700;
.view-head { margin: 20px 0 20px; }
.view-head h1 {
font-size: clamp(2.1rem, 5.5vw, 2.8rem);
line-height: 1.05;
}
.lede { margin: -8px 0 18px; font-size: 1.05rem; }
.view-head .sub { margin: 8px 0 0; color: var(--muted); font-size: 1.02rem; }
/* a quiet sage rule under the heading */
.view-head::after {
content: ''; display: block; width: 46px; height: 3px;
background: var(--sage); border-radius: 2px; margin-top: 14px; opacity: 0.8;
}
.rest { margin-top: 18px; }
.center { text-align: center; }
.pad { padding: 48px 0; }