home3: small-joys rail (WOTD/QOTD crafted + 'good thing today' SOON), whole-word headline clamp, 16px gaps

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-22 10:18:00 -04:00
parent 2173802c70
commit 3c519a5f50
+42 -22
View File
@@ -8,6 +8,15 @@
let news = $state(null); // {id, title, summary, image} let news = $state(null); // {id, title, summary, image}
let art = $state(null); // {title, artist, year, image} let art = $state(null); // {title, artist, year, image}
// headline truncated on a whole-word boundary (no mid-word ellipsis)
function clampWords(s, max = 48) {
if (!s || s.length <= max) return s;
const cut = s.slice(0, max);
const i = cut.lastIndexOf(' ');
return (i > 0 ? cut.slice(0, i) : cut).replace(/[\s,;:.]+$/, '') + '…';
}
let headline = $derived(clampWords(news?.title ?? 'What went right this week: the good news that actually matters'));
onMount(async () => { onMount(async () => {
try { try {
const a = await getJSON('/api/art/today'); const a = await getJSON('/api/art/today');
@@ -61,7 +70,7 @@
<div class="news-photo" style={news?.image ? `background-image:url(${news.image})` : ''}></div> <div class="news-photo" style={news?.image ? `background-image:url(${news.image})` : ''}></div>
<div class="news-body"> <div class="news-body">
<span class="label" style="color:#0083ad">GOOD NEWS</span> <span class="label" style="color:#0083ad">GOOD NEWS</span>
<h2>{news?.title ?? 'What went right this week: the good news that actually matters'}</h2> <h2>{headline}</h2>
<p class="summary">{news?.summary || "We read the week so you don't have to doomscroll it. Five quietly hopeful stories, summarised to the gist."}</p> <p class="summary">{news?.summary || "We read the week so you don't have to doomscroll it. Five quietly hopeful stories, summarised to the gist."}</p>
<div class="news-foot"> <div class="news-foot">
<a class="link news-link" href={news?.id ? `/a/${news.id}` : '/'}>Read article</a> <a class="link news-link" href={news?.id ? `/a/${news.id}` : '/'}>Read article</a>
@@ -136,18 +145,23 @@
</div> </div>
</div> </div>
<!-- skinny Word / Quote of the day strip (placeholder content; real engine wired later) --> <!-- "small joys" rail: three evenly-weighted daily delights (placeholder content) -->
<div class="card wotd"> <div class="joys">
<div class="wotd-half"> <div class="joy joy-word">
<span class="label">WORD OF THE DAY</span> <span class="label">WORD OF THE DAY</span>
<span class="word">Serene</span> <p class="word-line"><span class="word">Serene</span> <span class="word-meta">adj · /səˈriːn/</span></p>
<span class="def">Calm, peaceful, and untroubled.</span> <span class="def">Calm, peaceful, and untroubled.</span>
</div> </div>
<div class="wotd-half"> <div class="joy joy-quote">
<span class="label">QUOTE OF THE DAY</span> <span class="label">QUOTE OF THE DAY</span>
<span class="quote">“Very little is needed to make a happy life.”</span> <span class="qglyph" aria-hidden="true"></span>
<span class="quote">Very little is needed to make a happy life.</span>
<span class="quote-by">Marcus Aurelius</span> <span class="quote-by">Marcus Aurelius</span>
</div> </div>
<div class="joy joy-fact">
<div class="joy-top"><span class="label">A GOOD THING TODAY</span><span class="soon">SOON</span></div>
<span class="fact">In 1928, penicillin was discovered by a happy accident.</span>
</div>
</div> </div>
</div> </div>
</main> </main>
@@ -201,12 +215,12 @@
.bento { .bento {
max-width: 1180px; width: 100%; margin: 0 auto; box-sizing: border-box; max-width: 1180px; width: 100%; margin: 0 auto; box-sizing: border-box;
padding: 0 clamp(18px, 5vw, 44px) 16px; padding: 0 clamp(18px, 5vw, 44px) 16px;
display: grid; grid-template-columns: minmax(0, 1.18fr) minmax(0, 1.82fr); gap: 18px; display: grid; grid-template-columns: minmax(0, 1.18fr) minmax(0, 1.82fr); gap: 16px;
} }
/* right column matches the News height; Art stays pinned to the TOP and the Play/Moment /* right column matches the News height; Art stays pinned to the TOP and the Play/Moment
pair to the BOTTOM, with the extra space distributed BETWEEN them (FIX1). The cards pair to the BOTTOM, with the extra space distributed BETWEEN them (FIX1). The cards
themselves keep their natural size and never stretch. */ themselves keep their natural size and never stretch. */
.rightcol { display: flex; flex-direction: column; gap: 18px; } .rightcol { display: flex; flex-direction: column; gap: 16px; }
.rightcol .art { flex: none; } /* Art pinned to the top */ .rightcol .art { flex: none; } /* Art pinned to the top */
.pair-wrap { flex: 1; display: flex; align-items: center; } /* fill the rest; pair vertically centered */ .pair-wrap { flex: 1; display: flex; align-items: center; } /* fill the rest; pair vertically centered */
.card { .card {
@@ -228,7 +242,7 @@
.news-body { padding: 24px 26px; flex: 1; display: flex; flex-direction: column; } .news-body { padding: 24px 26px; flex: 1; display: flex; flex-direction: column; }
.news h2 { .news h2 {
font-size: clamp(1.55rem, 2.6vw, 30px); line-height: 1.14; margin: 12px 0 0; font-size: clamp(1.55rem, 2.6vw, 30px); line-height: 1.14; margin: 12px 0 0;
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
} }
/* the gist fades out at the bottom (FIX 1) instead of a hard ellipsis — full read on /news */ /* the gist fades out at the bottom (FIX 1) instead of a hard ellipsis — full read on /news */
.summary { .summary {
@@ -263,7 +277,7 @@
} }
/* bottom pair */ /* bottom pair */
.pair { width: 100%; display: grid; grid-template-columns: 1fr 1fr; gap: 18px; } .pair { width: 100%; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.play { background: #FFF3DC; border: 1px solid #f6e2b8; display: flex; flex-direction: column; } .play { background: #FFF3DC; border: 1px solid #f6e2b8; display: flex; flex-direction: column; }
.play-top { padding: 22px 24px 0; } .play-top { padding: 22px 24px 0; }
.play h3 { font-size: clamp(1.25rem, 1.9vw, 23px); margin: 14px 0 0; color: #5c3d0c; } .play h3 { font-size: clamp(1.25rem, 1.9vw, 23px); margin: 14px 0 0; color: #5c3d0c; }
@@ -299,15 +313,22 @@
.moment-line { font-family: 'Newsreader', Georgia, serif; font-style: italic; font-size: 18px; line-height: 1.3; color: #214a35; margin: 14px 0 0; } .moment-line { font-family: 'Newsreader', Georgia, serif; font-style: italic; font-size: 18px; line-height: 1.3; color: #214a35; margin: 14px 0 0; }
.moment-meta { margin-top: auto; padding-top: 16px; font-size: 13px; color: #6f9683; } .moment-meta { margin-top: auto; padding-top: 16px; font-size: 13px; color: #6f9683; }
/* skinny Word / Quote of the day strip — warm neutral, two halves split by a divider */ /* "small joys" rail — three evenly-weighted daily delights, each its own quiet tint */
.wotd { flex: none; display: flex; align-items: stretch; background: #f1ece1; border: 1px solid #e6ddc9; } .joys { flex: none; display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }
.wotd-half { flex: 1; padding: 15px 22px; display: flex; flex-direction: column; justify-content: center; gap: 3px; min-width: 0; } .joy { border-radius: 18px; padding: 14px 18px; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.wotd-half + .wotd-half { border-left: 1px solid #e6ddc9; } .joy-word { background: #f1ece1; border: 1px solid #e6ddc9; }
.wotd .label { color: var(--muted); } .joy-quote { background: #f2ecf6; border: 1px solid #e4d8ee; }
.wotd .word { font-family: 'Newsreader', Georgia, serif; font-weight: 600; font-size: 20px; color: var(--ink); } .joy-fact { background: #e8f2ea; border: 1px solid #cfe5d4; }
.wotd .def { font-size: 13px; color: var(--body); } .joy .label { color: var(--muted); }
.wotd .quote { font-family: 'Newsreader', Georgia, serif; font-style: italic; font-size: 15px; line-height: 1.3; color: #2d2a25; } .word-line { margin: 0; display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.wotd .quote-by { font-size: 12px; color: var(--muted); } .joy .word { font-family: 'Newsreader', Georgia, serif; font-weight: 600; font-size: 19px; color: var(--ink); }
.word-meta { font-size: 12px; color: var(--muted); font-style: italic; }
.joy .def { font-size: 12.5px; color: var(--body); line-height: 1.4; }
.qglyph { font-family: 'Newsreader', Georgia, serif; font-size: 26px; line-height: 1; height: 13px; color: #b9a8d6; }
.joy .quote { font-family: 'Newsreader', Georgia, serif; font-style: italic; font-size: 14px; line-height: 1.3; color: #2d2a25; }
.quote-by { font-size: 11.5px; color: var(--muted); }
.joy-fact .joy-top { display: flex; align-items: center; justify-content: space-between; }
.fact { font-size: 13px; line-height: 1.4; color: var(--body); }
.foot { .foot {
text-align: center; max-width: 1180px; width: 100%; margin: 14px auto 0; box-sizing: border-box; text-align: center; max-width: 1180px; width: 100%; margin: 14px auto 0; box-sizing: border-box;
@@ -324,7 +345,6 @@
.art { flex-direction: column; } .art { flex-direction: column; }
.art-swatch { width: 100%; min-height: 130px; } .art-swatch { width: 100%; min-height: 130px; }
.pair { grid-template-columns: 1fr; } .pair { grid-template-columns: 1fr; }
.wotd { flex-direction: column; } .joys { grid-template-columns: 1fr; }
.wotd-half + .wotd-half { border-left: none; border-top: 1px solid #e6ddc9; }
} }
</style> </style>