home3/hub: shared HubBar with mobile hamburger + Art-card mobile crop fix
#2 Mobile top bar → hamburger: extracted the editorial bar (brand + nav + new collapsible hamburger drop-panel) into a shared lib/components/HubBar.svelte, used by both /home3 and HubShell (the /word /quote /onthisday detail pages), so there's one nav to maintain/audit. Full horizontal nav ≥721px; hamburger + drop panel ≤720px. Escape + link-click close it; panel is hidden on desktop as a safety. Removed the duplicated bar markup/CSS from home3 + HubShell. #1 Mobile layout / Art card: on phones the Art card now stacks image-first with the painting in a proper 3:2 frame (aspect-ratio) instead of a stubby fixed 130px band that cropped the work to a sliver. Also drop the News gist's bottom fade once the bento is single-column (natural height = no truncation, so the fade was just dimming the final line), and let the joys header wrap on phones. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,111 @@
|
||||
<script>
|
||||
// Shared editorial top bar for the hub (/home3) and its detail pages (/word, /quote,
|
||||
// /onthisday). Full horizontal nav on wide screens; a hamburger + drop panel on phones
|
||||
// so the bar stays clean. `active` highlights the current section.
|
||||
let { active = '' } = $props();
|
||||
let open = $state(false);
|
||||
|
||||
const LINKS = [
|
||||
{ key: 'home', href: '/home3', label: 'Home' },
|
||||
{ key: 'news', href: '/', label: 'News' },
|
||||
{ key: 'games', href: '/play', label: 'Games' },
|
||||
{ key: 'art', href: '/art', label: 'Art' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={(e) => { if (e.key === 'Escape') open = false; }} />
|
||||
|
||||
<header class="bar">
|
||||
<a class="brand" href="/home3" aria-label="upbeatBytes home">
|
||||
<img src="/logo.svg" alt="upbeatBytes" width="586" height="196" />
|
||||
</a>
|
||||
|
||||
<div class="bar-end">
|
||||
<nav class="nav">
|
||||
{#each LINKS as l}
|
||||
<a class:on={active === l.key} href={l.href}>{l.label}</a>
|
||||
{/each}
|
||||
<span class="nav-soon">Entertainment</span>
|
||||
</nav>
|
||||
|
||||
<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>
|
||||
|
||||
<button class="burger" class:open aria-label="Menu" aria-expanded={open} aria-controls="hub-menu"
|
||||
onclick={() => (open = !open)}>
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if open}
|
||||
<div class="menu-wrap">
|
||||
<nav id="hub-menu" class="menu">
|
||||
{#each LINKS as l}
|
||||
<a class:on={active === l.key} href={l.href} onclick={() => (open = false)}>{l.label}</a>
|
||||
{/each}
|
||||
<span class="menu-soon">Entertainment <em>soon</em></span>
|
||||
</nav>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.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: 48px; width: auto; display: block; }
|
||||
|
||||
.bar-end { display: flex; align-items: center; gap: clamp(16px, 2.4vw, 32px); }
|
||||
.nav { display: flex; align-items: center; gap: clamp(16px, 2.4vw, 32px); font-size: 16.5px; font-weight: 500; }
|
||||
.nav a { color: var(--body, #6b6256); text-decoration: none; }
|
||||
.nav a.on { color: #23201b; }
|
||||
.nav a:hover { color: var(--teal, #0083ad); }
|
||||
.nav-soon { color: #b3a890; }
|
||||
.acct {
|
||||
width: 32px; height: 32px; border-radius: 50%; border: 1.5px solid #e6c9a0; background: #FCEFD7;
|
||||
display: flex; align-items: center; justify-content: center; flex: none;
|
||||
}
|
||||
.acct:hover { background: #fbe6c4; }
|
||||
|
||||
/* hamburger — phones only */
|
||||
.burger {
|
||||
display: none; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
|
||||
width: 40px; height: 40px; border-radius: 11px; border: 1.5px solid #e6c9a0; background: #FCEFD7;
|
||||
cursor: pointer; padding: 0; flex: none;
|
||||
}
|
||||
.burger:hover { background: #fbe6c4; }
|
||||
.burger span { width: 18px; height: 2px; border-radius: 2px; background: #7a6a52; transition: transform 0.2s ease, opacity 0.15s ease; }
|
||||
.burger.open span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
|
||||
.burger.open span:nth-child(2) { opacity: 0; }
|
||||
.burger.open span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
|
||||
|
||||
/* drop panel */
|
||||
.menu-wrap { max-width: 1180px; width: 100%; margin: 10px auto 0; box-sizing: border-box; padding: 0 clamp(18px, 5vw, 44px); }
|
||||
.menu {
|
||||
display: flex; flex-direction: column; background: #fff; border: 1px solid var(--news-border, #f2e7d3);
|
||||
border-radius: 14px; overflow: hidden; box-shadow: 0 14px 34px -20px rgba(60, 50, 30, 0.4);
|
||||
}
|
||||
.menu a, .menu .menu-soon {
|
||||
padding: 14px 18px; font-size: 16px; font-weight: 500; text-decoration: none;
|
||||
color: var(--body, #6b6256); border-top: 1px solid #f3ece0;
|
||||
}
|
||||
.menu a:first-child { border-top: none; }
|
||||
.menu a.on { color: #23201b; }
|
||||
.menu a:hover { background: var(--canvas, #FFF9EF); color: var(--teal, #0083ad); }
|
||||
.menu-soon { display: flex; align-items: center; justify-content: space-between; color: #b3a890; }
|
||||
.menu-soon em { font-style: normal; font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: #c3b69c; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.nav { display: none; }
|
||||
.burger { display: flex; }
|
||||
}
|
||||
@media (min-width: 721px) {
|
||||
.menu-wrap { display: none; } /* safety: never show the panel once back on desktop */
|
||||
}
|
||||
</style>
|
||||
@@ -1,27 +1,12 @@
|
||||
<script>
|
||||
// Shared shell for the hub + its detail pages (/word, /quote, /onthisday): the editorial
|
||||
// top bar, footer, fonts, and design tokens. Content goes in the default slot.
|
||||
import HubBar from './HubBar.svelte';
|
||||
let { active = '', children } = $props();
|
||||
</script>
|
||||
|
||||
<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={active === 'home'} href="/home3">Home</a>
|
||||
<a class:on={active === 'news'} href="/">News</a>
|
||||
<a class:on={active === 'games'} href="/play">Games</a>
|
||||
<a class:on={active === 'art'} href="/art">Art</a>
|
||||
<span class="nav-soon">Entertainment</span>
|
||||
<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>
|
||||
<HubBar {active} />
|
||||
|
||||
<main class="shell-main">{@render children?.()}</main>
|
||||
|
||||
@@ -42,24 +27,6 @@
|
||||
}
|
||||
.page :global(*) { box-sizing: border-box; }
|
||||
|
||||
.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: 48px; width: auto; display: block; }
|
||||
.nav { display: flex; align-items: center; gap: clamp(16px, 2.4vw, 32px); font-size: 16.5px; font-weight: 500; }
|
||||
.nav a { color: var(--body); text-decoration: none; }
|
||||
.nav a.on { color: #23201b; }
|
||||
.nav a:hover { color: var(--teal); }
|
||||
.nav-soon { color: #b3a890; }
|
||||
.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; }
|
||||
|
||||
.shell-main {
|
||||
flex: 1; width: 100%; max-width: 1180px; margin: 0 auto; box-sizing: border-box;
|
||||
padding: clamp(26px, 5vw, 56px) clamp(18px, 5vw, 44px) clamp(40px, 6vw, 72px);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { getJSON } from '$lib/api.js';
|
||||
import HubBar from '$lib/components/HubBar.svelte';
|
||||
|
||||
// /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
|
||||
@@ -102,23 +103,7 @@
|
||||
{/snippet}
|
||||
|
||||
<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="/home3">Home</a>
|
||||
<a href="/">News</a>
|
||||
<a href="/play">Games</a>
|
||||
<a href="/art">Art</a>
|
||||
<span class="nav-soon">Entertainment</span>
|
||||
<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>
|
||||
<HubBar active="home" />
|
||||
|
||||
<section class="hero">
|
||||
<h1>A <span class="t">calmer</span>, <span class="b">brighter</span> corner of the internet.</h1>
|
||||
@@ -266,25 +251,6 @@
|
||||
}
|
||||
.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: 48px; width: auto; display: block; }
|
||||
.nav { display: flex; align-items: center; gap: clamp(16px, 2.4vw, 32px); font-size: 16.5px; font-weight: 500; }
|
||||
.nav a { color: var(--body); text-decoration: none; }
|
||||
.nav a.on { color: #23201b; }
|
||||
.nav a:hover { color: var(--teal); }
|
||||
.nav-soon { color: #b3a890; } /* Entertainment — in the bar, but not yet a live page */
|
||||
.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 {
|
||||
@@ -491,11 +457,19 @@
|
||||
@media (max-width: 860px) {
|
||||
.bento { grid-template-columns: 1fr; }
|
||||
.news { grid-row: auto; }
|
||||
/* single column = natural card height, so the gist is never truncated; drop the
|
||||
bottom fade (it would otherwise dim the final line for no reason) */
|
||||
.summary-a { -webkit-mask-image: none; mask-image: none; flex: 0 1 auto; }
|
||||
}
|
||||
@media (max-width: 520px) {
|
||||
.art { flex-direction: column; }
|
||||
.art-swatch { width: 100%; min-height: 130px; }
|
||||
/* Art becomes an image-first card: the painting on top in a proper landscape frame
|
||||
(aspect-ratio, not a stubby fixed-height band that crop the work to a sliver),
|
||||
caption beneath. */
|
||||
.art { flex-direction: column; min-height: 0; }
|
||||
.art-swatch { width: 100%; min-width: 0; order: -1; aspect-ratio: 3 / 2; }
|
||||
.pair { grid-template-columns: 1fr; }
|
||||
.joys { grid-template-columns: 1fr; }
|
||||
/* tighten the joys header so the title + dots/arrows never collide on a phone */
|
||||
.joys-head { flex-wrap: wrap; gap: 8px 12px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user