/art v1: single featured piece, framed (look-overhaul testbed)
A calm "walk up to one piece on a quiet wall" page for the Daily Art engine. Bright, modern, cream canvas (not a dim brown gallery) so the artwork's own colors are the pop — the new look prototyped, scoped to /art so it doesn't touch the rest of the site yet. - Fetches /api/art/today; large soft-shadowed frame (click -> lightbox), a museum "placard" (serif title, artist · date, medium, "from The Met · Public Domain (CC0)", View-at-museum link). Calm loading/empty states. Unlinked from the homepage (safe to iterate). Ships the (Codex-cleared) art backend too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { getJSON } from '$lib/api.js';
|
||||
|
||||
let art = $state(null);
|
||||
let state = $state('loading'); // loading | ready | empty
|
||||
let zoom = $state(false);
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
art = await getJSON('/api/art/today');
|
||||
state = art?.image_url ? 'ready' : 'empty';
|
||||
} catch {
|
||||
state = 'empty';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Daily Art · upbeatBytes</title>
|
||||
<meta name="description" content="A masterwork a day from the world's open museum collections — one piece, beautifully framed, on upbeatBytes." />
|
||||
</svelte:head>
|
||||
|
||||
<div class="room">
|
||||
<header class="bar">
|
||||
<a class="brand" href="/">upbeat<span>Bytes</span></a>
|
||||
<nav class="nav">
|
||||
<a href="/">News</a>
|
||||
<a href="/play">Games</a>
|
||||
<a href="/art" aria-current="page">Art</a>
|
||||
<span class="pill">No ads · No paywalls</span>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="gallery">
|
||||
<div class="intro">
|
||||
<h1>Daily Art</h1>
|
||||
<p>A masterwork a day, from the world's open collections.</p>
|
||||
</div>
|
||||
|
||||
{#if state === 'ready'}
|
||||
<figure class="piece">
|
||||
<button class="frame" onclick={() => (zoom = true)} aria-label="View larger">
|
||||
<img src={art.image_url} alt={art.title} />
|
||||
</button>
|
||||
<figcaption class="placard">
|
||||
<h2 class="title">{art.title}</h2>
|
||||
<p class="who">{art.artist || 'Unknown artist'}{#if art.date_text} · {art.date_text}{/if}</p>
|
||||
{#if art.medium}<p class="medium">{art.medium}</p>{/if}
|
||||
<p class="credit">
|
||||
from {art.museum}{#if art.license} · {art.license}{/if}
|
||||
{#if art.source_url}<a class="more" href={art.source_url} target="_blank" rel="noopener">View at {art.museum} →</a>{/if}
|
||||
</p>
|
||||
</figcaption>
|
||||
</figure>
|
||||
{:else if state === 'empty'}
|
||||
<p class="note">The gallery's resting — a new piece is hung each morning. Check back soon.</p>
|
||||
{:else}
|
||||
<p class="note">Hanging today's piece…</p>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<footer class="foot">upbeatBytes — no ads, no paywalls, no doomscrolling.</footer>
|
||||
</div>
|
||||
|
||||
{#if zoom && art}
|
||||
<button class="lightbox" onclick={() => (zoom = false)} aria-label="Close">
|
||||
<img src={art.image_url} alt={art.title} />
|
||||
<span class="lb-cap">{art.title} · {art.artist || ''}</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* --- look-overhaul testbed: bright, modern, calm. Scoped to /art for now. --- */
|
||||
.room {
|
||||
--canvas: #faf6ee; /* warm cream, not brown */
|
||||
--surface: #ffffff;
|
||||
--ink: #232a31; /* dark slate */
|
||||
--muted: #707b86;
|
||||
--line: #ece5d8;
|
||||
--accent: #0a93c0; /* upbeatBytes blue, a touch brighter */
|
||||
--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;
|
||||
}
|
||||
|
||||
.bar {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 18px clamp(16px, 5vw, 56px);
|
||||
max-width: 1100px; width: 100%; margin: 0 auto; box-sizing: border-box;
|
||||
}
|
||||
.brand { font-weight: 800; font-size: 1.25rem; letter-spacing: -0.02em; color: var(--ink); text-decoration: none; }
|
||||
.brand span { color: var(--accent); }
|
||||
.nav { display: flex; align-items: center; gap: clamp(12px, 2.5vw, 26px); }
|
||||
.nav a { color: var(--muted); text-decoration: none; font-weight: 600; font-size: 0.95rem; }
|
||||
.nav a:hover { color: var(--ink); }
|
||||
.nav a[aria-current="page"] { color: var(--accent); }
|
||||
.pill {
|
||||
font-size: 0.76rem; font-weight: 600; color: var(--accent-deep);
|
||||
background: #e7f4f9; border: 1px solid #cdeaf3; border-radius: 999px; padding: 5px 12px;
|
||||
}
|
||||
|
||||
.gallery {
|
||||
flex: 1; width: 100%; max-width: 1100px; margin: 0 auto;
|
||||
padding: clamp(20px, 5vw, 56px); box-sizing: border-box;
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
}
|
||||
.intro { text-align: center; margin-bottom: clamp(24px, 5vw, 48px); }
|
||||
.intro h1 {
|
||||
font-family: Georgia, "Iowan Old Style", "Times New Roman", serif;
|
||||
font-size: clamp(2rem, 5vw, 3rem); margin: 0; letter-spacing: -0.01em;
|
||||
}
|
||||
.intro p { color: var(--muted); margin: 10px 0 0; font-size: 1.05rem; }
|
||||
|
||||
.piece { margin: 0; display: flex; flex-direction: column; align-items: center; max-width: 820px; }
|
||||
.frame {
|
||||
border: none; padding: 0; background: var(--surface); cursor: zoom-in;
|
||||
border-radius: 18px; overflow: hidden; line-height: 0;
|
||||
box-shadow: 0 18px 50px rgba(20, 30, 45, 0.14), 0 2px 8px rgba(20, 30, 45, 0.06);
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||
}
|
||||
.frame:hover { transform: translateY(-3px); box-shadow: 0 26px 64px rgba(20, 30, 45, 0.18); }
|
||||
.frame img { display: block; width: 100%; height: auto; max-height: 74vh; object-fit: contain; }
|
||||
|
||||
.placard { text-align: center; margin-top: clamp(20px, 4vw, 34px); max-width: 640px; }
|
||||
.title {
|
||||
font-family: Georgia, "Iowan Old Style", "Times New Roman", serif;
|
||||
font-size: clamp(1.4rem, 3.5vw, 2rem); margin: 0; line-height: 1.2;
|
||||
}
|
||||
.who { margin: 8px 0 0; font-size: 1.05rem; color: var(--ink); }
|
||||
.medium { margin: 4px 0 0; color: var(--muted); font-size: 0.95rem; font-style: italic; }
|
||||
.credit { margin: 16px 0 0; color: var(--muted); font-size: 0.88rem; }
|
||||
.more { display: inline-block; margin-left: 8px; color: var(--accent); font-weight: 600; text-decoration: none; }
|
||||
.more:hover { color: var(--accent-deep); }
|
||||
|
||||
.note { color: var(--muted); font-size: 1.05rem; margin-top: 40px; }
|
||||
|
||||
.foot {
|
||||
text-align: center; color: var(--muted); font-size: 0.84rem;
|
||||
padding: 28px 16px 36px; border-top: 1px solid var(--line); margin-top: 24px;
|
||||
}
|
||||
|
||||
.lightbox {
|
||||
position: fixed; inset: 0; z-index: 50; border: none; cursor: zoom-out;
|
||||
background: rgba(18, 22, 28, 0.92); display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center; gap: 14px; padding: 4vmin;
|
||||
}
|
||||
.lightbox img { max-width: 96vw; max-height: 88vh; border-radius: 8px; box-shadow: 0 20px 60px rgba(0,0,0,0.5); }
|
||||
.lb-cap { color: #e9e2d6; font-size: 0.9rem; }
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.pill { display: none; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user