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:
@@ -3,7 +3,6 @@
|
|||||||
import { getJSON } from '$lib/api.js';
|
import { getJSON } from '$lib/api.js';
|
||||||
import * as P from '$lib/prefs.js';
|
import * as P from '$lib/prefs.js';
|
||||||
import MoodNav from '$lib/components/MoodNav.svelte';
|
import MoodNav from '$lib/components/MoodNav.svelte';
|
||||||
import Lane from '$lib/components/Lane.svelte';
|
|
||||||
import ArticleCard from '$lib/components/ArticleCard.svelte';
|
import ArticleCard from '$lib/components/ArticleCard.svelte';
|
||||||
import BoundariesPanel from '$lib/components/BoundariesPanel.svelte';
|
import BoundariesPanel from '$lib/components/BoundariesPanel.svelte';
|
||||||
|
|
||||||
@@ -11,18 +10,25 @@
|
|||||||
let selected = $state('today');
|
let selected = $state('today');
|
||||||
let brief = $state(null);
|
let brief = $state(null);
|
||||||
let feed = $state([]);
|
let feed = $state([]);
|
||||||
let lanes = $state([]);
|
|
||||||
let userPrefs = $state(P.blank());
|
let userPrefs = $state(P.blank());
|
||||||
let showBoundaries = $state(false);
|
let showBoundaries = $state(false);
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
let error = $state('');
|
let error = $state('');
|
||||||
|
|
||||||
let filtersOn = $derived(P.active(userPrefs));
|
let filtersOn = $derived(P.active(userPrefs));
|
||||||
|
let current = $derived(moods.find((m) => m.key === selected));
|
||||||
const moodByKey = (k) => moods.find((m) => m.key === k);
|
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) {
|
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);
|
const q = P.param(merged);
|
||||||
return `/api/feed?limit=${limit}${q ? '&' + q : ''}`;
|
return `/api/feed?limit=${limit}${q ? '&' + q : ''}`;
|
||||||
}
|
}
|
||||||
@@ -31,23 +37,14 @@
|
|||||||
return `/api/brief?limit=5${q ? '&' + q : ''}`;
|
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) {
|
async function select(key) {
|
||||||
selected = key;
|
selected = key;
|
||||||
error = '';
|
error = '';
|
||||||
try {
|
try {
|
||||||
if (key === 'today') await loadToday();
|
// Today = just the day's highlights. Other moods reveal that category only
|
||||||
else await loadMood(key);
|
// 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) {
|
} catch (e) {
|
||||||
error = 'Something went quiet — could not reach the feed.';
|
error = 'Something went quiet — could not reach the feed.';
|
||||||
}
|
}
|
||||||
@@ -94,10 +91,16 @@
|
|||||||
<p class="muted center pad">Gathering the good news…</p>
|
<p class="muted center pad">Gathering the good news…</p>
|
||||||
{:else if error}
|
{:else if error}
|
||||||
<p class="muted center pad">{error}</p>
|
<p class="muted center pad">{error}</p>
|
||||||
{:else if selected === 'today'}
|
{:else}
|
||||||
|
{#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}
|
{#if brief?.items?.length}
|
||||||
<section class="rise">
|
<section class="rise">
|
||||||
<h2 class="kicker">{brief.title ?? 'Five Good Things Today'}</h2>
|
|
||||||
<ArticleCard article={brief.items[0]} hero onaction={applyAction} />
|
<ArticleCard article={brief.items[0]} hero onaction={applyAction} />
|
||||||
{#if brief.items.length > 1}
|
{#if brief.items.length > 1}
|
||||||
<div class="grid rest">
|
<div class="grid rest">
|
||||||
@@ -107,30 +110,20 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
<p class="endcap rise">✦ that's the good news for today ✦</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="muted center pad">No brief yet today — try a calmer filter, or check back soon.</p>
|
<p class="muted center pad">No brief yet today — try a calmer filter, or check back soon.</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else if feed.length}
|
||||||
{#each lanes as lane (lane.key)}
|
<div class="grid rise">
|
||||||
<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">
|
|
||||||
{#each feed as a (a.id)}
|
{#each feed as a (a.id)}
|
||||||
<ArticleCard article={a} onaction={applyAction} />
|
<ArticleCard article={a} onaction={applyAction} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
</section>
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -141,11 +134,19 @@
|
|||||||
}
|
}
|
||||||
.boundaries:hover { color: var(--sage-deep); }
|
.boundaries:hover { color: var(--sage-deep); }
|
||||||
.boundaries.on { color: var(--sage-deep); font-weight: 600; }
|
.boundaries.on { color: var(--sage-deep); font-weight: 600; }
|
||||||
.kicker {
|
|
||||||
font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.12em;
|
.view-head { margin: 20px 0 20px; }
|
||||||
color: var(--gold); margin: 22px 0 14px; font-family: var(--sans); font-weight: 700;
|
.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; }
|
.rest { margin-top: 18px; }
|
||||||
.center { text-align: center; }
|
.center { text-align: center; }
|
||||||
.pad { padding: 48px 0; }
|
.pad { padding: 48px 0; }
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
- Ability to silence some categories temporarily (Maybe a user doesn't even want to see health-related articles, even good ones, so they're not reminded of an ongoing medical issue -- a way to avoid something purposely for a bit)
|
| = tabled for now
|
||||||
- Terms to avoid list (To filter even good news that you'd rather not hear about)
|
- = Yet to do
|
||||||
|
* = completed item
|
||||||
|
|
||||||
|
|
||||||
|
* Ability to silence some categories temporarily (Maybe a user doesn't even want to see health-related articles, even good ones, so they're not reminded of an ongoing medical issue -- a way to avoid something purposely for a bit) [done: pause topics/flavors in Boundaries]
|
||||||
|
* Terms to avoid list (To filter even good news that you'd rather not hear about) [done: avoid words/phrases in Boundaries]
|
||||||
- Favorite/save articles
|
- Favorite/save articles
|
||||||
|
- Soothing background colors/gradients per each category as you scroll. Maybe a user preference.
|
||||||
|
- I really like the coloring for the metadata highlighting in each card (The grading bubbles)
|
||||||
|
- Some articles are behind paywalls.. what can we do?
|
||||||
|
- After an article is read, can we add a refresh button to fetch a replacement for it in the list?
|
||||||
|
* I want the top 5 to be tere, but I want the remaining categories to be hidden behing their selections. So the main screen should show just the current highlights, and then the other articles should only be visible when in that category. [done]
|
||||||
|
* Title headings should be a little larger -- if you select Today, Today should look like a proper heading, bold and beautiful. Switching to Wondow should show "Wonder" all nice and whatnot. [done]
|
||||||
Reference in New Issue
Block a user