fix: final-review findings — own-lead descriptions, PHS integration landing, planned tiles, 404, sitemap math, cleanups

This commit is contained in:
ctao
2026-07-25 13:13:27 +02:00
parent ffe54bc766
commit 3362b214c3
36 changed files with 107 additions and 75 deletions
+20
View File
@@ -0,0 +1,20 @@
---
import Base from '../layouts/Base.astro';
---
<Base title="CTAO Science Portal — Page not found" description="This page does not exist">
{/* Document archetype (DESIGN.md): the document IS the page — h1, one line,
recovery links, and a real GET search form as the way forward. */}
<article class="container article">
<h1>Page not found</h1>
<div class="prose">
<p>The address may be mistyped or the page may have moved — go to the
<a href="/">home page</a> or browse <a href="/news">all news</a>.</p>
</div>
<form class="search-form" role="search" action="/search" method="get">
<label class="sr-only" for="nf-q">Search news</label>
<svg class="ico search-ico" aria-hidden="true" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7" /><path d="m20 20-4.3-4.3" /></svg>
<input id="nf-q" name="q" type="search" placeholder="Search news…" autocomplete="off" />
<button class="btn" type="submit">Search</button>
</form>
</article>
</Base>
+1 -1
View File
@@ -14,7 +14,7 @@ const products = [
];
const fmt = (iso) => new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium' }).format(new Date(iso));
const tickets = [
{ id: '#4821', subject: 'Proposal form: co-I affiliation not saved', status: 'Answered', kind: 'ok' },
{ id: '#4821', subject: 'Question about co-investigator roles', status: 'Answered', kind: 'ok' },
{ id: '#4711', subject: 'Data download quota question', status: 'Open', kind: 'review' },
];
---
+4
View File
@@ -31,6 +31,10 @@ const services = [
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', 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"/>' },
{ title: 'Scheduling', planned: true, desc: 'Public mid- and long-term scheduling information — planned.',
icon: '<rect x="3.5" y="5" width="17" height="15" rx="2"/><path d="M3.5 9.5h17M8 3v4M16 3v4"/>' },
{ title: 'Science alerts', planned: true, desc: 'Public science alert information — planned.',
icon: '<path d="M12 4.5a5.5 5.5 0 0 0-5.5 5.5c0 3.6-1.2 5-1.9 5.7h14.8c-.7-.7-1.9-2.1-1.9-5.7A5.5 5.5 0 0 0 12 4.5z"/><path d="M10 19a2 2 0 0 0 4 0"/>' },
];
---
<Base title="CTAO Science Portal" description="News, observation proposals, data and user services of the Cherenkov Telescope Array Observatory" ambient>
+16 -32
View File
@@ -1,46 +1,30 @@
---
import Base from '../layouts/Base.astro';
---
<Base title="CTAO Science Portal — Proposals" description="Observation proposal submission (mock)">
<Base title="CTAO Science Portal — Proposals" description="Observation proposals — Proposal Handling System integration (planned)">
<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-mock">Mock — PHS not yet connected</span>
<span class="badge-ext">Planned integration: Proposal Handling System (APC team)</span>
{/* Integration landing, not a form: the PHS is APC's product — the spec
(§3.3.3) scopes this portal to integrating it, so we describe the
hand-off instead of imitating their submission UI. */}
<div class="panel panel--narrow">
<div class="form-row">
<label for="f-title">Proposal title</label>
<input id="f-title" type="text" placeholder="e.g. AGN flare observations" />
</div>
<div class="form-row">
<label for="f-scope">Telescope</label>
<select id="f-scope">
<option>LST-1 (Large-Sized Telescope)</option>
<option>MST (Medium-Sized Telescope)</option>
<option>SST (Small-Sized Telescope)</option>
</select>
</div>
<div class="form-row">
<label for="f-energy">Energy range</label>
<select id="f-energy">
<option>20 GeV – 3 TeV</option>
<option>80 GeV – 50 TeV</option>
<option>1 TeV – 300 TeV</option>
</select>
</div>
<div class="form-row">
<label for="f-hours">Requested time (hours)</label>
<input id="f-hours" type="number" min="1" placeholder="e.g. 25" />
</div>
<div class="form-row">
<label for="f-just">Scientific justification</label>
<textarea id="f-just" rows="4" placeholder="Brief description of the observation goal…"></textarea>
</div>
<button class="btn" type="button">Submit proposal</button>
<p class="notice">Demonstration mock — the form does not store any data.</p>
<h2 class="panel-title">Proposal Handling System</h2>
<p>Submitting observation proposals — including Target of Opportunity (ToO)
and multi-wavelength (MWL) requests — happens in the Proposal Handling
System, built by the APC team and integrated into this portal.</p>
<p>What the portal contributes:</p>
<ul class="panel-list">
<li>This menu entry, with a single sign-on hand-off (CTAO AAI).</li>
<li>Proposal status feedback in your <a href="/dashboard">Dashboard</a>.</li>
<li>Notifications via news and announcements.</li>
</ul>
<button class="btn" type="button" disabled>Open the Proposal Handling System — integration planned</button>
</div>
</div>
</section>
+6 -2
View File
@@ -2,7 +2,11 @@
// set is fully known at build time). /admin is intentionally excluded.
import { getCollection } from 'astro:content';
const PAGE_SIZE = 24; // keep in sync with src/pages/news/[...page].astro
// Same chunk math as src/pages/news/[...page].astro (keep in sync): page 1
// holds 25 items (featured lead + 24), later pages 24 — so the page count
// here can never drift from the archive's.
const FIRST = 25, REST = 24;
const newsPageCount = (n) => (n <= FIRST ? 1 : 1 + Math.ceil((n - FIRST) / REST));
export async function GET(context) {
const site = context.site ?? new URL('https://portal.ctao.org');
@@ -12,7 +16,7 @@ export async function GET(context) {
const urls = [
'/', '/news', '/proposals', '/dashboard', '/support', '/search', '/login',
...Array.from({ length: Math.ceil(news.length / PAGE_SIZE) - 1 }, (_, i) => `/news/${i + 2}`),
...Array.from({ length: newsPageCount(news.length) - 1 }, (_, i) => `/news/${i + 2}`),
...pages.map((p) => `/pages/${p.id}`),
].map((path) => ` <url><loc>${new URL(path, site).href}</loc></url>`);
const articles = news.map(
+1 -1
View File
@@ -17,7 +17,7 @@ const channels = [
<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>
<span class="badge-mock">Mock — target: SUSS User Support system</span>
<div class="grid">
{channels.map((c) => (
<article class="card tile">