feat: page composition per IA research — section order, tile icons, cross-page coherence

This commit is contained in:
ctao
2026-07-25 07:53:07 +02:00
parent 419e9a2dd1
commit d014445248
6 changed files with 71 additions and 30 deletions
+30 -1
View File
@@ -190,12 +190,41 @@ snap is worse than a calm fixed width.
| Badges | Quiet hairline pill, muted sentence-case text, Galaxy dot on mocks; "Mock — reason"; never removed | REQUIREMENTS §5, NN/g hierarchy |
| Cards | Cover · title (clamp 3) · desc (clamp 2) · date · read time; no non-differentiating metadata | NN/g metadata |
| Pagination | Newer/Older + windowed numbers, 44px targets, aria-current | NN/g pagination |
| Article | Back "← All news"; meta category · author · date · read time; italic muted captions; TOC at ≥3 headings; post-nav | editorial convention |
| Article | Back "← All news"; h1 → standfirst (description) → meta (category · author · date · read time) → cover → inline TOC → prose; italic muted captions; TOC at ≥3 headings; post-nav | Guardian/BBC anatomy |
| Forms | Visible labels above fields; placeholders are examples only; buttons start with a verb | GOV.UK forms, NN/g |
| Login | One line + button + one small-print line; no reassurance prose | auth brevity |
| Footer | Identity line → Contact · Search · Disclaimer · Privacy · Site settings · Content editor → © line; no implementation talk in UI copy | NN/g footers |
| Search results | Links lay out as flowing text (mark-safe); date chip; "Press Enter for all results" hint | — |
## Page composition (archetypes)
Every page follows one of three archetypes; a new page picks one instead of
inventing a section order.
| Archetype | Pages | Order |
|---|---|---|
| Portal landing | `/` | Hero (h1 + subtitle + CTA pair + one-line Latest teaser) → Services tiles (Moon) → Latest news 3-up + "All news" (white) |
| Section landing | `/news`, `/search`, `/proposals`, `/dashboard`, `/support` | Galaxy hero band carrying the h1 + one-line subtitle → single content band (Moon = utility, white = editorial listing) → footer |
| Document | `/news/<slug>`, `/pages/<slug>` | Back link → h1 → standfirst → meta → cover → inline TOC → prose (→ post-nav on articles); no hero band — the document IS the page |
- Home is task-first (NN/g homepage principles: clear starting points for the
top 1–4 tasks — for scientists that's proposals/data/support, not reading
news). The hero teaser is the freshness signal (deep-links the newest
article); the 3-card section below is the browse layer — different
granularity, not redundant.
- Document order is press anatomy (Guardian/BBC/Reuters): headline →
standfirst → byline/meta → lead image → body. The inline TOC sits between
lead image and body (Wikipedia/GOV.UK contents position) so the editorial
head stays unbroken and the list is adjacent to what it indexes.
- Back affordances: articles "← All news", static pages "← Home" (footer- and
deep-link-reached documents must not be dead ends). Auth (`/login`) is the
sole exception to the archetypes: one focused card, no hero, no sections.
- Service tiles carry a stroke glyph (`.ico` family: currentColor, round caps,
aria-hidden; 26px Galaxy) ABOVE the label — never instead of it (NN/g icon
usability: icons are ambiguous alone; beside an always-visible label they
differentiate otherwise-uniform tiles and speed grid scanning). Glyphs are
wayfinding, not decoration — they don't count against the one-accent budget.
## Do not
- No new hues, tints, or grays — derive via `color-mix` from brand tokens only.
-9
View File
@@ -48,15 +48,6 @@ const links = [
</head>
<body class={ambient ? 'has-ambient' : undefined}>
<a class="skip" href="#main">Skip to content</a>
{/* Demo disclosure — GOV.UK phase-banner pattern: one slim, honest banner
instead of implementation talk scattered through the UI. Not sticky;
it scrolls away and the header sticks below it. */}
<div class="phase-banner">
<div class="container">
<strong class="phase-tag">Demo</strong>
<span>This is a demonstration of the CTAO Science Portal — content is sample data.</span>
</div>
</div>
<header class="site-header">
<div class="container nav">
<a class="brand" href="/" aria-label="CTAO Science Portal — home">
+23 -7
View File
@@ -11,13 +11,23 @@ const iso = (d) => d.toISOString().slice(0, 10);
// 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.
// 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: 'Data Explorer', href: 'https://padc-ctao-data-explorer.obspm.fr/', ext: true, desc: 'Search and download CTAO science data products.', badge: 'ext' },
{ title: 'Dashboard', href: '/dashboard', desc: 'Your proposals, data products and support tickets in one place.', badge: 'mock' },
{ title: 'User Support', href: '/support', desc: 'Help desk, FAQ, user forum and mailing lists.', badge: 'mock' },
{ title: 'Software', desc: 'Science analysis tools and pipelines.', badge: 'tbd' },
{ title: 'Documentation', desc: 'User guides and technical documentation.', badge: 'tbd' },
{ title: 'Proposals', href: '/proposals', desc: 'Submit observation proposals, including ToO / MWL requests.', badge: 'mock',
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',
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',
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',
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',
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',
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>
@@ -39,13 +49,19 @@ const services = [
</div>
</section>
{/* Surface rhythm (DESIGN.md): services on Moon Gray, editorial news on white */}
{/* Section order is task-first: Services directly under the hero, news after.
NN/g homepage principles — the homepage must give clear starting points
for the user's top tasks; for scientists that is proposals/data/support,
not reading news. Freshness is still signalled above the fold by the
one-line hero teaser, so the 3-card news section can sit below.
Surface rhythm (DESIGN.md): services on Moon Gray, editorial news on white. */}
<section class="band--moon">
<div class="container page">
<div class="section-head"><h2>Services</h2></div>
<div class="grid">
{services.map((s) => (
<article class="card tile">
<svg class="ico" aria-hidden="true" viewBox="0 0 24 24" set:html={s.icon} />
<h3>{s.href
? (s.ext
? <a class="external" href={s.href} target="_blank" rel="noopener">{s.title}</a>
+8 -1
View File
@@ -26,9 +26,17 @@ const readMin = Math.max(1, Math.round((post.body ?? '').split(/\s+/).length / 2
<div class="container article-layout">
<article class="article">
<a class="back" href="/news">← All news</a>
{/* Editorial anatomy (Guardian/BBC/Reuters convention): headline →
standfirst → byline/meta → lead image → body. The standfirst is the
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>
{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>
@@ -39,7 +47,6 @@ const readMin = Math.max(1, Math.round((post.body ?? '').split(/\s+/).length / 2
</ol>
</details>
)}
{post.data.cover && <img class="cover" src={encodeURI(post.data.cover)} alt="" />}
<div class="prose">
<Content />
</div>
+3
View File
@@ -12,6 +12,9 @@ const { Content } = await render(page);
---
<Base title={`${page.data.title} — CTAO Science Portal`} description={page.data.description}>
<article class="container article">
{/* Static pages are footer-reached documents; same back affordance as
articles ("← All news") so deep links aren't dead ends. */}
<a class="back" href="/">← Home</a>
<h1>{page.data.title}</h1>
<div class="prose">
<Content />
+7 -12
View File
@@ -127,15 +127,6 @@ img { max-width: 100%; height: auto; display: block; }
official logo, never a CSS approximation. Budget: one accent per view; static. */
.flash { width: 12px; height: 12px; color: var(--cherenkov); flex: none; }
/* Demo disclosure — GOV.UK phase-banner pattern: slim Moon bar above the
header (scrolls away; the header sticks below it). Galaxy-on-tint pairs
computed ≥16:1 (same pairing as .cat chips). */
.phase-banner { background: var(--moon); border-bottom: 1px solid var(--border); font-size: 0.8rem; }
.phase-banner .container { display: flex; align-items: center; gap: 10px; padding-top: 6px; padding-bottom: 6px; }
.phase-tag {
flex: none; font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--galaxy); background: var(--tint-cherenkov); border-radius: var(--radius-pill); padding: 2px 10px;
}
/* Header — Galaxy Blue band, reverse (white) logo per BRAND B.1.2 */
.site-header { background: var(--galaxy); position: sticky; top: 0; z-index: 10; }
@@ -404,8 +395,10 @@ a.page-step { color: var(--link); }
span.page-step { color: var(--muted); } /* disabled end stop — non-interactive */
.page-gap { display: inline-flex; align-items: center; justify-content: center; min-width: 24px; min-height: 44px; color: var(--muted); }
/* Service tiles — card tokens, text-first */
/* Service tiles — card tokens; stroke glyph above an always-visible label
(NN/g: icons differentiate and speed scanning only when labelled) */
.tile { padding: var(--space-m); gap: 8px; }
.tile .ico { width: 26px; height: 26px; stroke-width: 1.75; stroke-linejoin: round; color: var(--galaxy); }
.tile h3 { margin: 0; }
.tile .badge-mock, .tile .badge-ext { margin: auto 0 0; align-self: flex-start; }
@@ -422,8 +415,10 @@ span.page-step { color: var(--muted); } /* disabled end stop — non-interactive
color: var(--muted); font-size: 0.9rem; margin-bottom: var(--space-l);
}
.article .cover { border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: var(--space-l); }
/* Lead paragraph — slightly larger and muted */
.prose > p:first-of-type { font-size: 1.08rem; color: var(--muted); }
/* Standfirst — the description shown as the visible lead under the headline
(Guardian/BBC anatomy). Replaces the old first-prose-paragraph lead styling
so an article never shows two competing leads. */
.article .standfirst { margin: 0 0 var(--space-s); font-size: 1.12rem; line-height: 1.55; color: var(--muted); }
/* Article TOC (MDN/Stripe pattern, rendered only when ≥3 h2/h3): sticky right rail
on wide screens, collapsed <details> under the title otherwise. */
.toc {