Homepage hub: /home2 prototype — sections-as-data + bigger shell + hybrid room cards

- New sections registry (lib/rooms.js): each room is one data entry (title/blurb/href/cta/
  size/preview/icon) — add or resize by editing the list.
- Reusable RoomCard (lib/components/RoomCard.svelte) with size variants and hybrid previews:
  Art shows today's live thumbnail, News shows today's top headline, others are blurb+CTA.
- /home2 hidden prototype (noindex, unlinked) with a bigger top bar (logo 50px, larger nav
  labels) and a reflowing grid hub of the four rooms (News/Art/Play/Daily Moment). Iterate
  the look here, then promote to / and remove the clone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-21 19:26:31 -04:00
parent dd8706e2fc
commit 5a8e178f51
3 changed files with 240 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
<script>
import { onMount } from 'svelte';
import { getJSON } from '$lib/api.js';
import { ROOMS } from '$lib/rooms.js';
import RoomCard from '$lib/components/RoomCard.svelte';
// Hidden prototype of the new homepage hub (/home2). Iterate the look here, then
// promote to / and remove this clone — same approach we used for /art.
let artImg = $state(null);
let newsHeadline = $state('');
onMount(async () => {
try { artImg = (await getJSON('/api/art/today'))?.image_url ?? null; } catch { /* card falls back to blurb */ }
try {
const b = await getJSON('/api/brief?limit=1');
newsHeadline = b?.items?.[0]?.title ?? '';
} catch { /* card falls back to blurb */ }
});
</script>
<svelte:head>
<title>upbeatBytes — a calmer corner of the internet</title>
<meta name="robots" content="noindex" />
<meta name="description" content="A calmer corner of the internet: good news, daily art, small games, and little resets." />
</svelte:head>
<div class="room">
<header class="bar">
<a class="brand" href="/home2" aria-label="upbeatBytes home">
<img src="/logo.svg" alt="upbeatBytes" width="586" height="196" />
</a>
<nav class="nav">
<a href="/">News</a>
<a href="/play">Games</a>
<a href="/art">Art</a>
<a class="acct" href="/account" aria-label="Your account">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor"
stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="8" r="3.3" />
<path d="M5.5 19.2a6.5 6.5 0 0 1 13 0" />
</svg>
</a>
</nav>
</header>
<main class="hub">
<div class="intro">
<h1>A calmer corner of the internet.</h1>
<p>Good news, daily art, small games, and little resets.</p>
</div>
<div class="grid">
{#each ROOMS as r (r.id)}
<RoomCard room={r} {artImg} {newsHeadline} />
{/each}
</div>
</main>
<footer class="foot">upbeatBytes — no ads, no paywalls, no doomscrolling.</footer>
</div>
<style>
.room {
--canvas: #faf6ee; --surface: #ffffff; --ink: #232a31; --muted: #707b86;
--line: #ece5d8; --accent: #0a93c0; --accent-deep: #066c8e;
min-height: 100vh; background: var(--canvas); color: var(--ink);
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
display: flex; flex-direction: column;
}
/* Bigger, more present top bar — logo + nav labels scaled up. */
.bar {
display: flex; align-items: center; justify-content: space-between;
padding: 20px clamp(18px, 5vw, 64px);
max-width: 1180px; width: 100%; margin: 0 auto; box-sizing: border-box;
}
.brand { display: block; line-height: 0; }
.brand img { height: 50px; width: auto; display: block; }
.nav { display: flex; align-items: center; gap: clamp(18px, 3vw, 38px); }
.nav a { color: var(--ink); text-decoration: none; font-weight: 600; font-size: 1.12rem; }
.nav a:hover { color: var(--accent); }
.acct {
display: inline-flex; align-items: center; justify-content: center;
width: 44px; height: 44px; border-radius: 50%; color: var(--muted);
}
.acct:hover { color: var(--accent); background: #eef6f9; }
.hub {
flex: 1; width: 100%; max-width: 1180px; margin: 0 auto; box-sizing: border-box;
padding: clamp(16px, 3vw, 40px) clamp(18px, 5vw, 64px) clamp(40px, 6vw, 72px);
}
.intro { text-align: center; margin: clamp(8px, 2vw, 22px) 0 clamp(28px, 4vw, 44px); }
.intro h1 {
font-family: Georgia, "Iowan Old Style", "Times New Roman", serif;
font-size: clamp(1.9rem, 4.5vw, 3rem); margin: 0; letter-spacing: -0.015em; line-height: 1.08;
}
.intro p { color: var(--muted); margin: 12px 0 0; font-size: clamp(1rem, 2vw, 1.2rem); }
.grid {
display: grid; gap: clamp(14px, 1.8vw, 22px);
grid-template-columns: repeat(4, 1fr); grid-auto-flow: dense;
}
/* size variants → grid spans (RoomCard owns min-heights) */
:global(.grid .card--large) { grid-column: span 2; grid-row: span 2; }
:global(.grid .card--wide) { grid-column: span 2; }
:global(.grid .card--tall) { grid-row: span 2; }
@media (max-width: 860px) {
.grid { grid-template-columns: repeat(2, 1fr); }
:global(.grid .card--large), :global(.grid .card--wide) { grid-column: span 2; }
:global(.grid .card--large), :global(.grid .card--tall) { grid-row: auto; }
}
@media (max-width: 540px) {
.grid { grid-template-columns: 1fr; }
:global(.grid .card--large), :global(.grid .card--wide), :global(.grid .card--tall) { grid-column: span 1; }
}
.foot {
text-align: center; color: var(--muted); font-size: 0.86rem;
padding: 28px 16px 40px; border-top: 1px solid var(--line); margin-top: auto;
}
</style>