em dashes out of skeleton copy (titles use ' - ', prose reworded); split hardening after review: poll errors name the failing repo, failed release is not retried until a new commit, link-content checks all three dirs, editor write-access note, two-repo unit descriptions and comments

This commit is contained in:
2026-09-03 19:41:56 +02:00
parent 13f945fbf3
commit 751fd0ec79
21 changed files with 54 additions and 42 deletions
+4
View File
@@ -65,6 +65,10 @@ missing). Everything else runs inside containers.
10. **[R]** Verify: `curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/` 10. **[R]** Verify: `curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/`
and `journalctl --user -u ctao-portal-build -n 20` (shows measured build times). and `journalctl --user -u ctao-portal-build -n 20` (shows measured build times).
Editor accounts need WRITE access to `ctao/content` (Gitea → repo →
Collaborators, or a team) — public read alone lets them sign in but every
save fails.
## CMS sign-in (Sveltia ↔ Gitea OAuth) ## CMS sign-in (Sveltia ↔ Gitea OAuth)
- Gitea OAuth2 app (PKCE, `confidential_client=false`, no secret) with - Gitea OAuth2 app (PKCE, `confidential_client=false`, no secret) with
+13 -8
View File
@@ -43,18 +43,22 @@ head_of() {
code_sha=$(head_of "$CODE_REPO") code_sha=$(head_of "$CODE_REPO")
content_sha=$(head_of "$CONTENT_REPO") content_sha=$(head_of "$CONTENT_REPO")
# Gitea down/unreachable is a transient, not a unit failure — exit 0 quietly # Gitea down/unreachable is a transient, not a unit failure — exit 0 quietly
# instead of painting the journal red every 10 s. # instead of painting the journal red every 10 s. Name the repo: a 404 here
for sha in "$code_sha" "$content_sha"; do # also means "repo/branch missing or renamed", not just "Gitea down".
if [[ ! "$sha" =~ ^[0-9a-f]{40}$ ]]; then [[ "$code_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CODE_REPO@$BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; }
echo "poll failed (gitea unreachable?) — skipping this tick" [[ "$content_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CONTENT_REPO@$BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; }
exit 0
fi
done
release="${code_sha:0:12}-${content_sha:0:12}" # code+content pin the release release="${code_sha:0:12}-${content_sha:0:12}" # code+content pin the release
# Skip only if this pair is both recorded AND still present in releases/ # Skip only if this pair is both recorded AND still present in releases/
# (a deleted release dir must trigger a rebuild, not an eternal skip). # (a deleted release dir must trigger a rebuild, not an eternal skip).
[[ "$release" == "$(cat "$BASE/state/last-built" 2>/dev/null)" \ [[ "$release" == "$(cat "$BASE/state/last-built" 2>/dev/null)" \
&& -d "$BASE/releases/$release" ]] && exit 0 && -d "$BASE/releases/$release" ]] && exit 0
# A release that already failed is not retried until either repo moves —
# otherwise one bad commit (e.g. broken frontmatter) turns into a full
# rebuild every 10 s on a shared VM. The failure is loud once, then quiet.
if [[ "$release" == "$(cat "$BASE/state/last-failed" 2>/dev/null)" ]]; then
echo "skipping $release — build failed before; push a fix to retry"
exit 0
fi
echo "building code=$code_sha content=$content_sha" echo "building code=$code_sha content=$content_sha"
t0=$(date +%s) t0=$(date +%s)
@@ -106,7 +110,8 @@ podman run --rm --network="$BUILD_NETNS" --memory=1g \
npm run build npm run build
rm -rf "../releases/$RELEASE" rm -rf "../releases/$RELEASE"
cp -a dist "../releases/$RELEASE" cp -a dist "../releases/$RELEASE"
' ' || { echo "$release" > "$BASE/state/last-failed"; echo "BUILD FAILED for $release (see above) — will not retry until a new commit"; exit 1; }
rm -f "$BASE/state/last-failed"
# --- 3. Atomic publish: symlink flip via rename(2) — no half-published moment. # --- 3. Atomic publish: symlink flip via rename(2) — no half-published moment.
# mv -T is GNU (the target host is Rocky); when testing on macOS put a # mv -T is GNU (the target host is Rocky); when testing on macOS put a
+1 -1
View File
@@ -4,7 +4,7 @@
# activate a service that is still running. # activate a service that is still running.
[Unit] [Unit]
Description=CTAO portal demo — rebuild if the content repo has new commits Description=CTAO portal demo — rebuild if the code or content repo has new commits
[Service] [Service]
Type=oneshot Type=oneshot
+1 -1
View File
@@ -6,7 +6,7 @@
# OnBootSec covers reboot. Enable: systemctl --user enable --now ctao-portal-build.timer # OnBootSec covers reboot. Enable: systemctl --user enable --now ctao-portal-build.timer
[Unit] [Unit]
Description=CTAO portal demo — poll the content repo every 10 s Description=CTAO portal demo — poll the code and content repos every 10 s
[Timer] [Timer]
OnActiveSec=5 OnActiveSec=5
+1 -1
View File
@@ -1,6 +1,6 @@
# Sveltia CMS config — backend: Gitea behind the Cyfronet ingress (HTTPS # Sveltia CMS config — backend: Gitea behind the Cyfronet ingress (HTTPS
# vhost). Editors log in via Gitea OAuth (PKCE, no client secret); edits are # vhost). Editors log in via Gitea OAuth (PKCE, no client secret); edits are
# committed to the ctao/portal repo. Gitea's ROOT_URL and CORS are pinned to # committed to the ctao/content repo. Gitea's ROOT_URL and CORS are pinned to
# these origins in deploy/ctao-demo-gitea.container. # these origins in deploy/ctao-demo-gitea.container.
backend: backend:
name: gitea name: gitea
+1 -1
View File
@@ -112,7 +112,7 @@
const unit = opts.unit || 'result'; const unit = opts.unit || 'result';
status.textContent = hits.length status.textContent = hits.length
? hits.length + ' ' + unit + (hits.length === 1 ? '' : 's') + ' for “' + input.value.trim() + '”' ? hits.length + ' ' + unit + (hits.length === 1 ? '' : 's') + ' for “' + input.value.trim() + '”'
: 'No results for “' + input.value.trim() + '” — try a shorter or different term.'; : 'No results for “' + input.value.trim() + '”. Try a shorter or different term.';
// Off-DOM build + atomic swap (no transient page-height collapse) // Off-DOM build + atomic swap (no transient page-height collapse)
const frag = document.createDocumentFragment(); const frag = document.createDocumentFragment();
for (const p of hits) { for (const p of hits) {
+3 -1
View File
@@ -6,7 +6,9 @@
set -euo pipefail set -euo pipefail
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
src="${1:-../ctao-content}" src="${1:-../ctao-content}"
[ -d "$src/news" ] || { echo "content clone not found at $src (git clone <gitea>/ctao/content.git first)"; exit 1; } for d in news pages uploads; do
[ -d "$src/$d" ] || { echo "content clone incomplete: $src/$d missing (git clone <gitea>/ctao/content.git first)"; exit 1; }
done
rm -rf src/content/news src/content/pages public/uploads rm -rf src/content/news src/content/pages public/uploads
mkdir -p src/content public mkdir -p src/content public
cp -a "$src/news" src/content/news cp -a "$src/news" src/content/news
+3 -2
View File
@@ -14,8 +14,9 @@ const news = defineCollection({
date: z.coerce.date(), date: z.coerce.date(),
category: z.string().default('news'), category: z.string().default('news'),
author: z.string().default('CTAO'), author: z.string().default('CTAO'),
// Public-path string, e.g. "/uploads/foo.jpg" — Sveltia uploads media to // Public-path string, e.g. "/uploads/foo.jpg" — media lives in the
// public/uploads, which Astro's image() helper can't validate (src/ only). // content repo's uploads/, overlaid to public/uploads at build time,
// which Astro's image() helper can't validate (src/ only).
cover: z.string().optional(), cover: z.string().optional(),
// BCP-47 tag when an article is not in English (e.g. "pl") — set as lang // BCP-47 tag when an article is not in English (e.g. "pl") — set as lang
// on the <article> element so screen readers pick the right voice. // on the <article> element so screen readers pick the right voice.
+5 -5
View File
@@ -2,7 +2,7 @@
import '../styles/global.css'; import '../styles/global.css';
// `ambient` opts a page into the whole-page drifting brand wash (home only — see DESIGN.md) // `ambient` opts a page into the whole-page drifting brand wash (home only — see DESIGN.md)
// `type`/`image` feed the social meta: articles pass type="article" + their cover. // `type`/`image` feed the social meta: articles pass type="article" + their cover.
const { title = 'CTAO Science Portal', description = 'CTAO Science Portal — demo', ambient = false, type = 'website', image } = Astro.props; const { title = 'CTAO Science Portal', description = 'CTAO Science Portal demo', ambient = false, type = 'website', image } = Astro.props;
// Absolute URLs for canonical/OG/RSS (head pattern from the official Astro // Absolute URLs for canonical/OG/RSS (head pattern from the official Astro
// blog template). `site` comes from astro.config.mjs and is always set. // blog template). `site` comes from astro.config.mjs and is always set.
const site = Astro.site; const site = Astro.site;
@@ -43,7 +43,7 @@ const links = [
<meta property="og:url" content={canonical} /> <meta property="og:url" content={canonical} />
<meta property="og:image" content={ogImage} /> <meta property="og:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<link rel="alternate" type="application/rss+xml" title="CTAO Science Portal — News" href={new URL('/rss.xml', site)} /> <link rel="alternate" type="application/rss+xml" title="CTAO Science Portal News" href={new URL('/rss.xml', site)} />
<link rel="sitemap" href="/sitemap.xml" /> <link rel="sitemap" href="/sitemap.xml" />
<link rel="icon" href="/brand/CTAO_Logo_positive.svg" type="image/svg+xml" /> <link rel="icon" href="/brand/CTAO_Logo_positive.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
@@ -59,7 +59,7 @@ const links = [
<a class="skip" href="#main">Skip to content</a> <a class="skip" href="#main">Skip to content</a>
<header class="site-header"> <header class="site-header">
<div class="container nav"> <div class="container nav">
<a class="brand" href="/" aria-label="CTAO Science Portal — home"> <a class="brand" href="/" aria-label="CTAO Science Portal, home">
<img src="/brand/AAFF_CTAO_Logo_RGB_Monochrome-negative.svg" alt="CTAO" /> <img src="/brand/AAFF_CTAO_Logo_RGB_Monochrome-negative.svg" alt="CTAO" />
<span class="brand-sub">Science&nbsp;Portal</span> <span class="brand-sub">Science&nbsp;Portal</span>
</a> </a>
@@ -164,7 +164,7 @@ const links = [
<div class="container"> <div class="container">
<div class="foot-top"> <div class="foot-top">
<img src="/brand/AAFF_CTAO_Logo_RGB_Monochrome-negative.svg" alt="CTAO" /> <img src="/brand/AAFF_CTAO_Logo_RGB_Monochrome-negative.svg" alt="CTAO" />
<p>CTAO Science Portal — demo by Cyfronet (SUSS-PORT team).</p> <p>CTAO Science Portal demo by Cyfronet (SUSS-PORT team).</p>
</div> </div>
{/* Order: action/utility first, legal middle, meta last (GOV.UK/NN-g {/* Order: action/utility first, legal middle, meta last (GOV.UK/NN-g
footer anatomy). Spec §3.3.1 requires the five items, not an order. */} footer anatomy). Spec §3.3.1 requires the five items, not an order. */}
@@ -183,7 +183,7 @@ const links = [
{/* The real brand flash — path taken from the official logo SVG (BRAND D.4). {/* The real brand flash — path taken from the official logo SVG (BRAND D.4).
Sole decorative accent in this view; static by design. */} Sole decorative accent in this view; static by design. */}
<svg class="flash" viewBox="85.3 15 15 15" aria-hidden="true"><path fill="currentColor" d="M94.7,23.8c1-1.4,3.3-2.1,5.2-2.7c-2,0.1-4.4,0.3-5.8-0.6c-1.4-1-2.1-3.3-2.7-5.2c0.1,2,0.3,4.4-0.6,5.8c-1,1.4-3.3,2.1-5.2,2.7c2-0.1,4.4-0.3,5.8,0.6c1.4,1,2.1,3.3,2.7,5.2C93.9,27.6,93.7,25.2,94.7,23.8z"/></svg> <svg class="flash" viewBox="85.3 15 15 15" aria-hidden="true"><path fill="currentColor" d="M94.7,23.8c1-1.4,3.3-2.1,5.2-2.7c-2,0.1-4.4,0.3-5.8-0.6c-1.4-1-2.1-3.3-2.7-5.2c0.1,2,0.3,4.4-0.6,5.8c-1,1.4-3.3,2.1-5.2,2.7c2-0.1,4.4-0.3,5.8,0.6c1.4,1,2.1,3.3,2.7,5.2C93.9,27.6,93.7,25.2,94.7,23.8z"/></svg>
<span>© 2026 CTAO — demonstration, not an official service.</span> <span>© 2026 CTAO. Demonstration, not an official service.</span>
</div> </div>
</div> </div>
</footer> </footer>
+3 -3
View File
@@ -1,7 +1,7 @@
--- ---
import Base from '../layouts/Base.astro'; import Base from '../layouts/Base.astro';
--- ---
<Base title="CTAO Science Portal — Page not found" description="This page does not exist"> <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, {/* Document archetype (DESIGN.md): the document IS the page — h1, one line,
recovery links, and a real GET search form as the way forward. */} recovery links, and a real GET search form as the way forward. */}
<div class="container article-layout"> <div class="container article-layout">
@@ -10,8 +10,8 @@ import Base from '../layouts/Base.astro';
<div class="prose"> <div class="prose">
{/* Keep "the <a>" on one line: the compiler drops the whitespace at a {/* Keep "the <a>" on one line: the compiler drops the whitespace at a
line break before a tag, which glued "the" to the link text. */} line break before a tag, which glued "the" to the link text. */}
<p>The address may be mistyped or the page may have moved — <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> Go to the <a href="/">home page</a> or browse <a href="/news">all news</a>.</p>
</div> </div>
<form class="search-form" role="search" action="/search" method="get"> <form class="search-form" role="search" action="/search" method="get">
<label class="sr-only" for="nf-q">Search news</label> <label class="sr-only" for="nf-q">Search news</label>
+1 -1
View File
@@ -11,7 +11,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>CTAO — Content Editor</title> <title>CTAO Content Editor</title>
<meta name="theme-color" content="#00004A" /> <meta name="theme-color" content="#00004A" />
<link rel="icon" href="/brand/CTAO_Logo_positive.svg" type="image/svg+xml" /> <link rel="icon" href="/brand/CTAO_Logo_positive.svg" type="image/svg+xml" />
<style> <style>
+1 -1
View File
@@ -4,7 +4,7 @@ import Base from '../layouts/Base.astro';
// and help-desk tickets. No integrations exist yet, so panels show empty // and help-desk tickets. No integrations exist yet, so panels show empty
// states instead of fabricated sample rows (source-documents-only rule). // states instead of fabricated sample rows (source-documents-only rule).
--- ---
<Base title="CTAO Science Portal — Dashboard" description="User dashboard (mock)"> <Base title="CTAO Science Portal - Dashboard" description="User dashboard (mock)">
<section class="band--moon"> <section class="band--moon">
<header class="container page-head"> <header class="container page-head">
<h1>Dashboard</h1> <h1>Dashboard</h1>
+2 -2
View File
@@ -25,9 +25,9 @@ const services = [
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"/>' }, 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"/>' },
// Two subdued planned tiles complete the 2×3 grid (an orphan 4th tile in a // Two subdued planned tiles complete the 2×3 grid (an orphan 4th tile in a
// 3-col grid reads broken); the remaining TBD services stay on the quiet line. // 3-col grid reads broken); the remaining TBD services stay on the quiet line.
{ title: 'Software', planned: true, desc: 'Landing page for CTAO software — planned.', { title: 'Software', planned: true, desc: 'Landing page for CTAO software (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"/>' }, 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: 'CTAO user documentation — planned.', { title: 'Documentation', planned: true, desc: 'CTAO user 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"/>' }, 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"/>' },
]; ];
const planned = ['Scheduling', 'Science alerts']; const planned = ['Scheduling', 'Science alerts'];
+2 -2
View File
@@ -1,14 +1,14 @@
--- ---
import Base from '../layouts/Base.astro'; import Base from '../layouts/Base.astro';
--- ---
<Base title="CTAO Science Portal — Sign in" description="Sign in via CTAO AAI (mock)"> <Base title="CTAO Science Portal - Sign in" description="Sign in via CTAO AAI (mock)">
<section class="auth-band"> <section class="auth-band">
<div class="band-bg" aria-hidden="true"></div> <div class="band-bg" aria-hidden="true"></div>
<div class="auth-card"> <div class="auth-card">
<img class="auth-logo" src="/brand/CTAO_Logo_positive.svg" alt="CTAO" /> <img class="auth-logo" src="/brand/CTAO_Logo_positive.svg" alt="CTAO" />
<h1>Sign in</h1> <h1>Sign in</h1>
{/* Auth pages are scanned, not read — one line, no reassurance prose */} {/* Auth pages are scanned, not read — one line, no reassurance prose */}
<p class="muted">Use your CTAO account — single sign-on via CTAO AAI.</p> <p class="muted">Use your CTAO account (single sign-on via CTAO AAI).</p>
<button class="btn btn-wide" type="button">Continue with your CTAO account</button> <button class="btn btn-wide" type="button">Continue with your CTAO account</button>
<p class="notice">No account? <a>Register via CTAO AAI</a></p> <p class="notice">No account? <a>Register via CTAO AAI</a></p>
</div> </div>
+3 -3
View File
@@ -45,8 +45,8 @@ for (let n = 1; n <= page.lastPage; n++) {
const hrefFor = (n) => (n === 1 ? '/news' : `/news/${n}`); const hrefFor = (n) => (n === 1 ? '/news' : `/news/${n}`);
--- ---
<Base <Base
title={first ? 'CTAO Science Portal — News' : `CTAO Science Portal — News, page ${page.currentPage}`} 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}`} description={first ? 'News and announcements of the CTAO' : `News and announcements of the CTAO, page ${page.currentPage} of ${page.lastPage}`}
> >
{/* Section-landing archetype: white head, display Galaxy h1 + standfirst {/* Section-landing archetype: white head, display Galaxy h1 + standfirst
(the navy band is reserved for / and /login — DESIGN.md). */} (the navy band is reserved for / and /login — DESIGN.md). */}
@@ -56,7 +56,7 @@ const hrefFor = (n) => (n === 1 ? '/news' : `/news/${n}`);
</header> </header>
<section class="container page"> <section class="container page">
<h2 class="sr-only">{first ? 'All news' : `All news — page ${page.currentPage}`}</h2> <h2 class="sr-only">{first ? 'All news' : `All news, page ${page.currentPage}`}</h2>
<div class="grid"> <div class="grid">
{page.data.map((post, i) => <NewsCard post={post} featured={first && i === 0} />)} {page.data.map((post, i) => <NewsCard post={post} featured={first && i === 0} />)}
</div> </div>
+1 -1
View File
@@ -20,7 +20,7 @@ const { Content, headings } = await render(post);
const toc = headings.filter((h) => h.depth === 2 || h.depth === 3); const toc = headings.filter((h) => h.depth === 2 || h.depth === 3);
const readMin = readMinOf(post); const readMin = readMinOf(post);
--- ---
<Base title={`${post.data.title} — CTAO`} description={post.data.description} type="article" image={post.data.cover && encodeURI(post.data.cover)}> <Base title={`${post.data.title} - CTAO`} description={post.data.description} type="article" image={post.data.cover && encodeURI(post.data.cover)}>
{/* Reading progress (scroll-driven CSS, no JS) — styled only where {/* Reading progress (scroll-driven CSS, no JS) — styled only where
animation-timeline is supported; elsewhere it stays an empty div. */} animation-timeline is supported; elsewhere it stays an empty div. */}
<div class="read-progress" aria-hidden="true"></div> <div class="read-progress" aria-hidden="true"></div>
+1 -1
View File
@@ -10,7 +10,7 @@ export async function getStaticPaths() {
const { page } = Astro.props; const { page } = Astro.props;
const { Content } = await render(page); const { Content } = await render(page);
--- ---
<Base title={`${page.data.title} — CTAO Science Portal`} description={page.data.description}> <Base title={`${page.data.title} - CTAO Science Portal`} description={page.data.description}>
{/* Same wrapper pattern as news/[slug] (never .container and .article on {/* Same wrapper pattern as news/[slug] (never .container and .article on
one element — both set max-width and would depend on rule order). */} one element — both set max-width and would depend on rule order). */}
<div class="container article-layout"> <div class="container article-layout">
+5 -5
View File
@@ -1,7 +1,7 @@
--- ---
import Base from '../layouts/Base.astro'; import Base from '../layouts/Base.astro';
--- ---
<Base title="CTAO Science Portal — Proposals" description="Observation proposals — Proposal Handling System integration (planned)"> <Base title="CTAO Science Portal - Proposals" description="Observation proposals: Proposal Handling System integration (planned)">
<section class="band--moon"> <section class="band--moon">
<header class="container page-head"> <header class="container page-head">
<h1>Observation proposals</h1> <h1>Observation proposals</h1>
@@ -13,15 +13,15 @@ import Base from '../layouts/Base.astro';
hand-off instead of imitating their submission UI. */} hand-off instead of imitating their submission UI. */}
<div class="panel panel--narrow"> <div class="panel panel--narrow">
<h2 class="panel-title">Proposal Handling System</h2> <h2 class="panel-title">Proposal Handling System</h2>
<p>Submitting observation proposals — including Target of Opportunity (ToO) <p>Submitting observation proposals, including Target of Opportunity (ToO)
and multi-wavelength (MWL) requests — happens in the Proposal Handling and multi-wavelength (MWL) requests, happens in the Proposal Handling
System, built by the APC team and integrated into this portal.</p> System. That system is built by the APC team and integrated into this portal.</p>
<p>What the portal contributes:</p> <p>What the portal contributes:</p>
<ul class="panel-list"> <ul class="panel-list">
<li>This menu entry, with a single sign-on hand-off (CTAO AAI).</li> <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>Proposal status feedback in your <a href="/dashboard">Dashboard</a>.</li>
</ul> </ul>
<button class="btn" type="button" disabled>Open the Proposal Handling System — integration planned</button> <button class="btn" type="button" disabled>Open the Proposal Handling System (integration planned)</button>
</div> </div>
</div> </div>
</section> </section>
+1 -1
View File
@@ -29,7 +29,7 @@ export async function GET(context) {
const xml = `<?xml version="1.0" encoding="UTF-8"?> const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel> <channel>
<title>CTAO Science Portal — News</title> <title>CTAO Science Portal News</title>
<link>${new URL('/news', site).href}</link> <link>${new URL('/news', site).href}</link>
<description>News and announcements of the Cherenkov Telescope Array Observatory</description> <description>News and announcements of the Cherenkov Telescope Array Observatory</description>
<language>en</language> <language>en</language>
+1 -1
View File
@@ -1,7 +1,7 @@
--- ---
import Base from '../layouts/Base.astro'; import Base from '../layouts/Base.astro';
--- ---
<Base title="CTAO Science Portal — Search" description="Search CTAO news and announcements"> <Base title="CTAO Science Portal - Search" description="Search CTAO news and announcements">
{/* Utility archetype: ONE surface per page — Moon canvas from the landing {/* Utility archetype: ONE surface per page — Moon canvas from the landing
head down (borderless cards/panels pop against it); editorial pages stay head down (borderless cards/panels pop against it); editorial pages stay
all-white. No mid-page surface seams (DESIGN.md). */} all-white. No mid-page surface seams (DESIGN.md). */}
+1 -1
View File
@@ -8,7 +8,7 @@ const channels = [
{ title: 'Mailing lists', desc: 'Announcements and topical mailing lists.' }, { title: 'Mailing lists', desc: 'Announcements and topical mailing lists.' },
]; ];
--- ---
<Base title="CTAO Science Portal — Support" description="User support: help desk, FAQ, forum, mailing lists (mock)"> <Base title="CTAO Science Portal - Support" description="User support: help desk, FAQ, forum, mailing lists (mock)">
<section class="band--moon"> <section class="band--moon">
<header class="container page-head"> <header class="container page-head">
<h1>User Support</h1> <h1>User Support</h1>