feat: Apple-grade pass — scale, type, photographic hero, de-noise, single-signal chrome, lighter header

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012jWfn3RwPfFGTtBddm36Uy
This commit is contained in:
ctao
2026-07-25 11:47:30 +02:00
committed by ctao
co-authored by Claude Fable 5
parent 3202b853a7
commit eaf511a976
11 changed files with 525 additions and 429 deletions
+4 -9
View File
@@ -19,16 +19,11 @@ const tickets = [
];
---
<Base title="CTAO Science Portal — Dashboard" description="User dashboard (mock)">
<section class="hero-band">
<div class="band-bg" aria-hidden="true"></div>
<div class="container hero">
<div class="eyebrow">User area</div>
<h1>Dashboard</h1>
<p>Your proposals, data products and support tickets in one place</p>
</div>
</section>
<section class="band--moon">
<header class="container page-head">
<h1>Dashboard</h1>
<p class="standfirst">Your proposals, data products and support tickets in one place.</p>
</header>
<div class="container page">
<span class="badge-mock">Mock — sample data, no live services connected</span>
<div class="dash-grid">
+15 -14
View File
@@ -10,27 +10,34 @@ const iso = (d) => d.toISOString().slice(0, 10);
// Reading time from the Markdown body (~220 wpm) — differentiating card metadata;
// category/author are identical across all articles, so cards omit them (NN/g).
const readMin = (p) => Math.max(1, Math.round((p.body ?? '').split(/\s+/).length / 220));
// Services per the Science Portal spec; unbuilt ones are labelled, never faked.
// Services per the Science Portal spec. Tiles carry no status chrome — honesty
// lives at the interaction point (REQUIREMENTS §5): each target page opens with
// its own whisper badge. Planned services stay in the grid but subdued (muted,
// no link) with availability folded into the description text.
// Tile icons: hand-drawn stroke glyphs in the header-icon family (.ico —
// currentColor, round caps), aria-hidden. NN/g icon research: icons alone are
// ambiguous, but NEXT TO an always-visible label they differentiate otherwise
// uniform tiles and speed up grid scanning.
const services = [
{ title: 'Proposals', href: '/proposals', desc: 'Submit observation proposals, including ToO / MWL requests.', badge: 'mock',
{ title: 'Proposals', href: '/proposals', desc: 'Submit observation proposals, including ToO / MWL requests.',
icon: '<path d="M13.5 3H7a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7.5z"/><path d="M13.5 3v4.5H18"/><path d="M9 12.5h6M9 16h4"/>' },
{ title: 'Data Explorer', href: 'https://padc-ctao-data-explorer.obspm.fr/', ext: true, desc: 'Search and download CTAO science data products.', badge: 'ext',
{ title: 'Data Explorer', href: 'https://padc-ctao-data-explorer.obspm.fr/', ext: true, desc: 'Search and download CTAO science data products.',
icon: '<ellipse cx="12" cy="5.5" rx="7" ry="2.5"/><path d="M5 5.5V18c0 1.4 3.1 2.5 7 2.5s7-1.1 7-2.5V5.5"/><path d="M5 11.8c0 1.4 3.1 2.5 7 2.5s7-1.1 7-2.5"/>' },
{ title: 'Dashboard', href: '/dashboard', desc: 'Your proposals, data products and support tickets in one place.', badge: 'mock',
{ title: 'Dashboard', href: '/dashboard', desc: 'Your proposals, data products and support tickets in one place.',
icon: '<rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/>' },
{ title: 'User Support', href: '/support', desc: 'Help desk, FAQ, user forum and mailing lists.', badge: 'mock',
{ title: 'User Support', href: '/support', desc: 'Help desk, FAQ, user forum and mailing lists.',
icon: '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4"/><path d="m5.7 5.7 3.5 3.5m5.6 0 3.5-3.5m0 12.6-3.5-3.5m-5.6 0-3.5 3.5"/>' },
{ title: 'Software', desc: 'Science analysis tools and pipelines.', badge: 'tbd',
{ title: 'Software', planned: true, desc: 'Science analysis tools and pipelines — planned.',
icon: '<rect x="3" y="4.5" width="18" height="15" rx="2"/><path d="m7 9.5 3 2.5-3 2.5M12.5 15H17"/>' },
{ title: 'Documentation', desc: 'User guides and technical documentation.', badge: 'tbd',
{ title: 'Documentation', planned: true, desc: 'User guides and technical documentation — planned.',
icon: '<path d="M12 6.7C10.6 5.2 8.6 4.5 5.7 4.5H4v13.6h1.7c2.9 0 4.9.7 6.3 2.2 1.4-1.5 3.4-2.2 6.3-2.2H20V4.5h-1.7c-2.9 0-4.9.7-6.3 2.2z"/><path d="M12 6.7v13.6"/>' },
];
---
<Base title="CTAO Science Portal" description="News, observation proposals, data and user services of the Cherenkov Telescope Array Observatory" ambient>
{/* Photographic hero: LST-1 under the La Palma night sky (copied from the
content library to /brand/hero.jpg) beneath the Galaxy scrim — text
contrast computed worst-case in DESIGN.md. Three elements only:
headline, brand subtitle, CTA pair (Apple hero restraint). */}
<section class="hero-band">
<div class="band-bg" aria-hidden="true"></div>
<div class="container hero">
@@ -43,9 +50,6 @@ const services = [
<a class="btn external" href="https://padc-ctao-data-explorer.obspm.fr/" target="_blank" rel="noopener">Explore the data</a>
<a class="btn btn--ghost" href="/proposals">Submit a proposal</a>
</div>
{posts[0] && (
<p class="hero-teaser">Latest: <a href={`/news/${posts[0].id}`}>{posts[0].data.title}</a></p>
)}
</div>
</section>
@@ -60,7 +64,7 @@ const services = [
<div class="section-head"><h2>Services</h2></div>
<div class="grid">
{services.map((s) => (
<article class="card tile">
<article class={s.planned ? 'card tile tile--planned' : 'card tile'}>
<svg class="ico" aria-hidden="true" viewBox="0 0 24 24" set:html={s.icon} />
<h3>{s.href
? (s.ext
@@ -68,9 +72,6 @@ const services = [
: <a href={s.href}>{s.title}</a>)
: s.title}</h3>
<p class="desc">{s.desc}</p>
{s.badge === 'mock' && <span class="badge-mock">Mock</span>}
{s.badge === 'ext' && <span class="badge-ext">External — LUX team</span>}
{s.badge === 'tbd' && <span class="badge-ext">Planned — not yet available</span>}
</article>
))}
</div>
+6 -8
View File
@@ -30,14 +30,12 @@ const hrefFor = (n) => (n === 1 ? '/news' : `/news/${n}`);
title={first ? 'CTAO Science Portal — News' : `CTAO Science Portal — News, page ${page.currentPage}`}
description={first ? 'News and announcements of the CTAO' : `News and announcements of the CTAO — page ${page.currentPage} of ${page.lastPage}`}
>
<section class="hero-band">
<div class="band-bg" aria-hidden="true"></div>
<div class="container hero">
<div class="eyebrow">Cherenkov Telescope Array Observatory</div>
<h1>News &amp; Announcements</h1>
<p>Science highlights and updates from the observatory and its partners</p>
</div>
</section>
{/* Section-landing archetype: white head, display Galaxy h1 + standfirst
(the navy band is reserved for / and /login — DESIGN.md). */}
<header class="container page-head">
<h1>News &amp; Announcements</h1>
<p class="standfirst">Science highlights and updates from the observatory and its partners.</p>
</header>
<section class="container page">
<h2 class="sr-only">{first ? 'All news' : `All news — page ${page.currentPage}`}</h2>
+5 -5
View File
@@ -31,15 +31,15 @@ const readMin = Math.max(1, Math.round((post.body ?? '').split(/\s+/).length / 2
description made visible — cards already show it, the article should too. */}
<h1>{post.data.title}</h1>
<p class="standfirst">{post.data.description}</p>
{/* Meta order: category · author · date · reading time */}
<div class="meta"><span class="cat">{post.data.category}</span><span>{post.data.author}</span>·<time datetime={post.data.date.toISOString().slice(0, 10)}>{fmt(post.data.date)}</time>·<span>{readMin} min read</span></div>
{/* Meta — one quiet muted line: category · author · date · reading time */}
<div class="meta"><span class="cat">{post.data.category}</span>·<span>{post.data.author}</span>·<time datetime={post.data.date.toISOString().slice(0, 10)}>{fmt(post.data.date)}</time>·<span>{readMin} min read</span></div>
{post.data.cover && <img class="cover" src={encodeURI(post.data.cover)} alt="" />}
{/* TOC sits between lead image and body (Wikipedia/GOV.UK contents
position): the press head above stays unbroken, and the list is
adjacent to the content it indexes. */}
{toc.length >= 3 && (
<details class="toc toc--inline">
<summary>On this page</summary>
<summary>On this page <svg class="ico" aria-hidden="true" viewBox="0 0 24 24"><path d="m5 9 7 7 7-7" /></svg></summary>
<ol>
{toc.map((h) => (
<li class={h.depth === 3 ? 'd3' : undefined}><a href={`#${h.slug}`}>{h.text}</a></li>
@@ -112,8 +112,8 @@ const readMin = Math.max(1, Math.round((post.body ?? '').split(/\s+/).length / 2
let observer;
const observe = () => {
observer?.disconnect();
// Narrow band below the sticky header (64px) — Starlight's rootMargin trick
const top = 76, band = 64;
// Narrow band below the sticky header (56px) — Starlight's rootMargin trick
const top = 68, band = 64;
observer = new IntersectionObserver(onIntersect, {
rootMargin: `-${top}px 0px ${top + band - document.documentElement.clientHeight}px`,
});
+4 -9
View File
@@ -2,16 +2,11 @@
import Base from '../layouts/Base.astro';
---
<Base title="CTAO Science Portal — Proposals" description="Observation proposal submission (mock)">
<section class="hero-band">
<div class="band-bg" aria-hidden="true"></div>
<div class="container hero">
<div class="eyebrow">Proposal Submission</div>
<h1>Observation proposals</h1>
<p>Submit observation proposals, including ToO and MWL requests</p>
</div>
</section>
<section class="band--moon">
<header class="container page-head">
<h1>Observation proposals</h1>
<p class="standfirst">Submit observation proposals, including ToO and MWL requests.</p>
</header>
<div class="container page">
<span class="badge-mock">Mock — no submission logic</span>
<span class="badge-ext">Planned integration: Proposal Handling System (APC team)</span>
+7 -9
View File
@@ -2,16 +2,14 @@
import Base from '../layouts/Base.astro';
---
<Base title="CTAO Science Portal — Search" description="Search CTAO news and announcements">
<section class="hero-band">
<div class="band-bg" aria-hidden="true"></div>
<div class="container hero">
<div class="eyebrow">Science Portal</div>
<h1>Search</h1>
<p>Find news and announcements</p>
</div>
</section>
{/* Utility archetype: ONE surface per page — Moon canvas from the landing
head down (borderless cards/panels pop against it); editorial pages stay
all-white. No mid-page surface seams (DESIGN.md). */}
<section class="band--moon">
<header class="container page-head">
<h1>Search</h1>
<p class="standfirst">Find news and announcements.</p>
</header>
<div class="container page">
<form class="search-form" role="search" action="/search" method="get">
<label class="sr-only" for="q">Search news</label>
+6 -10
View File
@@ -9,24 +9,20 @@ const channels = [
];
---
<Base title="CTAO Science Portal — Support" description="User support: help desk, FAQ, forum, mailing lists (mock)">
<section class="hero-band">
<div class="band-bg" aria-hidden="true"></div>
<div class="container hero">
<div class="eyebrow">User services</div>
<h1>User Support</h1>
<p>Help desk, FAQ, user forum and mailing lists</p>
</div>
</section>
<section class="band--moon">
<header class="container page-head">
<h1>User Support</h1>
<p class="standfirst">Help desk, FAQ, user forum and mailing lists.</p>
</header>
<div class="container page">
{/* One page-level whisper badge — the tiles below stay clean (they are
not interactive; honesty lives here, at the point users arrive). */}
<span class="badge-mock">Mock — support services not yet connected</span>
<div class="grid">
{channels.map((c) => (
<article class="card tile">
<h3>{c.title}</h3>
<p class="desc">{c.desc}</p>
<span class="badge-mock">Mock</span>
</article>
))}
</div>