home3: Claude Design "Frame A" homepage (editorial, with colour) — real logo + live data
- Rebuilds the design handoff's preferred Frame A: Newsreader serif headlines + Hanken Grotesk body (both self-hosted, OFL, no Google hotlink), warm cream canvas, per-card accent tints (News teal, Art plum, Play amber, Moment green), bento grid. - Uses our real /logo.svg instead of the mock's Bricolage wordmark + sunrise. - Wired live: Good News pulls the top headline/summary/photo (respects the saved Closer-to-Home filter); Daily Art pulls today's Met piece (title/artist/year + thumbnail). - Hidden prototype (noindex), spacing tuned per the /home2 pass (hero pulled up, more air before the bento). Sits beside /home2 for comparison. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { getJSON } from '$lib/api.js';
|
||||
|
||||
// /home3 — the Claude Design "Frame A" direction (editorial, with colour), rebuilt in
|
||||
// our codebase with our real logo + self-hosted fonts, wired to live data. Hidden
|
||||
// prototype (noindex), alongside /home2 so we can compare.
|
||||
let news = $state(null); // {title, summary, image}
|
||||
let art = $state(null); // {title, artist, year, image}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const a = await getJSON('/api/art/today');
|
||||
if (a) art = { title: a.title, artist: a.artist, year: a.date_text, image: a.image_url };
|
||||
} catch { /* fall back to gradient swatch */ }
|
||||
|
||||
let homeq = '';
|
||||
try {
|
||||
const hv = localStorage.getItem('goodnews:home') || '';
|
||||
const hs = localStorage.getItem('goodnews:homeScope') || 'nearby';
|
||||
if (hv && hs !== 'world') homeq = `&home=${encodeURIComponent(hv)}&scope=${hs}`;
|
||||
} catch { /* global brief */ }
|
||||
try {
|
||||
const it = (await getJSON(`/api/brief?limit=1${homeq}`))?.items?.[0];
|
||||
if (it) news = { title: it.title, summary: it.summary || it.description || '', image: it.image_url || null };
|
||||
} catch { /* fall back to design copy */ }
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>upbeatBytes — a calmer, brighter corner of the internet</title>
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta name="description" content="A calmer, brighter corner of the internet: good news, daily art, small games, and little resets." />
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
<header class="bar">
|
||||
<a class="brand" href="/home3" aria-label="upbeatBytes home">
|
||||
<img src="/logo.svg" alt="upbeatBytes" width="586" height="196" />
|
||||
</a>
|
||||
<nav class="nav">
|
||||
<a class="on" href="/">News</a>
|
||||
<a href="/play">Games</a>
|
||||
<a href="/art">Art</a>
|
||||
<a class="acct" href="/account" aria-label="Your account">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#C98A2E" stroke-width="2" aria-hidden="true">
|
||||
<circle cx="12" cy="8" r="4" /><path d="M4 21c0-4 4-6 8-6s8 2 8 6" />
|
||||
</svg>
|
||||
</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<section class="hero">
|
||||
<h1>A <span class="t">calmer</span>, <span class="b">brighter</span> corner of the internet.</h1>
|
||||
<p class="sub">Good news, daily art, small games, and little resets.</p>
|
||||
</section>
|
||||
|
||||
<main class="bento">
|
||||
<!-- Good News (tall) -->
|
||||
<a class="card news" href="/">
|
||||
<div class="news-photo" style={news?.image ? `background-image:url(${news.image})` : ''}></div>
|
||||
<div class="news-body">
|
||||
<span class="label" style="color:#0083ad">GOOD NEWS</span>
|
||||
<h2>{news?.title ?? 'What went right this week: the good news that actually matters'}</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>
|
||||
<div class="news-foot">
|
||||
<span class="link news-link">Read the brief</span>
|
||||
<span class="meta">Updated this morning</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Daily Art (wide) -->
|
||||
<a class="card art" href="/art">
|
||||
<div class="art-body">
|
||||
<span class="label" style="color:#8857C2">DAILY ART</span>
|
||||
<h3>A masterwork a day, beautifully framed</h3>
|
||||
<p class="art-today">
|
||||
{#if art}Today: <span class="ital">{art.title}</span>{#if art.artist} — {art.artist}{/if}{#if art.year}, {art.year}{/if}.
|
||||
{:else}Today: <span class="ital">Among the Sierra Nevada</span> — Bierstadt, 1868.{/if}
|
||||
</p>
|
||||
<span class="link art-link">View today</span>
|
||||
</div>
|
||||
<div class="art-swatch" style={art?.image ? `background-image:url(${art.image})` : ''}></div>
|
||||
</a>
|
||||
|
||||
<!-- bottom pair -->
|
||||
<div class="pair">
|
||||
<a class="card play" href="/play">
|
||||
<span class="label" style="color:#D2861B">PLAY</span>
|
||||
<h3>A little daily puzzle</h3>
|
||||
<p class="play-copy">Daily Word, Word Search, Bloom, Memory Match.</p>
|
||||
<span class="link play-link">Enter</span>
|
||||
</a>
|
||||
<div class="card moment">
|
||||
<div class="moment-top">
|
||||
<span class="label" style="color:#3F9A66">DAILY MOMENT</span>
|
||||
<span class="soon">SOON</span>
|
||||
</div>
|
||||
<p class="moment-line">A small calm to carry with you.</p>
|
||||
<span class="moment-meta">Breathing, sound & stillness.</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="foot">upbeatBytes — no ads, no paywalls, no doomscrolling.</footer>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@font-face { font-family: 'Hanken Grotesk'; src: url('/fonts/hanken-var.woff2') format('woff2'); font-weight: 400 700; font-style: normal; font-display: swap; }
|
||||
@font-face { font-family: 'Newsreader'; src: url('/fonts/newsreader-var.woff2') format('woff2'); font-weight: 400 600; font-style: normal; font-display: swap; }
|
||||
@font-face { font-family: 'Newsreader'; src: url('/fonts/newsreader-italic-var.woff2') format('woff2'); font-weight: 400 500; font-style: italic; font-display: swap; }
|
||||
|
||||
.page {
|
||||
--ink: #1c1916; --body: #6b6256; --muted: #a89e8c; --teal: #0083ad;
|
||||
--canvas: #FFF9EF; --news-border: #f2e7d3;
|
||||
min-height: 100vh; background: var(--canvas); color: #23201b;
|
||||
font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif;
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
.page :global(*) { box-sizing: border-box; }
|
||||
|
||||
/* Header — real logo left, nav right */
|
||||
.bar {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
max-width: 1180px; width: 100%; margin: 0 auto; box-sizing: border-box;
|
||||
padding: 26px clamp(18px, 5vw, 44px) 0;
|
||||
}
|
||||
.brand { display: block; line-height: 0; }
|
||||
.brand img { height: 42px; width: auto; display: block; }
|
||||
.nav { display: flex; align-items: center; gap: clamp(16px, 2.4vw, 30px); font-size: 15px; font-weight: 500; }
|
||||
.nav a { color: var(--body); text-decoration: none; }
|
||||
.nav a.on { color: #23201b; }
|
||||
.nav a:hover { color: var(--teal); }
|
||||
.acct {
|
||||
width: 32px; height: 32px; border-radius: 50%; border: 1.5px solid #e6c9a0; background: #FCEFD7;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.acct:hover { background: #fbe6c4; }
|
||||
|
||||
/* Hero — spacing tuned per the /home2 pass: pulled up a touch, more air before cards */
|
||||
.hero { text-align: center; max-width: 1180px; width: 100%; margin: 0 auto; padding: clamp(24px, 4vw, 34px) clamp(18px, 5vw, 44px) clamp(38px, 5vw, 48px); }
|
||||
.hero h1 {
|
||||
font-family: 'Newsreader', Georgia, serif; font-weight: 500;
|
||||
font-size: clamp(2.1rem, 5vw, 50px); line-height: 1.04; letter-spacing: -0.015em; margin: 0; color: var(--ink);
|
||||
}
|
||||
.hero h1 .t { color: #0083ad; }
|
||||
.hero h1 .b { color: #E0852C; }
|
||||
.hero .sub { font-family: 'Newsreader', Georgia, serif; font-style: italic; font-size: clamp(1rem, 2vw, 19px); color: #857b6c; margin: 14px 0 0; }
|
||||
|
||||
/* Bento grid */
|
||||
.bento {
|
||||
max-width: 1180px; width: 100%; margin: 0 auto; box-sizing: border-box;
|
||||
padding: 0 clamp(18px, 5vw, 44px) 16px;
|
||||
display: grid; grid-template-columns: minmax(0, 1.18fr) minmax(0, 1.82fr); gap: 18px;
|
||||
}
|
||||
.card {
|
||||
border-radius: 18px; overflow: hidden; text-decoration: none; color: inherit;
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
a.card:hover { transform: translateY(-2px); }
|
||||
.label { font-size: 11px; font-weight: 700; letter-spacing: 0.16em; }
|
||||
.link { font-size: 14px; font-weight: 600; padding-bottom: 2px; align-self: flex-start; }
|
||||
h2, h3 { font-family: 'Newsreader', Georgia, serif; font-weight: 500; letter-spacing: -0.01em; color: var(--ink); }
|
||||
|
||||
/* Good News — tall, photo on top */
|
||||
.news {
|
||||
grid-row: span 2; background: #fff; border: 1px solid var(--news-border);
|
||||
display: flex; flex-direction: column; box-shadow: 0 6px 20px -14px rgba(0, 131, 173, 0.4);
|
||||
}
|
||||
.news-photo { aspect-ratio: 5/4; background: linear-gradient(160deg, #6fc4d8, #9bd4c9 55%, #f6cf7e); background-size: cover; background-position: center; }
|
||||
.news-body { padding: 24px 26px; flex: 1; display: flex; flex-direction: column; }
|
||||
.news h2 { font-size: clamp(1.4rem, 2.4vw, 27px); line-height: 1.14; margin: 12px 0 0; }
|
||||
.summary { font-size: 14.5px; line-height: 1.55; color: var(--body); margin: 12px 0 0; }
|
||||
.news-foot { margin-top: auto; display: flex; align-items: center; justify-content: space-between; padding-top: 20px; }
|
||||
.news-link { color: var(--teal); border-bottom: 2px solid #6fc4d8; }
|
||||
.meta { font-size: 12px; color: var(--muted); }
|
||||
|
||||
/* Daily Art — wide, text left + artwork swatch right */
|
||||
.art { background: #F3EEF9; border: 1px solid #e4d8f1; display: flex; min-height: 188px; }
|
||||
.art-body { flex: 1; padding: 24px 26px; display: flex; flex-direction: column; }
|
||||
.art h3 { font-size: clamp(1.25rem, 2vw, 23px); line-height: 1.16; margin: 10px 0 0; color: #2a1c3d; }
|
||||
.art-today { font-size: 13.5px; line-height: 1.5; color: #6f6280; margin: 9px 0 0; }
|
||||
.ital { font-style: italic; font-family: 'Newsreader', Georgia, serif; }
|
||||
.art-link { margin-top: auto; color: #8857C2; border-bottom: 2px solid #c9aef0; }
|
||||
.art-swatch { width: 46%; min-width: 130px; background: linear-gradient(170deg, #bfe0f0 0%, #a9cf9a 50%, #d89a4e 100%); background-size: cover; background-position: center; }
|
||||
|
||||
/* bottom pair */
|
||||
.pair { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
|
||||
.play { background: #FFF3DC; border: 1px solid #f6e2b8; padding: 22px 24px; display: flex; flex-direction: column; }
|
||||
.play h3 { font-size: clamp(1.15rem, 1.8vw, 21px); margin: 9px 0 8px; color: #5c3d0c; }
|
||||
.play-copy { font-size: 13px; line-height: 1.5; color: #917642; margin: 0; }
|
||||
.play-link { margin-top: auto; padding-top: 16px; color: #D2861B; border-bottom: 2px solid #f0c878; }
|
||||
|
||||
.moment { background: #E6F3E9; border: 1px solid #cee6d3; padding: 22px 24px; display: flex; flex-direction: column; }
|
||||
.moment-top { display: flex; align-items: center; justify-content: space-between; }
|
||||
.soon { font-size: 10px; font-weight: 700; letter-spacing: 0.08em; color: #3F9A66; background: #fff; border-radius: 999px; padding: 3px 8px; }
|
||||
.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; }
|
||||
|
||||
.foot {
|
||||
text-align: center; max-width: 1180px; width: 100%; margin: 14px auto 0; box-sizing: border-box;
|
||||
padding: 20px clamp(18px, 5vw, 44px) 30px; font-size: 13px; color: var(--muted);
|
||||
border-top: 1px solid var(--news-border);
|
||||
}
|
||||
|
||||
/* responsive — collapse the bento on narrow screens */
|
||||
@media (max-width: 860px) {
|
||||
.bento { grid-template-columns: 1fr; }
|
||||
.news { grid-row: auto; }
|
||||
}
|
||||
@media (max-width: 520px) {
|
||||
.art { flex-direction: column; }
|
||||
.art-swatch { width: 100%; min-height: 130px; }
|
||||
.pair { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +1,7 @@
|
||||
manrope-var.woff2 — "Manrope" variable font by Mikhail Sharanda / Mirko Velimirovic.
|
||||
License: SIL Open Font License 1.1 (free to use/bundle, no attribution required in product UI).
|
||||
Source: https://github.com/sharanda/manrope (via fontsource).
|
||||
|
||||
hanken-var.woff2 — "Hanken Grotesk" by Alfredo Marco Pradil. License: SIL Open Font License 1.1.
|
||||
newsreader-var.woff2 / newsreader-italic-var.woff2 — "Newsreader" by Production Type (Google Fonts).
|
||||
License: SIL Open Font License 1.1. All self-hosted (no Google hotlink) for the /home3 design direction.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user