/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