Small joys: wire homepage rail to live data + rich pages (/word /quote /onthisday) + admin
- /home3: small-joys rail now reads live /api/word|quote|onthisday/today (placeholders only
as fallback); each cell links to its detail page.
- HubShell component (shared bar/footer/fonts/tokens) for the hub + detail pages.
- /word: big word, IPA, Listen (cached clip + browser-TTS fallback), definition, sentences.
- /quote: the quote, attribution, and the AI "what it means".
- /onthisday: the date, year + fact, image, summary, source.
- Admin "Small Joys" tab: per-pool list with feature/block/delete/add + re-pick, for all
three kinds. New admin API: GET/POST /api/admin/joys/{kind}[/{id}|/add|/repick].
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { getJSON } from '$lib/api.js';
|
||||
import HubShell from '$lib/components/HubShell.svelte';
|
||||
|
||||
let q = $state(null);
|
||||
let state = $state('loading');
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
q = await getJSON('/api/quote/today');
|
||||
state = q ? 'ready' : 'empty';
|
||||
} catch {
|
||||
state = 'empty';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Quote of the Day · upbeatBytes</title>
|
||||
<meta name="description" content="A hopeful quote every day — with its source and a plain-language take on what it means." />
|
||||
</svelte:head>
|
||||
|
||||
<HubShell active="">
|
||||
<article class="quote-page">
|
||||
{#if state === 'ready'}
|
||||
<p class="eyebrow">Quote of the day</p>
|
||||
<blockquote class="quote">
|
||||
<span class="glyph" aria-hidden="true">“</span>
|
||||
<p class="text">{q.text}</p>
|
||||
<footer class="attrib">
|
||||
<span class="dash"></span>
|
||||
<span class="by">{q.author}{#if q.work}, <span class="work">{q.work}</span>{/if}</span>
|
||||
</footer>
|
||||
</blockquote>
|
||||
|
||||
{#if q.meaning}
|
||||
<section class="meaning">
|
||||
<h2>What it means</h2>
|
||||
<p>{q.meaning}</p>
|
||||
</section>
|
||||
{/if}
|
||||
{:else if state === 'empty'}
|
||||
<p class="note">Today's quote is on its way. Check back soon.</p>
|
||||
{:else}
|
||||
<p class="note">Finding today's quote…</p>
|
||||
{/if}
|
||||
</article>
|
||||
</HubShell>
|
||||
|
||||
<style>
|
||||
.quote-page { max-width: 720px; margin: 0 auto; }
|
||||
.eyebrow {
|
||||
font-size: 12px; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase;
|
||||
color: #b06a86; margin: clamp(8px, 3vw, 28px) 0 0; text-align: center;
|
||||
}
|
||||
.quote { margin: clamp(18px, 4vw, 34px) 0 0; position: relative; text-align: center; }
|
||||
.glyph {
|
||||
font-family: 'Newsreader', Georgia, serif; font-size: clamp(4rem, 11vw, 7rem); line-height: 0.6;
|
||||
color: rgba(176, 106, 134, 0.22); display: block; height: clamp(2rem, 5vw, 3rem);
|
||||
}
|
||||
.quote .text {
|
||||
font-family: 'Newsreader', Georgia, serif; font-style: italic; font-weight: 500;
|
||||
font-size: clamp(1.6rem, 4.5vw, 2.6rem); line-height: 1.28; letter-spacing: -0.01em;
|
||||
color: #2f2240; margin: 8px 0 0;
|
||||
}
|
||||
.attrib { display: flex; align-items: center; justify-content: center; gap: 12px; margin-top: clamp(22px, 4vw, 32px); }
|
||||
.dash { width: 30px; height: 1px; background: #c9a6ba; }
|
||||
.by { font-family: 'Newsreader', Georgia, serif; font-size: 1.1rem; color: #7c64a0; }
|
||||
.work { font-style: italic; }
|
||||
|
||||
.meaning {
|
||||
margin: clamp(36px, 6vw, 56px) auto 0; max-width: 600px;
|
||||
border-top: 1px solid #ecdce4; padding-top: 26px;
|
||||
}
|
||||
.meaning h2 {
|
||||
font-size: 12px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase;
|
||||
color: var(--muted); margin: 0 0 12px; text-align: center;
|
||||
}
|
||||
.meaning p { font-size: clamp(1.05rem, 2vw, 1.2rem); line-height: 1.6; color: #4a4255; margin: 0; text-align: center; }
|
||||
|
||||
.note { text-align: center; color: var(--muted); font-size: 1.05rem; margin-top: 60px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user