diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7bc9d0f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.sh text eol=lf +*.service text eol=lf +*.timer text eol=lf +*.container text eol=lf diff --git a/DESIGN.md b/DESIGN.md deleted file mode 100644 index 5ff106d..0000000 --- a/DESIGN.md +++ /dev/null @@ -1,38 +0,0 @@ -# Design handoff - -All styling lives in **`src/styles/global.css`**: design tokens in `:root` -(colors, type scale, spacing, radius, shadows, `--header-h`) + plain-CSS -components below them. No frameworks, no preprocessors, no runtime JS for -presentation. Pages are `.astro` templates (plain HTML). - -## Brand (official: CTAO Brand Guidelines, April 2024, PDF in the project docs) - -- Colors: Galaxy Blue `#00004A`, Cherenkov Blue `#00E4D8`, Moon Gray - `#F5F5F5`, Cosmic Azure `#007AFF` (focus rings only; links use the - darkened `--link` so they pass AA on white), Interstellar Indigo `#00009C`. - Cherenkov is an ACCENT (the brand book rations it; currently: primary CTA, - hero subtitle, logo flash); it fails AA as text on white. -- Comments in the code cite `SPEC §…`, `BRAND D.x` and `REQUIREMENTS`: those - are the CTAO specification, the brand guidelines PDF and the team's - consolidated requirements, kept in the project's document folder at - Cyfronet, not in this repo. -- Type: **Inter** for everything, **Space Grotesk** (weight 500) only for - communicative headlines (h1/h2). Sizes come from the `--fs-*` scale. -- Logo files in `public/brand/`; always "CTAO", min 100px wide, no effects. - -## Practical notes (the non-obvious ones) - -- **`public/admin/preview.css` mirrors the `.prose` styles** so the CMS - editor preview matches articles 1:1. When you touch `.prose` or its - tokens, update the mirror (both files carry a KEEP IN SYNC comment). -- Fonts ship **inlined as data: URIs** in global.css (no font requests, no - swap flash). The woff2 files in `public/fonts/` look unused but feed the - CMS preview, so keep them. -- All `animation`/`transition` rules sit in the single - `prefers-reduced-motion: no-preference` block at the end of global.css. -- Scroll-driven effects need `vite.build.cssMinify: 'esbuild'` - (astro.config.mjs): the default minifier breaks `animation-timeline`. -- `--header-h` drives the sticky-header offsets (anchors, TOC rail, reading - progress); change the header height only through the token. -- Deterministic gate before pushing: `npm run check` (types + build + link - check, a few seconds). diff --git a/README.md b/README.md index 70d029b..f31967d 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,110 @@ -# CTAO Science Portal: static site + git-based CMS +# CTAO Science Portal — simple proof of concept -Public portal for CTAO news, built as a fully static site. Editors get a -browser WYSIWYG editor (Sveltia CMS) that commits Markdown to a Gitea repo; -a build job turns commits into static HTML. No application or database runs -on the public path. +The `simple` branch uses the sibling `mockup` project's layout, assets, CSS, +and header interactions. Astro generates a static site. Sveltia CMS edits +exactly four Markdown pages in the separate `ctao/content` repository. -Live demo: · -Gitea on the demo machine: +## Scope -## Repository layout & deployment +| Example submenu | URL | Content file | +|---|---|---| +| Lorem ipsum | `/example/lorem-ipsum/` | `pages/lorem-ipsum.md` | +| Dolor sit amet | `/example/dolor-sit-amet/` | `pages/dolor-sit-amet.md` | +| Consectetur adipiscing | `/example/consectetur-adipiscing/` | `pages/consectetur-adipiscing.md` | +| Sed do eiusmod | `/example/sed-do-eiusmod/` | `pages/sed-do-eiusmod.md` | -Code and content are SEPARATE repositories, so editorial commits never mix -with development: +Home, News, Data, Proposals, Dashboard, Sign in, Search, Support, Privacy, +Disclaimer, and Contact have the shared header/footer, a title, and an empty +body. Search submits to its placeholder. Only English is available; the +other language options are inactive. There are no news articles, news +pagination, RSS feeds, search index, service integrations, or public sign-in. +`/admin/` remains the working CMS entry point. -| Repo | Holds | Source of truth | Who commits | -|---|---|---|---| -| `ctao-portal` (this one, Bitbucket) | Templates, CSS, deploy | development | the team | -| `ctao/content` (Gitea on the demo machine) | `news/`, `pages/`, `uploads/` | editorial | Sveltia CMS / editors | +## Run locally -The demo machine polls ITS Gitea (`ctao/portal` mirror + `ctao/content`) and -republishes on a push to either, so a push to Bitbucket does NOT deploy; -deploying code = pushing it to the machine's Gitea (a deliberate step). The -build overlays content onto code (`deploy/build.sh`); the content paths are -gitignored here. +Use Node 22.19 or newer (deployment uses Node 24). -## Stack - -- **Astro** (static output). Pages are `.astro` templates (plain HTML with a - JS frontmatter block). No React/Vue/Svelte components, no client-side - framework. -- **Plain CSS**: everything lives in `src/styles/global.css` as design - tokens (custom properties) + rules. No Tailwind, no preprocessor. - The design system (tokens, type scale, spacing, motion policy) is - documented in `DESIGN.md`. Read it before touching styles. -- **Sveltia CMS**: a single prebuilt JS bundle, vendored in - `public/vendor/sveltia-cms.js` and loaded on `/admin/` - (`src/pages/admin/index.astro`), configured by `public/admin/config.yml`. - It runs entirely in the editor's browser and talks to the Gitea API - (OAuth PKCE). There is no CMS server. To update it: - `curl -sL https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js -o public/vendor/sveltia-cms.js` -- Runtime dependencies: none beyond Astro. This is deliberate; keep it - that way. - -## Develop - -Prerequisites: Node 22.19 or newer (the machine builds on Node 24), and -network access to the demo machine's Gitea (Cyfronet network / VPN) for the -content clone below. Without content the site still builds, but with no -articles and `npm run check` reports the footer's page links as broken. - -``` +```sh npm ci -git clone https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git ../ctao-content -./scripts/link-content.sh # copies news/pages/uploads into the dev tree -npm run dev # http://localhost:4321 -npm run build # static output in dist/ -npm run check # deterministic gate: astro check (types) + build + internal-link check +npm run dev ``` -`tsconfig.json` excludes `public/` on purpose: the vendored CMS bundle there is -2 MB of minified JS and would make `astro check` run out of memory. +Open `http://localhost:4321/example/lorem-ipsum/` for the full mockup example. +The first run copies bundled `sample-content/pages/` and `uploads/` into the +ignored runtime directories. Existing content is preserved. An incomplete +existing set fails with a missing-file error instead of silently mixing +sample content with editorial content. -## Layout +```sh +npm run content:setup # explicitly restore the four sample pages +npm run content:sync -- ../ctao-content # copy from a separate content checkout +npm run check # types, static build, internal links +npm run build +npm run preview # inspect the built site +``` -| Path | What | +`content:setup` and `content:sync` replace the four runtime Markdown files and +copy uploads, overwriting same-named media. They do not modify the source +content checkout. Only the four allowlisted pages are loaded and rendered; +unrelated Markdown files never create public routes. + +## Edit with Sveltia locally, without Gitea + +```sh +npm run content:local +npm run dev +``` + +In Chrome or Edge, open `/admin/`, select **Work with Local Repository**, and +choose this project's `.tmp/content` directory. The setup command creates a +local Git repository there, seeded from the bundled content. Re-running it +preserves edits in that directory. + +Save an Example page, then run this in another terminal to refresh the site: + +```sh +npm run content:sync -- .tmp/content +``` + +The local editor writes files without committing them. This follows +[Sveltia's local workflow](https://sveltiacms.app/en/docs/workflows/local). +Choose the content repository root, not the portal code repository root: +the CMS expects `pages/` and `uploads/` directly beneath it. + +## Content and publishing + +The CMS uses a [fixed file collection](https://sveltiacms.app/en/docs/collections/files). +Editors can change title, introduction, optional image/alternative text/caption, +Markdown body, and optional button label/destination. Both button fields are +needed to display the button. A Markdown blockquote receives the mockup's +highlight styling. Images without alternative text are treated as decorative. +Menu labels, ordering, URLs, and the shared site layout remain code-controlled. + +Remote editing uses Gitea OAuth and saves to **`ctao/content`, branch `simple`**. +That branch must contain the four files from `sample-content/pages/` and the +sample upload before publishing this version. The code mirror is +**`ctao/portal`, branch `simple`**. Deployment independently selects +`CODE_BRANCH` and `CONTENT_BRANCH`, overlays content, runs checks, and publishes +atomically. `CONTENT_BRANCH` must match `backend.branch` in the CMS config. + +See [deploy/README.md](deploy/README.md) for migration and publishing steps. +A push to Bitbucket alone does not deploy the site. + +## Where things live + +| Path | Purpose | |---|---| -| `src/pages/` | Routes (`.astro` templates), incl. `news/`, `search`, RSS/sitemap | -| `src/layouts/Base.astro` | HTML shell: head, header/nav, footer | -| `src/content/news/*.md` | Articles (from the content repo, gitignored here; schema in `src/content.config.ts`) | -| `src/styles/global.css` | All CSS: tokens + components + prose | -| `DESIGN.md` | Design handoff: where the tokens live, brand basics, practical notes | -| `public/admin/` | CMS config (`config.yml`) + editor preview styles (`preview.css`) | -| `public/uploads/` | Editor-uploaded media (from the content repo, gitignored here) | -| `deploy/` | Runbook + container/systemd units for the demo machine, see `deploy/README.md` | +| `src/layouts/Base.astro` | Mockup header, navigation, footer, page shell | +| `src/components/PageHero.astro` | Breadcrumb, title, introduction, nebula | +| `src/pages/example/[slug].astro` | One reusable editable-page template | +| `src/data/examples.json` | Fixed submenu labels, order, and slugs | +| `src/data/placeholders.json` | Empty destinations | +| `src/styles/global.css` | Mockup styles plus Markdown/placeholder variants | +| `public/assets/`, `public/header.js` | Mockup assets and menu behavior | +| `sample-content/` | Versioned seed content for local use and Gitea setup | +| `src/content/pages/`, `public/uploads/` | Ignored runtime content overlay | +| `public/admin/config.yml` | Four fixed CMS files and Gitea configuration | +| `scripts/preview-styles.mjs` | Generates CMS body preview styles from site CSS | -## Editing content - -Editors use `/admin/` (link in the footer) and sign in with a Gitea account. -Saving commits to `main`; the machine polls and republishes automatically -(seconds). Full history/rollback = git history in Gitea. Articles with -`draft: true` are excluded from the build. - -Note for styling work: `public/admin/preview.css` mirrors the `.prose` rules -from `global.css` so the editor preview matches the site 1:1; keep them in -sync (both files carry a KEEP IN SYNC comment). - -## Upgrading - -Versions are pinned exactly; an upgrade is a deliberate change, never a side -effect of `npm install`. Astro ships security fixes only for the current and -one previous major, so plan one upgrade per major rather than skipping several. - -``` -npx @astrojs/upgrade # moves astro and official integrations together -npm run check # types + build + links must pass -npm run preview # verify the real dist/ output, not the dev server -``` - -No experimental flags in `astro.config.mjs`: they can change in minor releases. +The vendored CMS bundle remains in `public/vendor/sveltia-cms.js`. Dependencies +remain pinned; this simplification adds no application dependencies. diff --git a/astro.config.mjs b/astro.config.mjs index 1116727..1641feb 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,31 +1,9 @@ import { defineConfig } from 'astro/config'; -// Static output (zero runtime) — the whole point of the git-based approach. export default defineConfig({ output: 'static', - // Public origin — used to build absolute URLs (canonical, og:*, RSS, - // sitemap). Change when the portal moves to its production domain. site: 'https://astro.isl-dev.grid.cyfronet.pl', - // URL contract, pinned explicitly (these are the defaults): changing either - // later rewrites every public URL. nginx serves /path/ via try_files $uri/. - trailingSlash: 'ignore', + trailingSlash: 'always', build: { format: 'directory' }, - // Lossless whitespace handling instead of the v7 default 'jsx', which drops - // line breaks around inline elements and silently glues words to links when - // a template line wraps before . Templates render as written. compressHTML: true, - // Built-in prefetch on every internal link (no per-link attributes needed). - // Default 'hover' strategy: near-instant navigation without the bandwidth - // cost of 'viewport' on a 24-card grid; auto-falls back to 'tap' on - // data-saver / slow connections (Astro handles that). - prefetch: { prefetchAll: true }, - vite: { - build: { - // Lightning CSS (the default minifier) illegally folds animation-timeline - // INTO the `animation` shorthand (`animation: ... view()`), which browsers - // reject — silently killing every scroll-driven effect in production. - // esbuild minifies without property merging. - cssMinify: 'esbuild', - }, - }, }); diff --git a/deploy/README.md b/deploy/README.md index 4943cd6..a3cadba 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -29,6 +29,37 @@ missing). Everything else runs inside containers. | 3000 | Gitea | https://astro-git.isl-dev.grid.cyfronet.pl (ingress vhost) | | 8080 | portal (nginx, static) | https://astro.isl-dev.grid.cyfronet.pl (ingress vhost) | +## Switch an existing demo to `simple` + +1. In a checkout of `ctao/content`, create a `simple` branch from the existing + content branch. Copy `sample-content/pages/*.md` and + `sample-content/uploads/hero.jpg` from this code repository into its root + `pages/` and `uploads/` directories, then commit the seed files. Existing + news or other page files can remain in the content history; this portal + loads only the four Example files. +2. Push the content branch to Gitea as `ctao/content:simple`, and the code + branch as `ctao/portal:simple`. These are separate pushes; Bitbucket is + not the deployment remote. +3. Copy the updated `build.sh` to `~/ctao-portal-demo/bin/build.sh` and the + updated `ctao-portal-build.service` to `~/.config/systemd/user/`. Reload + systemd with `systemctl --user daemon-reload`. The service explicitly sets + `CODE_BRANCH=simple` and `CONTENT_BRANCH=simple`; the script defaults agree. +4. Start the build service or let its timer run. It overlays `pages/` and + `uploads/`, clears the previous news overlay, and runs `npm run check` + before the atomic release switch. Missing Example pages fail the build. +5. Verify the four `/example/…/` pages and empty `/news/`. Sign in at `/admin/`, + save an Example title, and check that a commit appears on + `ctao/content:simple` and the rebuilt page reflects it. + +The OAuth client and public portal origin stay the same. Sveltia's +`backend.branch` in `public/admin/config.yml` must always match +`CONTENT_BRANCH`. Branches are selected independently; a single `BRANCH` +environment variable is no longer used. For a separate preview hostname, +update the site origin, OAuth redirect, and Gitea CORS together. + +To roll back, restore the previous deployed release and the previous worker +script/service configuration. The original content `main` branch is retained. + ## Install (each step reviewed before running; [W] = writes to the machine) 1. **[W]** `loginctl enable-linger $USER`. Without lingering every user unit @@ -53,10 +84,12 @@ missing). Everything else runs inside containers. password never lands in a file or shell history: `podman exec -it ctao-demo-gitea gitea admin user create --admin --username --email --random-password` 7. **[W]** In the Gitea UI: create org `ctao` with repos `portal` and - `content` (both public read). Then, from your workstation, push both over + `content` (both public read). Seed the content `simple` branch with + `sample-content/pages/` and `sample-content/uploads/` as described above. + Then, from your workstation, push both over the vhost with a repo-scoped token: - `git push https://:@astro-git.isl-dev.grid.cyfronet.pl/ctao/portal.git main` - `git push https://:@astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git main` + `git push https://:@astro-git.isl-dev.grid.cyfronet.pl/ctao/portal.git simple` + `git push https://:@astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git simple` 8. **[W]** `systemctl --user enable --now ctao-portal-build.timer`. The first run clones + `npm ci` + builds (minutes); later runs are seconds. Wait until `journalctl --user -u ctao-portal-build -n 5` shows `published ` diff --git a/deploy/build.sh b/deploy/build.sh index 85980e5..211e8f8 100755 --- a/deploy/build.sh +++ b/deploy/build.sh @@ -7,7 +7,7 @@ # # Two repositories by design: code (templates/CSS, developed by the team) and # content (Markdown + uploads, committed by the CMS). The build overlays -# content onto code, so an editor publishing an article and a developer +# content onto code, so an editor publishing a page and a developer # shipping CSS never mix histories — either change republishes the site. set -euo pipefail @@ -17,7 +17,8 @@ BASE="${BASE:-$HOME/ctao-portal-demo}" GITEA_URL="${GITEA_URL:-http://localhost:3000}" # published by ctao-demo-gitea CODE_REPO="${CODE_REPO:-ctao/portal}" # owner/repo in Gitea CONTENT_REPO="${CONTENT_REPO:-ctao/content}" -BRANCH="${BRANCH:-main}" +CODE_BRANCH="${CODE_BRANCH:-simple}" +CONTENT_BRANCH="${CONTENT_BRANCH:-simple}" BUILD_IMAGE="${BUILD_IMAGE:-localhost/ctao-portal-build:1}" # The build joins the Gitea container's network namespace: localhost inside # the build = Gitea's loopback (port 3000), host loopback stays unreachable. @@ -37,16 +38,16 @@ done # --- 1. Cheap poll: both branch heads via the local Gitea API --- head_of() { - curl -fsS --max-time 5 "$GITEA_URL/api/v1/repos/$1/branches/$BRANCH" \ + curl -fsS --max-time 5 "$GITEA_URL/api/v1/repos/$1/branches/$2" \ | jq -r '.commit.id' || true } -code_sha=$(head_of "$CODE_REPO") -content_sha=$(head_of "$CONTENT_REPO") +code_sha=$(head_of "$CODE_REPO" "$CODE_BRANCH") +content_sha=$(head_of "$CONTENT_REPO" "$CONTENT_BRANCH") # Gitea down/unreachable is a transient, not a unit failure — exit 0 quietly # instead of painting the journal red every 10 s. Name the repo: a 404 here # also means "repo/branch missing or renamed", not just "Gitea down". -[[ "$code_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CODE_REPO@$BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; } -[[ "$content_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CONTENT_REPO@$BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; } +[[ "$code_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CODE_REPO@$CODE_BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; } +[[ "$content_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CONTENT_REPO@$CONTENT_BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; } 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/ # (a deleted release dir must trigger a rebuild, not an eternal skip). @@ -73,7 +74,7 @@ podman run --rm --network="$BUILD_NETNS" --memory=1g \ -e ASTRO_TELEMETRY_DISABLED=1 \ -e CODE_SHA="$code_sha" -e CODE_URL="$CODE_URL" \ -e CONTENT_SHA="$content_sha" -e CONTENT_URL="$CONTENT_URL" \ - -e RELEASE="$release" -e BRANCH="$BRANCH" \ + -e RELEASE="$release" -e CODE_BRANCH="$CODE_BRANCH" -e CONTENT_BRANCH="$CONTENT_BRANCH" \ -v "$BASE/repo:/work/repo:z" \ -v "$BASE/content:/work/content:z" \ -v "$BASE/releases:/work/releases:z" \ @@ -81,21 +82,20 @@ podman run --rm --network="$BUILD_NETNS" --memory=1g \ -w /work "$BUILD_IMAGE" sh -ec ' git config --global safe.directory "/work/repo" git config --global --add safe.directory "/work/content" - sync_clone() { # $1 dir $2 url $3 sha - [ -d "$1/.git" ] || git clone --branch "$BRANCH" "$2" "$1" + sync_clone() { # $1 dir $2 url $3 sha $4 branch + [ -d "$1/.git" ] || git clone --branch "$4" "$2" "$1" git -C "$1" remote set-url origin "$2" # self-heal if the URL changes - git -C "$1" fetch --quiet origin "$BRANCH" + git -C "$1" fetch --quiet origin "$4" # --force: the working copy is disposable; a stray tracked-file edit # must not wedge every future build. git -C "$1" checkout --quiet --force "$3" } - sync_clone repo "$CODE_URL" "$CODE_SHA" - sync_clone content "$CONTENT_URL" "$CONTENT_SHA" + sync_clone repo "$CODE_URL" "$CODE_SHA" "$CODE_BRANCH" + sync_clone content "$CONTENT_URL" "$CONTENT_SHA" "$CONTENT_BRANCH" # Overlay content onto code (these paths are gitignored in the code repo). # mkdir -p: checkout prunes the emptied parent dirs, cp needs them back. rm -rf repo/src/content/news repo/src/content/pages repo/public/uploads mkdir -p repo/src/content repo/public - cp -a content/news repo/src/content/news cp -a content/pages repo/src/content/pages cp -a content/uploads repo/public/uploads cd repo @@ -109,7 +109,9 @@ podman run --rm --network="$BUILD_NETNS" --memory=1g \ npm ci --ignore-scripts --no-audit --no-fund echo "$lock" > .deps-hash fi - npm run build + # Validate the overlay and links before publishing; never seed demo content + # in place of an incomplete content branch. + npm run check rm -rf "../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; } diff --git a/deploy/ctao-portal-build.service b/deploy/ctao-portal-build.service index 5f905ab..145bb24 100644 --- a/deploy/ctao-portal-build.service +++ b/deploy/ctao-portal-build.service @@ -8,6 +8,8 @@ Description=CTAO portal demo — rebuild if the code or content repo has new com [Service] Type=oneshot +Environment=CODE_BRANCH=simple +Environment=CONTENT_BRANCH=simple ExecStart=%h/ctao-portal-demo/bin/build.sh # Polite neighbour on the shared VM: Nice=10 diff --git a/package-lock.json b/package-lock.json index 5fa8b3c..f9de21e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,9 @@ "devDependencies": { "@astrojs/check": "0.9.10", "typescript": "6.0.3" + }, + "engines": { + "node": ">=22.19.0" } }, "node_modules/@astrojs/check": { diff --git a/package.json b/package.json index ea43c3d..da6cf7b 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,15 @@ "version": "0.1.0", "private": true, "scripts": { + "content:setup": "node scripts/content.mjs", + "content:sync": "node scripts/content.mjs", + "content:local": "node scripts/local-content.mjs", + "predev": "node scripts/content.mjs --ensure && node scripts/preview-styles.mjs", "dev": "astro dev", + "prebuild": "node scripts/content.mjs --ensure && node scripts/preview-styles.mjs", "build": "astro build", "preview": "astro preview", + "precheck": "node scripts/content.mjs --ensure && node scripts/preview-styles.mjs", "check": "astro check && astro build && node scripts/check-links.mjs" }, "dependencies": { diff --git a/public/admin/config.yml b/public/admin/config.yml index 5f96dcf..ca1c1c1 100644 --- a/public/admin/config.yml +++ b/public/admin/config.yml @@ -4,10 +4,10 @@ # these origins in deploy/ctao-demo-gitea.container. backend: name: gitea - # Content lives in its OWN repo (editorial history separate from code — - # the build overlays it onto the portal code, see deploy/build.sh). + # Keep this branch in sync with CONTENT_BRANCH in deploy/build.sh. + # Seed the content branch from sample-content/ before deploying. repo: ctao/content - branch: main + branch: simple base_url: https://astro-git.isl-dev.grid.cyfronet.pl api_root: https://astro-git.isl-dev.grid.cyfronet.pl/api/v1 app_id: cf1c44ac-ebe6-4a97-bf2f-f7f26ef1126c @@ -45,36 +45,58 @@ media_libraries: optimize: true collections: - - name: news - label: "News" - label_singular: "Article" - folder: "news" - create: true - slug: "{{slug}}" - extension: md - format: frontmatter - summary: "{{title}} · {{date}}" - fields: - - { name: title, label: "Title", widget: string } - - { name: description, label: "Short description", widget: text } - - { name: date, label: "Date", widget: datetime, date_format: "YYYY-MM-DD", time_format: false } - - { name: category, label: "Category", widget: string, default: "news" } - - { name: author, label: "Author", widget: string, default: "CTAO" } - - { name: cover, label: "Cover image", widget: image, required: false } - - { name: lang, label: "Language (if not English, e.g. pl)", widget: string, required: false } - - { name: draft, label: "Draft (unpublished)", widget: boolean, default: false } - - { name: body, label: "Body", widget: markdown } - - - name: pages - label: "Pages" - label_singular: "Page" - folder: "pages" - create: true - slug: "{{slug}}" - extension: md - format: frontmatter - summary: "{{title}}" - fields: - - { name: title, label: "Title", widget: string } - - { name: description, label: "Description", widget: text, required: false } - - { name: body, label: "Body", widget: markdown } + - name: examples + label: "Example pages" + files: + - name: lorem-ipsum + label: "Lorem ipsum" + file: "pages/lorem-ipsum.md" + format: frontmatter + fields: + - { name: title, label: "Title", widget: string } + - { name: introduction, label: "Introduction", widget: text, required: false } + - { name: image, label: "Image", widget: image, required: false } + - { name: imageAlt, label: "Image alternative text", widget: string, required: false, hint: "Describe the image; leave empty only for decorative images." } + - { name: imageCaption, label: "Image caption", widget: string, required: false } + - { name: body, label: "Body", widget: markdown } + - { name: buttonLabel, label: "Button label", widget: string, required: false } + - { name: buttonUrl, label: "Button destination", widget: string, required: false, hint: "A site path such as /example/lorem-ipsum/, or an https:// URL." } + - name: dolor-sit-amet + label: "Dolor sit amet" + file: "pages/dolor-sit-amet.md" + format: frontmatter + fields: + - { name: title, label: "Title", widget: string } + - { name: introduction, label: "Introduction", widget: text, required: false } + - { name: image, label: "Image", widget: image, required: false } + - { name: imageAlt, label: "Image alternative text", widget: string, required: false, hint: "Describe the image; leave empty only for decorative images." } + - { name: imageCaption, label: "Image caption", widget: string, required: false } + - { name: body, label: "Body", widget: markdown } + - { name: buttonLabel, label: "Button label", widget: string, required: false } + - { name: buttonUrl, label: "Button destination", widget: string, required: false, hint: "A site path such as /example/lorem-ipsum/, or an https:// URL." } + - name: consectetur-adipiscing + label: "Consectetur adipiscing" + file: "pages/consectetur-adipiscing.md" + format: frontmatter + fields: + - { name: title, label: "Title", widget: string } + - { name: introduction, label: "Introduction", widget: text, required: false } + - { name: image, label: "Image", widget: image, required: false } + - { name: imageAlt, label: "Image alternative text", widget: string, required: false, hint: "Describe the image; leave empty only for decorative images." } + - { name: imageCaption, label: "Image caption", widget: string, required: false } + - { name: body, label: "Body", widget: markdown } + - { name: buttonLabel, label: "Button label", widget: string, required: false } + - { name: buttonUrl, label: "Button destination", widget: string, required: false, hint: "A site path such as /example/lorem-ipsum/, or an https:// URL." } + - name: sed-do-eiusmod + label: "Sed do eiusmod" + file: "pages/sed-do-eiusmod.md" + format: frontmatter + fields: + - { name: title, label: "Title", widget: string } + - { name: introduction, label: "Introduction", widget: text, required: false } + - { name: image, label: "Image", widget: image, required: false } + - { name: imageAlt, label: "Image alternative text", widget: string, required: false, hint: "Describe the image; leave empty only for decorative images." } + - { name: imageCaption, label: "Image caption", widget: string, required: false } + - { name: body, label: "Body", widget: markdown } + - { name: buttonLabel, label: "Button label", widget: string, required: false } + - { name: buttonUrl, label: "Button destination", widget: string, required: false, hint: "A site path such as /example/lorem-ipsum/, or an https:// URL." } diff --git a/public/admin/preview.css b/public/admin/preview.css index f445703..cb82dd6 100644 --- a/public/admin/preview.css +++ b/public/admin/preview.css @@ -1,71 +1,232 @@ -/* Entry-preview styles for the Sveltia editor, registered via the documented - CMS.registerPreviewStyle API (bare element selectors, per the official docs - example). This file MIRRORS the article styles 1:1 — the design tokens from - :root and the `.prose` block in src/styles/global.css — with `.prose` - dropped from selectors (the preview pane has no such wrapper). - KEEP IN SYNC: when you touch `.prose` or the tokens in global.css, update - the corresponding value here (a matching reminder sits next to `.prose`). - Fonts: same self-hosted woff2 files the site uses, same origin. (The - Sveltia bundle still loads its own UI fonts from jsDelivr — only the - PREVIEW pane is CDN-free.) */ +/* Generated by scripts/preview-styles.mjs; edit src/styles/global.css. */ +/* ========================================================================== + CTAO Science Portal: content page mockup + Colours and type follow the CTAO Brand Guidelines (April 2024). + ========================================================================== */ + +/* Fonts + ========================================================================== */ + @font-face { - font-family: "Inter"; font-weight: 400 700; font-display: swap; - src: url("/fonts/inter-latin.woff2") format("woff2"); + font-family: "Inter"; + font-weight: 400 700; + font-display: swap; + src: url("/assets/fonts/inter-latin.woff2") format("woff2"); } + @font-face { - font-family: "Space Grotesk"; font-weight: 500 700; font-display: swap; - src: url("/fonts/space-grotesk-latin.woff2") format("woff2"); + font-family: "Space Grotesk"; + font-weight: 500 700; + font-display: swap; + src: url("/assets/fonts/space-grotesk-latin.woff2") format("woff2"); } +/* Tokens + ========================================================================== */ + :root { - /* tokens copied from global.css :root */ - --galaxy: #00004a; - --moon: #f5f5f5; - --text: #101228; - --muted: #46536a; - --border: #e2e5ee; - --link: #0057c2; - --font-body: "Inter", Arial, system-ui, sans-serif; - --font-display: "Space Grotesk", var(--font-body); - --fs-s: 0.9rem; - --fs-l: 1.125rem; - --fs-xl: 1.5rem; - --fs-h2: clamp(1.75rem, 3.5vw, 2.5rem); - --radius: 16px; + /* Brand main colours (D.2.1) */ + --galaxy-blue: #00004a; + --cherenkov-blue: #00e4d8; + --moon-gray: #f5f5f5; + + /* Brand secondary colours (D.2.2) */ + --white: #ffffff; + --cosmic-azure: #007aff; + --interstellar-indigo: #00009c; + + /* Text */ + --text: #111111; + --text-muted: #555555; + /* Cosmic Azure at 84% brightness: 5.4:1 on white, 3.5:1 against body text */ + --link: #0066d6; + + /* Type: Inter for everything, Space Grotesk for headlines (D.3) */ + --font-body: "Inter", Arial, sans-serif; + --font-head: "Space Grotesk", Arial, sans-serif; + + /* Layout */ + --content-width: 85rem; + --gutter: clamp(1rem, 4vw, 2.5rem); + --header-height: 7rem; + --header-height-scrolled: 5rem; + --radius-lg: 60px; + --radius-md: 30px; + --radius-sm: 15px; + /* Dropdown submenus (Example, language) */ + --radius-menu: 8px; + --transition: 0.3s ease-in-out; + + /* Film grain tile over the hero background, also hides gradient banding */ + --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='.05'/%3E%3C/svg%3E"); +} + +/* Base + ========================================================================== */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + scroll-padding-top: var(--header-height-scrolled); } -/* preview shell ≈ article column */ body { + margin: 0; font-family: var(--font-body); + font-size: 1rem; + line-height: 1.5; color: var(--text); - max-width: 70ch; - margin: 0 auto; - padding: 24px; + background: var(--white); + -webkit-font-smoothing: antialiased; } -/* = global.css base heading/link rules = */ -h1, h2 { font-family: var(--font-display); font-weight: 500; letter-spacing: -0.025em; line-height: 1.1; } -h3 { font-weight: 600; } -h1, h2, h3 { overflow-wrap: break-word; text-wrap: balance; } -a { color: var(--link); text-decoration: underline; text-underline-offset: 3px; } - -/* = global.css `.prose` rules, selectors unwrapped = */ -body { line-height: 1.65; overflow-wrap: break-word; } -a { overflow-wrap: anywhere; } -h1 { color: var(--galaxy); font-size: var(--fs-h2); } /* mirrors `.article h1` */ -h2 { color: var(--galaxy); margin: 44px 0 12px; font-size: var(--fs-xl); } -h3 { color: var(--galaxy); margin: 30px 0 8px; font-size: var(--fs-l); } -p { margin: 0 0 16px; text-wrap: pretty; } -ul, ol { margin: 0 0 16px; padding-left: 22px; } -li { margin: 6px 0; } -blockquote { - margin: 22px 0; padding: 12px 20px; border-left: 3px solid var(--galaxy); - background: var(--moon); color: var(--text); +body.no-scroll { + overflow: hidden; } -img { display: block; max-width: 100%; height: auto; border: 1px solid var(--border); border-radius: var(--radius); } -img + em, p > em:only-child { color: var(--muted); font-size: var(--fs-s); } -hr { border: 0; border-top: 1px solid var(--border); margin: 32px 0; } -table { display: block; width: 100%; overflow-x: auto; border-collapse: collapse; margin: 0 0 16px; font-size: var(--fs-s); } -th { text-align: left; font-weight: 600; color: var(--galaxy); } -th, td { padding: 10px 12px; border-bottom: 1px solid var(--border); } -thead th { border-bottom: 2px solid var(--galaxy); } + +img { + display: block; + max-width: 100%; + height: auto; +} + +a { + color: var(--link); + text-decoration: none; +} + +a:hover, +a:focus-visible { + text-decoration: underline; + text-underline-offset: 0.15em; +} + +button { + padding: 0; + font: inherit; + color: inherit; + background: none; + border: 0; + cursor: pointer; +} + +/* Focus ring: Link blue on light backgrounds, Cherenkov Blue on dark ones */ +:focus-visible { + outline: 2px solid var(--link); + outline-offset: 3px; +} + +.site-header :focus-visible, +.page-hero :focus-visible, +.site-footer :focus-visible { + outline-color: var(--cherenkov-blue); +} + +/* Prose + ========================================================================== */ + +body { + padding-bottom: 5rem; +} + +body > * { + max-width: 64rem; +} + +/* Galaxy Blue headline on light background (D.3.4) */ +body h2 { + margin: 3rem 0 1rem; + font-family: var(--font-head); + font-size: clamp(1.75rem, 3vw, 2.375rem); + font-weight: 500; + line-height: 1.1; + letter-spacing: -0.02em; + color: var(--galaxy-blue); +} + +body h2:first-child { + margin-top: 0; +} + +body p { + margin: 0 0 1.25rem; + font-size: 1.0625rem; + line-height: 1.6; +} + +/* Text links: colour only at rest, underline on hover and focus */ +body a:not(.btn):hover, +body a:not(.btn):focus-visible { + text-decoration: underline; + text-underline-offset: 0.15em; +} + +/* Cherenkov Blue fails contrast as text on white, so it is the accent bar */ +body .highlight, +body blockquote { + margin: 2.5rem 0; + padding-left: 1.5rem; + font-size: 1.375rem; + font-weight: 500; + line-height: 1.35; + color: var(--galaxy-blue); + border-left: 4px solid var(--cherenkov-blue); +} + +/* Buttons + ========================================================================== */ + +.btn { + display: inline-flex; + align-items: center; + gap: 0.75rem; + height: 2.75rem; + padding: 0 1.5rem; + font-size: 0.9375rem; + font-weight: 700; + border-radius: 999px; +} + +/* Underline the label only: a flex container would pass it on to the arrow */ +.btn:hover, +.btn:focus-visible { + text-decoration: none; +} + +.btn:hover .btn__label, +.btn:focus-visible .btn__label { + text-decoration: underline; + text-underline-offset: 0.15em; +} + +.btn--primary { + color: var(--white); + background: linear-gradient( + 90deg, + var(--galaxy-blue) 0%, + var(--cosmic-azure) 70%, + var(--cherenkov-blue) 100% + ); +} + +.btn--primary:hover { + filter: brightness(1.1); +} + +body { overflow-wrap: anywhere; } +body h3, body h4, body h5, body h6 { color: var(--galaxy-blue); font-family: var(--font-head); } +body li { margin-bottom: 0.5rem; } +body blockquote p { font-size: inherit; line-height: inherit; } +body pre { padding: 1rem; overflow-x: auto; background: var(--moon-gray); } +body code { overflow-wrap: normal; } +body table { display: block; overflow-x: auto; border-collapse: collapse; } +body th, body td { padding: 0.75rem; border-bottom: 1px solid #ddd; text-align: left; } +body hr { margin: 2rem 0; border: 0; border-top: 1px solid #ddd; } +body img { border-radius: var(--radius-sm); } + +body { display: block; min-height: 0; max-width: 64rem; margin: 0 auto; padding: 1.5rem; } +h1 { color: var(--galaxy-blue); font-family: var(--font-head); font-weight: 500; line-height: 1.05; } diff --git a/public/assets/fonts/inter-latin.woff2 b/public/assets/fonts/inter-latin.woff2 new file mode 100644 index 0000000..91dc3e8 Binary files /dev/null and b/public/assets/fonts/inter-latin.woff2 differ diff --git a/public/assets/fonts/space-grotesk-latin.woff2 b/public/assets/fonts/space-grotesk-latin.woff2 new file mode 100644 index 0000000..7b0e76a Binary files /dev/null and b/public/assets/fonts/space-grotesk-latin.woff2 differ diff --git a/public/assets/logo-negative.svg b/public/assets/logo-negative.svg new file mode 100644 index 0000000..d61e30e --- /dev/null +++ b/public/assets/logo-negative.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/logo-positive.svg b/public/assets/logo-positive.svg new file mode 100644 index 0000000..88eeb49 --- /dev/null +++ b/public/assets/logo-positive.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/logo-sp-negative.svg b/public/assets/logo-sp-negative.svg new file mode 100644 index 0000000..46af0bf --- /dev/null +++ b/public/assets/logo-sp-negative.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/public/assets/logo-sp-positive.svg b/public/assets/logo-sp-positive.svg new file mode 100644 index 0000000..f83cbe1 --- /dev/null +++ b/public/assets/logo-sp-positive.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/public/assets/nebula.svg b/public/assets/nebula.svg new file mode 100644 index 0000000..3d8ddb5 --- /dev/null +++ b/public/assets/nebula.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/header.js b/public/header.js new file mode 100644 index 0000000..3eb09fa --- /dev/null +++ b/public/header.js @@ -0,0 +1,151 @@ +/* + * CTAO portal mockup: header behaviour + * + * 1. The sticky header turns solid Galaxy Blue after 40px of scroll. + * 2. Submenus: every button with data-submenu-toggle (Example, language) + * toggles the element named in its aria-controls. One open at a time. + * 3. The search button toggles the search panel. + * 4. The burger button toggles the main nav on small screens. + * 5. Escape and clicks outside the header close open menus. + * + * Review helper: index.html#open= opens a panel on load, + * for example #open=submenu-example, #open=submenu-lang, + * #open=search-panel or #open=site-nav. + */ +(function () { + "use strict"; + + // Keep in sync with the small-screen media query in css/styles.css + const MOBILE_QUERY = window.matchMedia("(max-width: 1079px)"); + + const header = document.getElementById("site-header"); + const nav = document.getElementById("site-nav"); + const searchPanel = document.getElementById("search-panel"); + const searchButton = header.querySelector('[aria-controls="search-panel"]'); + const menuButton = header.querySelector('[aria-controls="site-nav"]'); + const submenuButtons = Array.from( + header.querySelectorAll("[data-submenu-toggle]"), + ); + + /* 1. Scrolled state */ + + function updateScrolled() { + header.classList.toggle("is-scrolled", window.scrollY > 40); + } + + window.addEventListener("scroll", updateScrolled, { passive: true }); + updateScrolled(); + + /* Keeps the header solid while a full-width panel is open */ + function updateHeaderState() { + const panelOpen = + !searchPanel.hidden || header.classList.contains("is-nav-open"); + header.classList.toggle("is-open", panelOpen); + } + + /* 2. Submenus */ + + function setSubmenu(button, open) { + const submenu = document.getElementById( + button.getAttribute("aria-controls"), + ); + button.setAttribute("aria-expanded", String(open)); + submenu.hidden = !open; + } + + function closeSubmenus(except) { + submenuButtons.forEach(function (button) { + if (button !== except) setSubmenu(button, false); + }); + } + + submenuButtons.forEach(function (button) { + button.addEventListener("click", function () { + const open = button.getAttribute("aria-expanded") !== "true"; + closeSubmenus(button); + setSearch(false); + setSubmenu(button, open); + }); + }); + + /* 3. Search */ + + function setSearch(open) { + searchButton.setAttribute("aria-expanded", String(open)); + searchPanel.hidden = !open; + updateHeaderState(); + if (open) searchPanel.querySelector("input").focus(); + } + + searchButton.addEventListener("click", function () { + const open = searchPanel.hidden; + closeSubmenus(); + setMobileNav(false); + setSearch(open); + }); + + /* 4. Mobile nav */ + + function setMobileNav(open) { + menuButton.setAttribute("aria-expanded", String(open)); + header.classList.toggle("is-nav-open", open); + document.body.classList.toggle("no-scroll", open); + updateHeaderState(); + } + + menuButton.addEventListener("click", function () { + const open = !header.classList.contains("is-nav-open"); + closeSubmenus(); + setSearch(false); + setMobileNav(open); + }); + + /* 5. Dismiss */ + + function closeAll() { + closeSubmenus(); + setSearch(false); + setMobileNav(false); + } + + document.addEventListener("click", function (event) { + if (!header.contains(event.target)) closeAll(); + }); + + document.addEventListener("keydown", function (event) { + if (event.key !== "Escape") return; + + const openSubmenuButton = submenuButtons.find(function (button) { + return button.getAttribute("aria-expanded") === "true"; + }); + + if (openSubmenuButton) { + setSubmenu(openSubmenuButton, false); + openSubmenuButton.focus(); + } else if (!searchPanel.hidden) { + setSearch(false); + searchButton.focus(); + } else if (header.classList.contains("is-nav-open")) { + setMobileNav(false); + menuButton.focus(); + } + }); + + /* Switching between desktop and small-screen layouts starts clean */ + MOBILE_QUERY.addEventListener("change", closeAll); + + /* Review helpers */ + + const openMatch = location.hash.match(/^#open=([\w-]+)$/); + if (openMatch) { + const target = header.querySelector( + '[aria-controls="' + openMatch[1] + '"]', + ); + if (target) { + if (MOBILE_QUERY.matches && nav.contains(target)) menuButton.click(); + target.click(); + } + } + + if (location.hash === "#scrolled") window.scrollTo(0, 420); +})(); diff --git a/public/search-client.js b/public/search-client.js deleted file mode 100644 index dd78122..0000000 --- a/public/search-client.js +++ /dev/null @@ -1,164 +0,0 @@ -// Shared client-side news search over /search.json (no dependencies, no modules). -// Used by the header search (field + mobile reveal) and /search (full results). -// Focused-empty state per Baymard autocomplete guidance: recent searches (local -// only — localStorage, no network) plus the newest articles, never a void. -(function () { - const RKEY = 'ctao-recent-searches'; - function recents() { - try { return JSON.parse(localStorage.getItem(RKEY) || '[]'); } catch { return []; } - } - function remember(q) { - q = (q || '').trim(); - if (q.length < 2) return; - try { - const r = [q, ...recents().filter((x) => x.toLowerCase() !== q.toLowerCase())].slice(0, 4); - localStorage.setItem(RKEY, JSON.stringify(r)); - } catch { /* private mode — recents are a nicety */ } - } - - window.newsSearch = function (opts) { - const input = document.getElementById(opts.input); - const list = document.getElementById(opts.list); - const status = document.getElementById(opts.status); - // Null-safe: a page may render only some search surfaces — bail silently - // (returning a no-op) so one missing input can't kill later inits. - if (!input || !list || !status) return function () {}; - let index = null; - let timer; - async function ensureIndex() { - if (index) return index; - try { - index = await (await fetch('/search.json')).json(); - } catch { - // Network hiccup: say so instead of failing silently; index stays - // null so the next keystroke retries. - status.textContent = 'Search is unavailable right now.'; - return null; - } - return index; - } - // First title match wrapped in (Starlight search pattern) — built - // from text nodes, never innerHTML. - function highlight(text, q) { - const i = text.toLowerCase().indexOf(q); - if (i < 0) return [text]; - const mark = document.createElement('mark'); - mark.textContent = text.slice(i, i + q.length); - return [text.slice(0, i), mark, text.slice(i + q.length)]; - } - function label(text) { - const li = document.createElement('li'); - li.className = 'suggest-label'; - li.setAttribute('aria-hidden', 'true'); // the status line announces it - li.textContent = text; - return li; - } - function row(children, href, q) { - const li = document.createElement('li'); - const a = document.createElement('a'); - a.href = href; - if (q) a.dataset.q = q; - a.append(...children); - li.append(a); - return li; - } - // Dropdowns close with a "Press Enter for all results" row — a real list - // element (was a CSS ::after), aria-hidden like the group labels so - // screen-reader exposure stays consistent (the status line announces). - function hint() { - if (!opts.dismiss || !list.firstChild) return; - const li = document.createElement('li'); - li.className = 'suggest-hint'; - li.setAttribute('aria-hidden', 'true'); - li.textContent = 'Press Enter for all results'; - list.append(li); - } - // Focused-empty state: up to 4 recent queries, topped up to 5 rows with - // the newest articles from the same index. - async function idle() { - if (input.value.trim().length >= 2) return; - // Build off-DOM, swap atomically — clearing before the await shrinks the - // page mid-flight and the browser clamps the scroll position ("jumps up"). - const frag = document.createDocumentFragment(); - const r = recents(); - if (r.length) { - frag.append(label('Recent')); - for (const term of r) frag.append(row([term], '/search?q=' + encodeURIComponent(term), term)); - } - const idx = await ensureIndex(); - if (!idx) return; - const fresh = idx.slice(0, Math.max(1, 5 - r.length)); - if (input.value.trim().length >= 2) return; // typed meanwhile — run() owns the list - frag.append(label('Latest news')); - for (const p of fresh) { - const li = row([p.title], '/news/' + encodeURIComponent(p.slug)); - const small = document.createElement('small'); - small.textContent = p.date; - li.append(small); - frag.append(li); - } - list.replaceChildren(frag); - hint(); - status.textContent = opts.hint || (r.length ? 'Showing recent searches and latest news' : 'Showing latest news'); - } - async function run() { - const q = input.value.trim().toLowerCase(); - if (q.length < 2) { list.replaceChildren(); idle(); return; } - const idx = await ensureIndex(); - if (!idx) { list.replaceChildren(); return; } - const hits = idx - .filter((p) => (p.title + ' ' + p.description + ' ' + p.category).toLowerCase().includes(q)) - .slice(0, opts.limit); - const unit = opts.unit || 'result'; - status.textContent = hits.length - ? hits.length + ' ' + unit + (hits.length === 1 ? '' : 's') + ' for “' + input.value.trim() + '”' - : 'No results for “' + input.value.trim() + '”. Try a shorter or different term.'; - // Off-DOM build + atomic swap (no transient page-height collapse) - const frag = document.createDocumentFragment(); - for (const p of hits) { - const li = row(highlight(p.title, q), '/news/' + encodeURIComponent(p.slug)); - const small = document.createElement('small'); - small.textContent = p.date; - li.append(small); - frag.append(li); - } - // Dropdown only: an empty list would collapse (.suggest:empty) and the - // panel would just vanish — show the no-results line sighted users too - // (the status line above is sr-only in the header; /search shows a - // visible notice instead). - if (!hits.length && opts.dismiss) { - const li = document.createElement('li'); - li.className = 'suggest-hint'; - li.setAttribute('aria-hidden', 'true'); // the status line announces it - li.textContent = 'No results for “' + input.value.trim() + '”'; - frag.append(li); - } - list.replaceChildren(frag); - hint(); - } - input.addEventListener('input', () => { clearTimeout(timer); timer = setTimeout(run, 150); }); - input.addEventListener('focus', () => { if (input.value.trim().length < 2) idle(); }); - // Remember successful queries: Enter (form submit) and suggestion clicks - if (input.form) input.form.addEventListener('submit', () => remember(input.value)); - list.addEventListener('click', (e) => { - const a = e.target.closest && e.target.closest('a'); - remember((a && a.dataset.q) || input.value); - }); - // Dropdown dismiss (opt-in — /search keeps its results list). The - // suggestion list is a NON-MODAL popup (combobox-style; plain - // Tab-reachable links, no roving arrow-key focus): interacting outside - // the field+list closes it but the interaction is NOT swallowed — - // unlike the modal header panels, which scrim-dismiss in Base.astro. - if (opts.dismiss) { - const box = input.closest('form') || input.parentElement; - const hide = () => { clearTimeout(timer); list.replaceChildren(); status.textContent = ''; }; - document.addEventListener('pointerdown', (e) => { if (list.firstChild && !box.contains(e.target)) hide(); }); - // relatedTarget may be null mid-click on our own links — pointerdown covers that path - box.addEventListener('focusout', (e) => { if (e.relatedTarget && !box.contains(e.relatedTarget)) hide(); }); - input.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && list.firstChild) { hide(); e.stopPropagation(); } // panel Escape stays a second step - }); - } - return run; // callers may run() immediately (e.g. /search?q=…) - }; -})(); diff --git a/sample-content/pages/consectetur-adipiscing.md b/sample-content/pages/consectetur-adipiscing.md new file mode 100644 index 0000000..4c9c760 --- /dev/null +++ b/sample-content/pages/consectetur-adipiscing.md @@ -0,0 +1,12 @@ +--- +title: "Consectetur adipiscing" +introduction: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." +--- + +## Consectetur adipiscing + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +- Lorem ipsum dolor sit amet. +- Consectetur adipiscing elit. +- Sed do eiusmod tempor incididunt. diff --git a/sample-content/pages/dolor-sit-amet.md b/sample-content/pages/dolor-sit-amet.md new file mode 100644 index 0000000..311b501 --- /dev/null +++ b/sample-content/pages/dolor-sit-amet.md @@ -0,0 +1,19 @@ +--- +title: "Dolor sit amet" +introduction: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." +image: "/uploads/hero.jpg" +imageAlt: "Telescope beneath a star-filled night sky" +imageCaption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." +--- + +## Dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + + + +Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam. + +Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur. At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti. + +Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. diff --git a/sample-content/pages/lorem-ipsum.md b/sample-content/pages/lorem-ipsum.md new file mode 100644 index 0000000..0a0deca --- /dev/null +++ b/sample-content/pages/lorem-ipsum.md @@ -0,0 +1,25 @@ +--- +title: "Lorem Ipsum Dolor Sit Amet Consectetur" +introduction: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." +image: "/uploads/hero.jpg" +imageAlt: "Telescope beneath a star-filled night sky" +imageCaption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." +buttonLabel: "Lorem ipsum" +buttonUrl: "/example/dolor-sit-amet/" +--- + +## Lorem Ipsum Dolor + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat ([lorem ipsum](/example/dolor-sit-amet/)). Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo [inventore veritatis](/example/consectetur-adipiscing/) et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia [consequuntur magni](/example/sed-do-eiusmod/) dolores eos qui ratione voluptatem sequi nesciunt. + +> Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + +## Consectetur Adipiscing Elit + +Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam. + +Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur. At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti. + +Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. diff --git a/sample-content/pages/sed-do-eiusmod.md b/sample-content/pages/sed-do-eiusmod.md new file mode 100644 index 0000000..ffe6039 --- /dev/null +++ b/sample-content/pages/sed-do-eiusmod.md @@ -0,0 +1,16 @@ +--- +title: "Sed do eiusmod" +introduction: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." +--- + +## Sed do eiusmod + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + + + +Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam. + +Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur. At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti. + +Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. diff --git a/sample-content/uploads/hero.jpg b/sample-content/uploads/hero.jpg new file mode 100644 index 0000000..fdc915c Binary files /dev/null and b/sample-content/uploads/hero.jpg differ diff --git a/scripts/check-links.mjs b/scripts/check-links.mjs index f793d44..8fb9da0 100644 --- a/scripts/check-links.mjs +++ b/scripts/check-links.mjs @@ -4,8 +4,9 @@ // URLs, mailto:, data: and pure-#fragment links are out of scope. import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs'; import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; -const DIST = new URL('../dist', import.meta.url).pathname; +const DIST = fileURLToPath(new URL('../dist', import.meta.url)); if (!existsSync(DIST)) { console.error('dist/ not found — run `npm run build` first'); process.exit(1); } const htmlFiles = []; diff --git a/scripts/content.mjs b/scripts/content.mjs new file mode 100644 index 0000000..0445c5d --- /dev/null +++ b/scripts/content.mjs @@ -0,0 +1,34 @@ +// Copy the four editable files into Astro's ignored content directory. +// --ensure seeds only a fresh checkout; an incomplete existing set fails loudly. +import { cpSync, existsSync, mkdirSync, readFileSync } from 'node:fs'; +import { resolve, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = fileURLToPath(new URL('../', import.meta.url)); +const examples = JSON.parse(readFileSync(join(root, 'src/data/examples.json'), 'utf8')); +const ensure = process.argv.includes('--ensure'); +const source = resolve(process.argv[2] && !ensure ? process.argv[2] : join(root, 'sample-content')); +const pages = join(root, 'src/content/pages'); +const uploads = join(root, 'public/uploads'); + +function validate(directory) { + for (const { slug } of examples) { + if (!existsSync(join(directory, `${slug}.md`))) { + throw new Error(`Missing ${join(directory, `${slug}.md`)}. Run npm run content:setup or npm run content:sync -- .`); + } + } +} + +if (ensure && existsSync(pages)) { + validate(pages); +} else { + validate(join(source, 'pages')); + if (!existsSync(join(source, 'uploads'))) throw new Error(`Missing uploads directory in ${source}`); + mkdirSync(pages, { recursive: true }); + mkdirSync(uploads, { recursive: true }); + for (const { slug } of examples) { + cpSync(join(source, 'pages', `${slug}.md`), join(pages, `${slug}.md`)); + } + cpSync(join(source, 'uploads'), uploads, { recursive: true }); + console.log(`Example content copied from ${source}`); +} diff --git a/scripts/link-content.sh b/scripts/link-content.sh index a8046b4..3cdb0ea 100755 --- a/scripts/link-content.sh +++ b/scripts/link-content.sh @@ -1,17 +1,7 @@ #!/usr/bin/env bash # Local dev: put the content repo's files where the build expects them. # Usage: ./scripts/link-content.sh [path-to-content-clone] (default ../ctao-content) -# Copies (not symlinks) so the dev tree behaves exactly like the machine -# build overlay; re-run after pulling content changes. +# Cross-platform implementation is shared with npm run content:sync. set -euo pipefail cd "$(dirname "$0")/.." -src="${1:-../ctao-content}" -for d in news pages uploads; do - [ -d "$src/$d" ] || { echo "content clone incomplete: $src/$d missing (git clone /ctao/content.git first)"; exit 1; } -done -rm -rf src/content/news src/content/pages public/uploads -mkdir -p src/content public -cp -a "$src/news" src/content/news -cp -a "$src/pages" src/content/pages -cp -a "$src/uploads" public/uploads -echo "content linked from $src" +node scripts/content.mjs "${1:-../ctao-content}" diff --git a/scripts/local-content.mjs b/scripts/local-content.mjs new file mode 100644 index 0000000..e6acb87 --- /dev/null +++ b/scripts/local-content.mjs @@ -0,0 +1,19 @@ +// Prepare a disposable content repository for Sveltia's local folder picker. +import { cpSync, existsSync, mkdirSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { join } from 'node:path'; + +const root = fileURLToPath(new URL('../', import.meta.url)); +const local = join(root, '.tmp/content'); +if (!existsSync(local)) { + mkdirSync(local, { recursive: true }); + cpSync(join(root, 'sample-content/pages'), join(local, 'pages'), { recursive: true }); + cpSync(join(root, 'sample-content/uploads'), join(local, 'uploads'), { recursive: true }); +} +if (!existsSync(join(local, '.git'))) { + execFileSync('git', ['init', '--quiet', '--initial-branch=simple', local], { stdio: 'inherit' }); +} +execFileSync(process.execPath, [join(root, 'scripts/content.mjs'), local], { stdio: 'inherit' }); +console.log(`In Sveltia, choose Work with Local Repository and select: ${local}`); +console.log('After saving, run: npm run content:sync -- .tmp/content'); diff --git a/scripts/preview-styles.mjs b/scripts/preview-styles.mjs new file mode 100644 index 0000000..74c8538 --- /dev/null +++ b/scripts/preview-styles.mjs @@ -0,0 +1,13 @@ +// Derive editor body styles from the mockup CSS so the preview cannot drift. +import { readFileSync, writeFileSync } from 'node:fs'; + +const css = readFileSync(new URL('../src/styles/global.css', import.meta.url), 'utf8'); +const base = css.slice(0, css.indexOf('/* Utilities')); +const prose = css.slice(css.indexOf('/* Prose'), css.indexOf('/* Footer')); +const markdown = css.slice(css.indexOf('.prose { overflow-wrap:'), css.indexOf('.submenu a[aria-current="page"]')); +const preview = (base + prose + markdown).replace(/\.prose\b/g, 'body'); +writeFileSync(new URL('../public/admin/preview.css', import.meta.url), + '/* Generated by scripts/preview-styles.mjs; edit src/styles/global.css. */\n' + preview + ` +body { display: block; min-height: 0; max-width: 64rem; margin: 0 auto; padding: 1.5rem; } +h1 { color: var(--galaxy-blue); font-family: var(--font-head); font-weight: 500; line-height: 1.05; } +`); diff --git a/src/components/NewsCard.astro b/src/components/NewsCard.astro deleted file mode 100644 index 1eb1417..0000000 --- a/src/components/NewsCard.astro +++ /dev/null @@ -1,16 +0,0 @@ ---- -// The one news card — used by the home page and the /news archive. -// `featured` (archive page 1, first item) switches the wide lead variant and -// eager-loads its cover. Cover alt stays empty by design: the image is -// decorative next to the always-visible title. -import { fmt, iso, readMin } from '../lib/news.js'; -const { post, featured = false } = Astro.props; ---- - diff --git a/src/components/PageHero.astro b/src/components/PageHero.astro new file mode 100644 index 0000000..5b4f085 --- /dev/null +++ b/src/components/PageHero.astro @@ -0,0 +1,17 @@ +--- +interface Props { title: string; introduction?: string; example?: boolean; hasImage?: boolean } +const { title, introduction, example = false, hasImage = false } = Astro.props; +--- +
+
+ +

{title}

+ {introduction &&

{introduction}

} +
+
diff --git a/src/content.config.ts b/src/content.config.ts index 10023f4..fb35762 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -1,41 +1,26 @@ import { defineCollection } from 'astro:content'; import { z } from 'astro/zod'; import { glob } from 'astro/loaders'; +import examples from './data/examples.json'; -// News/articles live as Markdown files in src/content/news/*.md. -// This is exactly what Sveltia CMS edits — the CMS writes these files, Astro -// renders them to static HTML at build time. -const news = defineCollection({ - // Top-level only ('*.md', not '**'): entry ids feed a non-rest [slug] - // route, and an id from a subfolder would contain '/' and break it. - loader: glob({ pattern: '*.md', base: './src/content/news' }), - schema: z.object({ - title: z.string(), - description: z.string(), - date: z.coerce.date(), - category: z.string().default('news'), - author: z.string().default('CTAO'), - // Public-path string, e.g. "/uploads/foo.jpg" — media lives in the - // 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(), - // BCP-47 tag when an article is not in English (e.g. "pl") — set as lang - // on the
element so screen readers pick the right voice. - lang: z.string().optional(), - draft: z.boolean().default(false), - }), -}); - -// Static portal pages (privacy, disclaimer, contact…) — separate collection -// because the content type differs (no dates/covers/categories), and editors -// get a distinct "Pages" section in the CMS. +const destination = z.string().refine( + (value) => !value || /^(\/(?!\/)|https?:\/\/|mailto:|#)/.test(value), + 'Use a site path, https:// URL, mailto: address, or fragment.', +); const pages = defineCollection({ - // Top-level only, same reason as news: the [slug] route is non-rest. - loader: glob({ pattern: '*.md', base: './src/content/pages' }), + // Only these four files can become public content pages. + loader: glob({ + pattern: `{${examples.map(({ slug }) => slug).join(',')}}.md`, + base: './src/content/pages', + }), schema: z.object({ - title: z.string(), - description: z.string().optional(), + title: z.string().min(1), + introduction: z.string().optional(), + image: z.string().optional(), + imageAlt: z.string().optional(), + imageCaption: z.string().optional(), + buttonLabel: z.string().optional(), + buttonUrl: destination.optional(), }), }); - -export const collections = { news, pages }; +export const collections = { pages }; diff --git a/src/data/examples.json b/src/data/examples.json new file mode 100644 index 0000000..f6854d0 --- /dev/null +++ b/src/data/examples.json @@ -0,0 +1,18 @@ +[ + { + "slug": "lorem-ipsum", + "label": "Lorem ipsum" + }, + { + "slug": "dolor-sit-amet", + "label": "Dolor sit amet" + }, + { + "slug": "consectetur-adipiscing", + "label": "Consectetur adipiscing" + }, + { + "slug": "sed-do-eiusmod", + "label": "Sed do eiusmod" + } +] diff --git a/src/data/placeholders.json b/src/data/placeholders.json new file mode 100644 index 0000000..8840213 --- /dev/null +++ b/src/data/placeholders.json @@ -0,0 +1,42 @@ +[ + { + "slug": "news", + "title": "News" + }, + { + "slug": "data", + "title": "Data" + }, + { + "slug": "proposals", + "title": "Proposals" + }, + { + "slug": "dashboard", + "title": "Dashboard" + }, + { + "slug": "login", + "title": "Sign in" + }, + { + "slug": "search", + "title": "Search" + }, + { + "slug": "support", + "title": "Support" + }, + { + "slug": "pages/privacy", + "title": "Privacy" + }, + { + "slug": "pages/disclaimer", + "title": "Disclaimer" + }, + { + "slug": "pages/contact", + "title": "Contact" + } +] diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index fae7bcf..b881dfe 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -1,191 +1,119 @@ --- import '../styles/global.css'; -// `ambient` opts a page into the whole-page drifting brand wash (home only) -// `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; -// Absolute URLs for canonical/OG/RSS (head pattern from the official Astro -// blog template). `site` comes from astro.config.mjs and is always set. -const site = Astro.site; -const canonical = new URL(Astro.url.pathname, site); -// SVG covers never reach og:image — link-preview crawlers (Slack/Teams/ -// LinkedIn) don't render SVG, so those articles fall back to the brand card. -const ogImage = new URL(image && !image.endsWith('.svg') ? image : '/brand/og-card.jpg', site); -const path = Astro.url.pathname; -const isActive = (href: string) => (href === '/' ? path === '/' : path.startsWith(href)); -// Navigation = services from the Science Portal spec (REQUIREMENTS.md §2/§5). -// Order: serial-position effect (NN/g) — News (universal entry) first, Support -// (help convention) last; Data/Proposals by task frequency; Dashboard is the -// personal area, placed late next to the Sign-in cluster. External systems -// built by other teams are links out, clearly marked. -const links = [ - { href: '/news', label: 'News' }, - { href: 'https://padc-ctao-data-explorer.obspm.fr/', label: 'Data', ext: true }, - { href: '/proposals', label: 'Proposals' }, - { href: '/dashboard', label: 'Dashboard' }, - { href: '/support', label: 'Support' }, -]; +import examples from '../data/examples.json'; +interface Props { title?: string; description?: string } +const { title = 'CTAO Science Portal', description = 'CTAO Science Portal' } = Astro.props; +const path = Astro.url.pathname.replace(/\/?$/, '/'); +const canonical = new URL(Astro.url.pathname, Astro.site); --- -{/* Always "en": the chrome (nav/footer) is English; a non-English article - sets lang on its
element instead ([slug].astro). */} - {title} + {title === 'CTAO Science Portal' ? title : `${title} – CTAO`} - - {/* Social cards (Open Graph + Twitter) — article cover when present, brand card otherwise */} - - - - - - - - - - - - {/* The home hero photo is a CSS background, which browsers discover late — - preloading it moves the page's LCP element to the front of the queue. */} - {path === '/' && } - {/* Brand typography (BRAND D.3): Inter (body) + Space Grotesk (headlines). - Both ship inlined as data: URIs in the render-blocking stylesheet — - no font request, no swap (see global.css @font-face). */} + + + - - - + + +
+ + diff --git a/src/layouts/Placeholder.astro b/src/layouts/Placeholder.astro new file mode 100644 index 0000000..36a6a12 --- /dev/null +++ b/src/layouts/Placeholder.astro @@ -0,0 +1,10 @@ +--- +import Base from './Base.astro'; +import PageHero from '../components/PageHero.astro'; +interface Props { title: string } +const { title } = Astro.props; +--- + + +
+ diff --git a/src/lib/news.js b/src/lib/news.js deleted file mode 100644 index 5d1fbd0..0000000 --- a/src/lib/news.js +++ /dev/null @@ -1,11 +0,0 @@ -// Shared news helpers — single source for card metadata and archive chunking. -export const fmt = (d) => new Intl.DateTimeFormat('en-GB', { dateStyle: 'long' }).format(d); -export 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). -export const readMin = (p) => Math.max(1, Math.round((p.body ?? '').split(/\s+/).length / 220)); -// Archive chunking: page 1 holds 25 items (1 featured lead + 24 grid = even -// 3-column rows), later pages 24. Used by BOTH the /news/[...page] route and -// sitemap.xml.js — the page counts cannot drift apart. -export const PAGE_FIRST = 25; -export const PAGE_REST = 24; diff --git a/src/pages/404.astro b/src/pages/404.astro index 130c600..7b28356 100644 --- a/src/pages/404.astro +++ b/src/pages/404.astro @@ -1,24 +1,8 @@ --- import Base from '../layouts/Base.astro'; +import PageHero from '../components/PageHero.astro'; --- - - {/* Document archetype: the document IS the page — h1, one line, - recovery links, and a real GET search form as the way forward. */} -
- -
+ + + diff --git a/src/pages/[...placeholder].astro b/src/pages/[...placeholder].astro new file mode 100644 index 0000000..0a301d6 --- /dev/null +++ b/src/pages/[...placeholder].astro @@ -0,0 +1,9 @@ +--- +import Placeholder from '../layouts/Placeholder.astro'; +import placeholders from '../data/placeholders.json'; +export function getStaticPaths() { + return placeholders.map(({ slug, title }) => ({ params: { placeholder: slug }, props: { title } })); +} +const { title } = Astro.props; +--- + diff --git a/src/pages/admin/index.astro b/src/pages/admin/index.astro index 7b33bd4..d0e92d7 100644 --- a/src/pages/admin/index.astro +++ b/src/pages/admin/index.astro @@ -29,7 +29,7 @@ diff --git a/src/pages/dashboard.astro b/src/pages/dashboard.astro deleted file mode 100644 index 3d14bb2..0000000 --- a/src/pages/dashboard.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import Base from '../layouts/Base.astro'; -// Per SPEC §3.3.2 the dashboard aggregates proposal statuses, data products -// and help-desk tickets. No integrations exist yet, so panels show empty -// states instead of fabricated sample rows (source-documents-only rule). ---- - -
-
-

Dashboard

-

Your proposals, data products and support tickets in one place.

-
-
-
-
-

My proposals

-

Proposal statuses will appear here once the Proposal Handling System (APC team) is connected.

-
-
-

Recent data products

-

Your data products will appear here. Search and download in the Data Explorer (external, LUX team).

-
-
-

Support tickets

-

Help-desk tickets will appear here once User Support is connected.

-
-
-
-
- diff --git a/src/pages/example/[slug].astro b/src/pages/example/[slug].astro new file mode 100644 index 0000000..9d1714f --- /dev/null +++ b/src/pages/example/[slug].astro @@ -0,0 +1,35 @@ +--- +import { getCollection, render } from 'astro:content'; +import Base from '../../layouts/Base.astro'; +import PageHero from '../../components/PageHero.astro'; +import examples from '../../data/examples.json'; + +export async function getStaticPaths() { + const pages = await getCollection('pages'); + return examples.map(({ slug }) => { + const page = pages.find(({ id }) => id === slug); + if (!page) throw new Error(`Missing Example page: ${slug}. Run npm run content:setup.`); + return { params: { slug }, props: { page } }; + }); +} +const { page } = Astro.props; +const { Content } = await render(page); +const { title, introduction, image, imageAlt, imageCaption, buttonLabel, buttonUrl } = page.data; +--- + + + {image &&
+
+ {imageAlt + {imageCaption &&
{imageCaption}
} +
+
} + + diff --git a/src/pages/index.astro b/src/pages/index.astro index 7fa2335..71f7c38 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,93 +1,4 @@ --- -import { getCollection } from 'astro:content'; -import Base from '../layouts/Base.astro'; -import NewsCard from '../components/NewsCard.astro'; - -const posts = (await getCollection('news', ({ data }) => !data.draft)) - .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) - .slice(0, 3); -// Services per the Science Portal spec (REQUIREMENTS §5). Tiles carry no -// status chrome; the footer's demo note is the single mock disclosure. -// 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.', - icon: '' }, - { title: 'Data Explorer', href: 'https://padc-ctao-data-explorer.obspm.fr/', ext: true, desc: 'Search and download CTAO science data products.', - icon: '' }, - { title: 'Dashboard', href: '/dashboard', desc: 'Your proposals, data products and support tickets in one place.', - icon: '' }, - { title: 'User Support', href: '/support', desc: 'Help desk, FAQ, user forum and mailing lists.', - icon: '' }, - // 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. - { title: 'Software', planned: true, desc: 'Landing page for CTAO software (planned).', - icon: '' }, - { title: 'Documentation', planned: true, desc: 'CTAO user documentation (planned).', - icon: '' }, -]; -const planned = ['Scheduling', 'Science alerts']; +import Placeholder from '../layouts/Placeholder.astro'; --- - - {/* 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 at design time. Three elements only: - headline, brand subtitle, CTA pair (Apple hero restraint). */} -
- -
-
Cherenkov Telescope Array Observatory
-

CTAO Science Portal

-

Exploring the Universe at the Highest Energies

- {/* Hero actions = the two REAL things a logged-out visitor can do today: - news (SPEC §3.1 lists general information/announcements FIRST; §3.3.1 - "displays the main information"; calls for proposals arrive VIA news) - and the external Data Explorer. Proposals stays in nav + tiles — a - mock doesn't earn hero billing. Search lives in the header. */} - -
-
- - {/* 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: services on Moon Gray, editorial news on white. */} -
-
-

Services

-
- {services.map((s) => ( - - ))} -
-

Planned services: {planned.join(' · ')}

-
-
- -
-
-

Latest news

- All news → -
-
- {posts.map((post) => )} -
-
- + diff --git a/src/pages/login.astro b/src/pages/login.astro deleted file mode 100644 index 5463099..0000000 --- a/src/pages/login.astro +++ /dev/null @@ -1,16 +0,0 @@ ---- -import Base from '../layouts/Base.astro'; ---- - -
- -
- -

Sign in

- {/* Auth pages are scanned, not read — one line, no reassurance prose */} -

Use your CTAO account (single sign-on via CTAO AAI).

- -

No account? Register via CTAO AAI

-
-
- diff --git a/src/pages/news/[...page].astro b/src/pages/news/[...page].astro deleted file mode 100644 index e183974..0000000 --- a/src/pages/news/[...page].astro +++ /dev/null @@ -1,83 +0,0 @@ ---- -import { getCollection } from 'astro:content'; -import Base from '../../layouts/Base.astro'; -import NewsCard from '../../components/NewsCard.astro'; -import { PAGE_FIRST as FIRST, PAGE_REST as REST } from '../../lib/news.js'; - -// Paginated archive; the [...page] rest route makes page 1 the bare /news URL, -// then /news/2 … /news/N. Custom slicing instead of paginate(): page 1 holds -// FIRST items (1 featured lead + 24 grid = even 3-column rows), later pages -// REST — paginate() cannot vary page size. Chunk sizes live in lib/news.js, -// shared with sitemap.xml.js. -export async function getStaticPaths() { - const posts = (await getCollection('news', ({ data }) => !data.draft)).sort( - (a, b) => b.data.date.valueOf() - a.data.date.valueOf(), - ); - const chunks = [posts.slice(0, FIRST)]; - for (let i = FIRST; i < posts.length; i += REST) chunks.push(posts.slice(i, i + REST)); - const lastPage = chunks.length; - const hrefOf = (n: number) => (n === 1 ? '/news' : `/news/${n}`); - return chunks.map((data, i) => { - const n = i + 1; - return { - params: { page: n === 1 ? undefined : String(n) }, - props: { page: { - data, - currentPage: n, - lastPage, - url: { - prev: n > 1 ? hrefOf(n - 1) : undefined, - next: n < lastPage ? hrefOf(n + 1) : undefined, - }, - } }, - }; - }); -} - -const { page } = Astro.props; -const first = page.currentPage === 1; -// Windowed page list — 1 … n-1 n n+1 … last (0 marks an ellipsis) -const nums = []; -for (let n = 1; n <= page.lastPage; n++) { - if (n === 1 || n === page.lastPage || Math.abs(n - page.currentPage) <= 1) nums.push(n); - else if (nums.at(-1) !== 0) nums.push(0); -} -const hrefFor = (n: number) => (n === 1 ? '/news' : `/news/${n}`); ---- - - {/* Section-landing archetype: white head, display Galaxy h1 + standfirst - (the navy band is reserved for / and /login). */} -
-

News & Announcements

-

Science highlights and updates from the observatory and its partners.

-
- -
-

{first ? 'All news' : `All news, page ${page.currentPage}`}

-
- {page.data.map((post, i) => )} -
- {page.lastPage > 1 && ( - - )} -
- diff --git a/src/pages/news/[slug].astro b/src/pages/news/[slug].astro deleted file mode 100644 index cd88b1c..0000000 --- a/src/pages/news/[slug].astro +++ /dev/null @@ -1,144 +0,0 @@ ---- -import { getCollection, render } from 'astro:content'; -import Base from '../../layouts/Base.astro'; -import { fmt, readMin as readMinOf } from '../../lib/news.js'; - -export async function getStaticPaths() { - // Date-sorted so each article knows its sequential neighbours (prev/next - // pattern of every editorial site — keeps readers in the content flow). - const posts = (await getCollection('news', ({ data }) => !data.draft)).sort( - (a, b) => b.data.date.valueOf() - a.data.date.valueOf(), - ); - return posts.map((post, i) => ({ - params: { slug: post.id }, - props: { post, newer: posts[i - 1] ?? null, older: posts[i + 1] ?? null }, - })); -} - -const { post, newer, older } = Astro.props; -const { Content, headings } = await render(post); -const toc = headings.filter((h) => h.depth === 2 || h.depth === 3); -const readMin = readMinOf(post); ---- - - {/* Reading progress (scroll-driven CSS, no JS) — styled only where - animation-timeline is supported; elsewhere it stays an empty div. */} - - {/* TOC pattern (MDN/Stripe/NN-g): sticky right rail ≥1200px, collapsed
- under the title below that. CSS shows exactly one of the two. */} - {/* lang sits on the article, not : the chrome (nav/footer) stays - English for screen readers even when the article body is not. */} -
-
- ← All news - {/* 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. */} -

{post.data.title}

-

{post.data.description}

- {/* Meta — one quiet muted line: category · author · date · reading time */} -
- {post.data.category}· - {post.data.author}· - · - {readMin} min read -
- {post.data.cover && } - {/* 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 && ( -
- On this page -
    - {toc.map((h) => ( -
  1. {h.text}
  2. - ))} -
-
- )} -
- -
- {(newer || older) && ( - - )} -
- {toc.length >= 3 && ( - - )} -
- - - diff --git a/src/pages/pages/[slug].astro b/src/pages/pages/[slug].astro deleted file mode 100644 index 54d8eec..0000000 --- a/src/pages/pages/[slug].astro +++ /dev/null @@ -1,27 +0,0 @@ ---- -import { getCollection, render } from 'astro:content'; -import Base from '../../layouts/Base.astro'; - -export async function getStaticPaths() { - const pages = await getCollection('pages'); - return pages.map((page) => ({ params: { slug: page.id }, props: { page } })); -} - -const { page } = Astro.props; -const { Content } = await render(page); ---- - - {/* Same wrapper pattern as news/[slug] (never .container and .article on - one element — both set max-width and would depend on rule order). */} -
-
- {/* Static pages are footer-reached documents; same back affordance as - articles ("← All news") so deep links aren't dead ends. */} - ← Home -

{page.data.title}

-
- -
-
-
- diff --git a/src/pages/proposals.astro b/src/pages/proposals.astro deleted file mode 100644 index 7c2b420..0000000 --- a/src/pages/proposals.astro +++ /dev/null @@ -1,28 +0,0 @@ ---- -import Base from '../layouts/Base.astro'; ---- - -
-
-

Observation proposals

-

Submit observation proposals, including ToO and MWL requests.

-
-
- {/* 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. */} -
-

Proposal Handling System

-

Submitting observation proposals, including Target of Opportunity (ToO) - and multi-wavelength (MWL) requests, happens in the Proposal Handling - System. That system is built by the APC team and integrated into this portal.

-

What the portal contributes:

-
    -
  • This menu entry, with a single sign-on hand-off (CTAO AAI).
  • -
  • Proposal status feedback in your Dashboard.
  • -
- -
-
-
- diff --git a/src/pages/rss.xml.js b/src/pages/rss.xml.js deleted file mode 100644 index aeb623d..0000000 --- a/src/pages/rss.xml.js +++ /dev/null @@ -1,43 +0,0 @@ -// RSS 2.0 feed — hand-rolled static endpoint like search.json.js (no @astrojs/rss -// dependency; the spec is 20 lines of XML). Newest 30 items, absolute URLs from -// the configured `site`. -import { getCollection } from 'astro:content'; - -const esc = (s = '') => - s.replace(/[<>&'"]/g, (c) => ({ '<': '<', '>': '>', '&': '&', "'": ''', '"': '"' })[c]); - -export async function GET(context) { - const site = context.site; // always set in astro.config.mjs - const posts = (await getCollection('news', ({ data }) => !data.draft)) - .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) - .slice(0, 30); - - const items = posts - .map((p) => { - const url = new URL(`/news/${p.id}`, site).href; - return ` - ${esc(p.data.title)} - ${url} - ${url} - ${p.data.date.toUTCString()} - ${esc(p.data.description)} - ${esc(p.data.category)} - `; - }) - .join('\n'); - - const xml = ` - - - CTAO Science Portal News - ${new URL('/news', site).href} - News and announcements of the Cherenkov Telescope Array Observatory - en - ${(posts[0]?.data.date ?? new Date()).toUTCString()} - -${items} - - -`; - return new Response(xml, { headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' } }); -} diff --git a/src/pages/search.astro b/src/pages/search.astro deleted file mode 100644 index df18c25..0000000 --- a/src/pages/search.astro +++ /dev/null @@ -1,36 +0,0 @@ ---- -import Base from '../layouts/Base.astro'; ---- - - {/* 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. */} -
-
-

Search

-

Find news and announcements.

-
-
- -

Type to search the news archive.

-
    - -
    -
    - - -{/* search-client.js is loaded once (deferred) by the Base layout; this inline - module runs after it in the shared after-parse queue — order guaranteed. */} - diff --git a/src/pages/search.json.js b/src/pages/search.json.js deleted file mode 100644 index 1dce58e..0000000 --- a/src/pages/search.json.js +++ /dev/null @@ -1,19 +0,0 @@ -// Build-time search index (Astro static endpoint — no server, no dependencies). -// Kept lean: title/description/category/date/slug only (~60 KB for 200+ articles). -import { getCollection } from 'astro:content'; - -export async function GET() { - const posts = await getCollection('news', ({ data }) => !data.draft); - const index = posts - .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) - .map((p) => ({ - slug: p.id, - title: p.data.title, - description: p.data.description, - category: p.data.category, - date: p.data.date.toISOString().slice(0, 10), - })); - return new Response(JSON.stringify(index), { - headers: { 'Content-Type': 'application/json; charset=utf-8' }, - }); -} diff --git a/src/pages/sitemap.xml.js b/src/pages/sitemap.xml.js index eb5050d..73e1bb8 100644 --- a/src/pages/sitemap.xml.js +++ b/src/pages/sitemap.xml.js @@ -1,40 +1,12 @@ -// Sitemap — hand-rolled static endpoint (no @astrojs/sitemap dependency; our URL -// set is fully known at build time). /admin is intentionally excluded. -import { getCollection } from 'astro:content'; -import { PAGE_FIRST, PAGE_REST } from '../lib/news.js'; - -// Chunk sizes are imported from lib/news.js — the same values the -// /news/[...page] route slices with, so the page count here cannot drift. -const newsPageCount = (n) => (n <= PAGE_FIRST ? 1 : 1 + Math.ceil((n - PAGE_FIRST) / PAGE_REST)); - -export async function GET(context) { - const site = context.site; // always set in astro.config.mjs - const news = (await getCollection('news', ({ data }) => !data.draft)) - .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()); - const pages = await getCollection('pages'); - - // Static pages come from the actual route files, so a new src/pages/*.astro - // can never be forgotten here. 404 is excluded; /admin (subdirectory) is - // intentionally unlisted. Go-live note: /login and /dashboard are mock - // pages — filter them out before robots.txt stops disallowing everything. - const staticPages = Object.keys(import.meta.glob('./*.astro')) - .map((p) => p.slice(1, -'.astro'.length)) - .filter((p) => p !== '/404') - .map((p) => (p === '/index' ? '/' : p)) - .sort(); - const urls = [ - ...staticPages, '/news', - ...Array.from({ length: newsPageCount(news.length) - 1 }, (_, i) => `/news/${i + 2}`), - ...pages.map((p) => `/pages/${p.id}`), - ].map((path) => ` ${new URL(path, site).href}`); - const articles = news.map( - (p) => ` ${new URL(`/news/${p.id}`, site).href}${p.data.date.toISOString().slice(0, 10)}`, - ); +import examples from '../data/examples.json'; +import placeholders from '../data/placeholders.json'; +export function GET({ site }) { + const paths = ['/', ...placeholders.map(({ slug }) => `/${slug}/`), + ...examples.map(({ slug }) => `/example/${slug}/`)]; const xml = ` -${[...urls, ...articles].join('\n')} - -`; +${paths.map((path) => ` ${new URL(path, site).href}`).join('\n')} +`; return new Response(xml, { headers: { 'Content-Type': 'application/xml; charset=utf-8' } }); } diff --git a/src/pages/support.astro b/src/pages/support.astro deleted file mode 100644 index a788209..0000000 --- a/src/pages/support.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import Base from '../layouts/Base.astro'; -// User support channels per the Science Portal spec — all mock for now. -const channels = [ - { title: 'Help desk', desc: 'Submit and track support tickets.' }, - { title: 'FAQ & troubleshooting', desc: 'Common questions and step-by-step guides.' }, - { title: 'User forum', desc: 'Discuss with the CTAO user community.' }, - { title: 'Mailing lists', desc: 'Announcements and topical mailing lists.' }, -]; ---- - -
    -
    -

    User Support

    -

    Help desk, FAQ, user forum and mailing lists.

    -
    -
    -

    Support channels

    -
    - {channels.map((c) => ( -
    -

    {c.title}

    -

    {c.desc}

    -
    - ))} -
    -
    -
    - diff --git a/src/styles/global.css b/src/styles/global.css index 360e574..25da924 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,835 +1,856 @@ /* ========================================================================== - CTAO Science Portal — design tokens per the official CTAO Brand Guidelines - (April 2024): colors D.2, typography D.3 (Inter body / Space Grotesk display - at medium weight per D.3.4), flash D.4. All look-and-feel lives in the tokens - below; WCAG 2.1 AA contrast (photo-hero worst cases computed at design time; recompute if scrim or glow alphas change). - Light theme only (brand is light-first; navy bands carry the dark identity). - System rules (spacing / elevation / surfaces / motion) live in the comments next to each block below. + CTAO Science Portal: content page mockup + Colours and type follow the CTAO Brand Guidelines (April 2024). ========================================================================== */ + +/* Fonts + ========================================================================== */ + +@font-face { + font-family: "Inter"; + font-weight: 400 700; + font-display: swap; + src: url("/assets/fonts/inter-latin.woff2") format("woff2"); +} + +@font-face { + font-family: "Space Grotesk"; + font-weight: 500 700; + font-display: swap; + src: url("/assets/fonts/space-grotesk-latin.woff2") format("woff2"); +} + +/* Tokens + ========================================================================== */ + :root { - color-scheme: light; - /* Brand palette — hex values straight from the brand book */ - --galaxy: #00004a; /* Galaxy Blue — headings, header/footer bands */ - --cherenkov: #00e4d8; /* Cherenkov Blue — primary CTA, flash, hero subtitle, logo cluster ONLY */ - --moon: #f5f5f5; /* Moon Gray — surfaces */ - --azure: #007aff; /* Cosmic Azure — focus rings (UI only on light) */ - --indigo: #00009c; /* Interstellar Indigo — gradient depth */ - /* Derived, WCAG-adjusted */ - --text: #101228; /* near-Galaxy body ink */ - --muted: #46536a; - --border: #e2e5ee; - --link: #0057c2; /* Cosmic Azure darkened to pass AA (4.5:1) for body links */ - --on-galaxy-muted: #c7cde9; - --tint-cherenkov: color-mix(in srgb, var(--cherenkov) 16%, transparent); /* highlight only */ - --cherenkov-hover: color-mix(in oklab, var(--cherenkov), var(--galaxy) 12%); /* CTA hover state */ - /* stroke 3: at ~0.72em rendering this matches the .ico family's ~1.5px optical weight */ - --ico-ext: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cpath d="M7 17 17 7M9 7h8v8" fill="none" stroke="black" stroke-width="3"/%3E%3C/svg%3E'); - /* Type — fonts (BRAND D.3.1/D.3.2) and the ONLY font sizes in this codebase */ - --font-body: "Inter", "Inter Fallback", Arial, system-ui, sans-serif; - --font-display: "Space Grotesk", "Space Grotesk Fallback", var(--font-body); /* headlines only */ - --fs-xs: 0.8125rem; /* chips, meta, badges, labels */ - --fs-s: 0.9rem; /* secondary text, nav, forms, footer */ - --fs-l: 1.125rem; /* card titles, standfirst, panel titles */ - --fs-xl: 1.5rem; /* prose h2, featured title, auth h1 */ - --fs-h2: clamp(1.75rem, 3.5vw, 2.5rem); /* section heads */ - --fs-h1: clamp(2.25rem, 5vw, 3.5rem); /* page/article headlines */ - --fs-display: clamp(2.5rem, 7vw, 4.75rem); /* home hero only */ - /* ctao.org uses generous rounding (15/30/60px tokens) and pill buttons */ - --radius: 16px; - --radius-pill: 999px; - --maxw: 1120px; - /* Fluid spacing scale (Apple-scale rhythm: sections breathe, mobile stays compact). - Every section / container / card padding sits on this scale — no ad-hoc values. */ - --gutter: clamp(18px, 4vw, 28px); /* page side padding */ - --space-s: 12px; - --space-m: 24px; - --space-l: clamp(24px, 2vw + 16px, 40px); - --space-xl: clamp(56px, 7vw, 120px); /* section rhythm */ - --header-h: 56px; /* sticky header; anchors, TOC rail, progress bar derive from it */ - --space-band: clamp(72px, 10vw, 160px); /* hero / auth / landing-head padding */ - /* Elevation — navy-tinted, soft/large/low-alpha; the ONLY two shadows allowed */ - --shadow-ambient: 0 1px 2px rgba(0, 0, 74, 0.04), 0 8px 24px rgba(0, 0, 74, 0.06); - --shadow-lifted: 0 2px 6px rgba(0, 0, 74, 0.05), 0 24px 48px rgba(0, 0, 74, 0.14); - /* Motion — ONE hover language everywhere (ctao.org: quiet .25s fades) */ - --dur: 0.25s; - --ease: ease-out; - /* Nebula glow (BRAND D.5) — auth band, static AND animated. Alphas are - contrast ceilings (computed for AA at design time): Cherenkov ≤.38 / Azure ≤.35; cores sit in - opposite corners — cores never meet. */ - /* Aurora rule (Gemini-style): gradient-only surfaces (login) get LARGE, soft, - slowly flowing color fields — one protagonist per surface: the photo hero - animates only the photo (Ken Burns), never glow flares on top of it. */ - --nebula: - radial-gradient(60% 70% at 78% 78%, rgba(0, 228, 216, 0.42), transparent 70%), - radial-gradient(55% 62% at 10% 12%, rgba(0, 122, 255, 0.36), transparent 68%), - radial-gradient(80% 90% at 42% 55%, rgba(0, 0, 156, 0.8), transparent 74%); - /* Hero scrim over the photo — worst-case AA computed at design time: - small text lives left of 52% band width (α ≥ .76 there). */ - --hero-scrim: linear-gradient(to right, - rgba(0, 0, 74, 0.84), rgba(0, 0, 74, 0.76) 52%, - rgba(0, 0, 74, 0.44) 78%, rgba(0, 0, 74, 0.26)); -} -/* ≤640px the hero text spans the full width — flat scrim, computed ≥5.28:1 */ -@media (max-width: 640px) { - :root { --hero-scrim: linear-gradient(rgba(0, 0, 74, 0.8), rgba(0, 0, 74, 0.8)); } + /* Brand main colours (D.2.1) */ + --galaxy-blue: #00004a; + --cherenkov-blue: #00e4d8; + --moon-gray: #f5f5f5; + + /* Brand secondary colours (D.2.2) */ + --white: #ffffff; + --cosmic-azure: #007aff; + --interstellar-indigo: #00009c; + + /* Text */ + --text: #111111; + --text-muted: #555555; + /* Cosmic Azure at 84% brightness: 5.4:1 on white, 3.5:1 against body text */ + --link: #0066d6; + + /* Type: Inter for everything, Space Grotesk for headlines (D.3) */ + --font-body: "Inter", Arial, sans-serif; + --font-head: "Space Grotesk", Arial, sans-serif; + + /* Layout */ + --content-width: 85rem; + --gutter: clamp(1rem, 4vw, 2.5rem); + --header-height: 7rem; + --header-height-scrolled: 5rem; + --radius-lg: 60px; + --radius-md: 30px; + --radius-sm: 15px; + /* Dropdown submenus (Example, language) */ + --radius-menu: 8px; + --transition: 0.3s ease-in-out; + + /* Film grain tile over the hero background, also hides gradient banding */ + --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='.05'/%3E%3C/svg%3E"); } -/* Brand fonts — latin subset, variable weight, BOTH inlined below as data: - URIs in this render-blocking stylesheet: no separate font fetch exists, so - there is no swap window and no font flash between navigations. The - metric-matched Arial fallbacks cover only the pre-CSS paint (override - values computed from actual font metrics: weighted latin advance widths, - capsize method). The standalone /fonts/*.woff2 files are used ONLY by - public/admin/preview.css (CMS entry preview) — do not delete them. */ -@font-face { - font-family: "Inter"; font-weight: 400 700; font-display: swap; - /* ~64 KB base64 — the price of zero swap (the tracked uppercase eyebrow - used to rewrap on Inter arrival and jump the hero on mobile). */ - src: url("data:font/woff2;base64,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") format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -@font-face { - font-family: "Space Grotesk"; font-weight: 500 700; font-display: swap; - /* ~30 KB base64 — headlines render correctly from the very first paint - (no swap, no text-wrap:balance reflow jump on slow connections). */ - src: url("data:font/woff2;base64,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") format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -@font-face { - font-family: "Inter Fallback"; src: local("Arial"); - size-adjust: 107%; ascent-override: 90.2%; descent-override: 22.48%; line-gap-override: 0%; -} -@font-face { - font-family: "Space Grotesk Fallback"; src: local("Arial"); - size-adjust: 111.12%; ascent-override: 88.55%; descent-override: 26.28%; line-gap-override: 0%; +/* Base + ========================================================================== */ + +*, +*::before, +*::after { + box-sizing: border-box; } -/* Screen-reader-only utility (visually hidden, still announced) */ -.sr-only { - position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; - overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; +html { + scroll-padding-top: var(--header-height-scrolled); } -* { box-sizing: border-box; } -html { -webkit-text-size-adjust: 100%; } -/* Anchor jumps (TOC, skip link) must land below the 76px sticky header. That - offset used to be a global `html { scroll-padding-top: 76px }` — but Chrome - re-scrolls the focused editable into view on EVERY keystroke and honours - scroll-padding-top while doing it (crbug.com/41492445: the reveal also - ignores the editable's own scroll-margin, so a negative scroll-margin on - the header inputs could not cancel it). The header search fields sit above - the 76px line by definition and, being sticky, can never be scrolled below - it — so each typed character walked the page toward 0. Scoping the offset - to the in-content anchor targets fixes typing and keeps every jump - (TOC headings, skip link → #main) landing below the header. */ -main, main [id] { scroll-margin-top: calc(var(--header-h) + 20px); } -/* Guard, not a solution: every wide element is contained at its own - level (tables scroll in their box, long words wrap); clip only catches - regressions so one offender can never remove the page gutters. `clip`, unlike - `hidden`, creates no scroll container — sticky header keeps working. */ -html, body { overflow-x: clip; } body { margin: 0; font-family: var(--font-body); - background: #fff; - color: var(--text); + font-size: 1rem; line-height: 1.5; - min-height: 100dvh; + color: var(--text); + background: var(--white); + -webkit-font-smoothing: antialiased; } -/* Display type: Space Grotesk at medium weight (BRAND D.3.4 — regular/medium), - tight tracking at display sizes, compact leading. Hierarchy comes from size. */ -h1, h2 { font-family: var(--font-display); font-weight: 500; letter-spacing: -0.025em; line-height: 1.1; } -h3 { font-weight: 600; } -h1, h2, h3, .hero p { text-wrap: balance; } -/* Overlong unbroken words (slugs, codes) may break rather than overflow the page */ -h1, h2, h3 { overflow-wrap: break-word; } -.prose p, .desc { text-wrap: pretty; } - -/* Inline SVG icons — stroke inherits currentColor */ -.ico { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; flex: none; } - -/* All disclosures (menu, search, TOC) share the plain-button look */ -summary { cursor: pointer; list-style: none; } -summary::-webkit-details-marker { display: none; } - -a { color: var(--link); text-decoration: underline; text-underline-offset: 3px; } -::selection { background: var(--galaxy); color: #fff; } -/* On the navy bands the Galaxy fill would be invisible (band ≈ selection): - the pair inverts, no new colors. */ -:is(.site-header, .hero-band, .auth-band, .footer) ::selection { background: #fff; color: var(--galaxy); } -a:hover { color: var(--indigo); } -/* Placeholder links ( without href) don't pretend to be clickable */ -a:not([href]) { text-decoration: none; cursor: default; } -img { max-width: 100%; height: auto; display: block; } - -/* Visible keyboard focus everywhere (WCAG 2.4.7) */ -:focus-visible { outline: 2px solid var(--azure); outline-offset: 2px; } - -/* Skip link — first tab stop, hidden until focused (WCAG 2.4.1) */ -.skip { - position: absolute; left: 16px; top: -52px; z-index: 20; - background: var(--galaxy); color: #fff; text-decoration: none; - padding: 10px 16px; border-radius: 0 0 var(--radius) var(--radius); -} -.skip:focus { top: 0; } - -/* Safety net: even third-party/UA leftovers never move for reduced-motion users */ -@media (prefers-reduced-motion: reduce) { - *, *::before, *::after { transition: none !important; animation: none !important; } +body.no-scroll { + overflow: hidden; } -.container { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gutter); } -/* Surface rhythm: white (editorial + landing heads) → Moon Gray (service/utility - bands) → Galaxy (home hero, auth, footer) */ -.band--moon { background: var(--moon); } - -/* Flash — the REAL brand star (BRAND D.4): inline SVG path lifted from the - official logo, never a CSS approximation. Budget: one accent per view; static. */ -.flash { width: 12px; height: 12px; color: var(--cherenkov); flex: none; } - - -/* Header — Galaxy Blue band, reverse (white) logo per BRAND B.1.2; translucent - over content where backdrop blur is supported (AA computed: worst underlying - white → bar (31,31,96), muted nav text 9.4:1). Solid Galaxy fallback. */ -/* Lifted shadow separates the bar (and the mobile dropdown panels - underneath it) from content — the header floats above the page. */ -.site-header { background: var(--galaxy); position: sticky; top: 0; z-index: 10; box-shadow: var(--shadow-lifted); } -@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { - .site-header { - background: color-mix(in srgb, var(--galaxy) 88%, transparent); - -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); - } -} -.nav { display: flex; align-items: center; gap: 4px; min-height: var(--header-h); flex-wrap: nowrap; } -.nav > nav { display: flex; align-items: center; gap: 4px; } -/* One-row guarantee: everything keeps its natural size except the spacer - (links are single words, they can never wrap) */ -.nav > * { flex: none; } -/* Header tiers, one row always: ≥1025px full nav · ≤1024px disclosure menu · - ≤640px sub-brand hidden · ≤480px icon-only menu, smaller logo · ≤360px compact. - Search is the same icon-reveal at every width (one pattern, no width math). */ -.menu { display: none; } -.menu .i-close { display: none; } -.menu[open] .i-menu { display: none; } -.menu[open] .i-close { display: block; } -.brand { display: flex; align-items: center; gap: 10px; margin-right: 10px; text-decoration: none; } -.brand img { height: 26px; width: auto; } -.brand .brand-sub { - color: var(--cherenkov); font-size: var(--fs-xs); font-weight: 500; - border-left: 1px solid rgba(255, 255, 255, 0.25); padding-left: 10px; -} -.nav .spacer { flex: 1; } -.nav a.navlink { - color: var(--on-galaxy-muted); font-weight: 500; font-size: var(--fs-s); - padding: 10px 10px; text-decoration: none; position: relative; -} -/* Underline-grow (the current nav-hover convention): a 2px rule scales in - from the left on hover and stays put on the active page. Transform-only; - its transition lives in the no-preference block like every other motion. - ::before by codebase convention — ::after belongs to the external arrow. */ -.nav a.navlink::before { - content: ""; position: absolute; left: 10px; right: 10px; bottom: 4px; - height: 2px; border-radius: 1px; background: #fff; - transform: scaleX(0); transform-origin: 0 50%; -} -.nav a.navlink:hover { color: #fff; } -.nav a.navlink:hover::before, .nav a.navlink[aria-current='page']::before { transform: scaleX(1); } -.nav a.navlink[aria-current='page'] { color: #fff; } -/* AAI sign-in entry — ghost/outline: Cherenkov fill is reserved for the page's - primary CTA, the header only whispers. */ -.nav .login-mock { - color: #fff; background: transparent; font: inherit; font-weight: 600; font-size: var(--fs-s); - border: 1px solid rgba(255, 255, 255, 0.55); border-radius: var(--radius-pill); - padding: 7px 18px; margin-left: 6px; text-decoration: none; white-space: nowrap; -} -.nav .login-mock:hover { background: rgba(255, 255, 255, 0.12); color: #fff; border-color: #fff; } -/* Tap targets ≥44px (WCAG 2.5.5); inline-flex keeps the active bottom border */ -.nav a.navlink, .nav .login-mock { - display: inline-flex; align-items: center; gap: 6px; min-height: 44px; -} -/* Site search. Desktop ≥1025px: VISIBLE quiet field — NN/g magnifying-glass- - icon research: icon-only search reduces discoverability/usage; a visible - input is recommended when search is an important task (content-heavy portal, - spec-required search). Chrome stays whisper-quiet: hairline on navy. - Flexes 140-200px (only it and the spacer flex — one-row invariant). */ -.nav-search { position: relative; display: flex; align-items: center; flex: 0 1 200px; min-width: 140px; margin-left: 6px; } -/* stroke 2.6 ≈ the family's 1.5px rendered weight at 14px (icons must not thin out when small) */ -.nav-search .search-ico { left: 11px; width: 14px; height: 14px; color: rgba(255, 255, 255, 0.7); stroke-width: 2.6; } -.nav-search input { - width: 100%; min-width: 0; font: inherit; font-size: var(--fs-s); color: #fff; - background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.45); /* 3.55:1 vs Galaxy — WCAG 1.4.11 */ - border-radius: var(--radius-pill); padding: 7px 14px 7px 32px; -} -.nav-search input::placeholder { color: rgba(255, 255, 255, 0.65); } /* 6.9:1 */ -.nav-search input:focus { border-color: #fff; } -/* Autocomplete panel: never narrower than the field, wide enough for a - typical title in ≤2 lines (Baymard autocomplete; DocSearch ~500px) */ -.nav-search .suggest { left: auto; right: 0; width: min(480px, calc(100vw - 2 * var(--gutter))); top: calc(100% + 10px); } -/* ≤1024px replacement: a 44px magnifier
    reveals a search row under - the header (NN/g mobile-search pattern) — see the menu media block. */ -.search-pop { display: none; } -.search-pop summary { - display: inline-flex; align-items: center; justify-content: center; - min-width: 44px; min-height: 44px; color: #fff; -} -.search-pop form { - position: absolute; left: 0; right: 0; top: 100%; - display: flex; justify-content: center; gap: 8px; padding: 12px var(--gutter) 16px; - background: var(--galaxy); border-top: 1px solid rgba(255, 255, 255, 0.15); - box-shadow: var(--shadow-lifted); -} -.search-pop input { - flex: 0 1 560px; min-width: 0; font: inherit; color: var(--text); - background: #fff; border: 1px solid transparent; border-radius: var(--radius-pill); - padding: 10px 16px; -} -.search-pop input:focus { border-color: var(--azure); } -.search-pop .btn { padding: 10px 18px; } -.search-pop .suggest { - left: max(var(--gutter), 50vw - 330px); right: max(var(--gutter), 50vw - 330px); - top: calc(100% + 4px); -} -@media (max-width: 1024px) { - .nav > nav { display: none; } - .nav-search { display: none; } /* field is desktop-only (NN/g tiers) */ - .search-pop { display: block; } /* magnifier reveal takes over ≤1024px */ - .menu { display: block; } - .menu summary { - display: inline-flex; align-items: center; gap: 8px; min-height: 44px; - padding: 0 10px; margin-left: 6px; color: #fff; font-weight: 500; white-space: nowrap; - } - .menu nav { - position: absolute; left: 0; right: 0; top: 100%; - display: flex; flex-direction: column; padding: 6px var(--gutter) 14px; - background: var(--galaxy); border-top: 1px solid rgba(255, 255, 255, 0.15); - box-shadow: var(--shadow-lifted); - } - .menu nav a { - display: flex; align-items: center; min-height: 44px; - color: #fff; font-weight: 500; text-decoration: none; - border-bottom: 1px solid rgba(255, 255, 255, 0.12); - } - .menu nav a:last-child { border-bottom: 0; } - .menu nav a[aria-current="page"] { font-weight: 700; } -} -@media (max-width: 640px) { .brand .brand-sub { display: none; } } -@media (max-width: 480px) { - .brand img { height: 22px; } - .menu summary .menu-label { display: none; } - .menu summary { min-width: 44px; justify-content: center; padding: 0; margin-left: 2px; } -} -@media (max-width: 360px) { - .nav { padding: 0 14px; gap: 2px; } - .brand { margin-right: 4px; } - /* logo stays 22px — 22px tall ≈ 105px wide, brand minimum is 100px digital */ - .nav .login-mock { padding: 7px 12px; } -} -/* External-link marker — SVG arrow via mask, inherits currentColor */ -.external::after { - content: ""; display: inline-block; width: 0.72em; height: 0.72em; margin-left: 3px; - background: currentColor; - -webkit-mask: var(--ico-ext) center / contain no-repeat; - mask: var(--ico-ext) center / contain no-repeat; +img { + display: block; + max-width: 100%; + height: auto; } -/* Hero (home only) — image-led: LST-1 under the night sky, full-bleed beneath a - Galaxy scrim (--hero-scrim; worst-case AA computed at design time). With motion - allowed a Cherenkov/Azure glow layer drifts above the photo (see motion block). */ -.hero-band { - background: var(--hero-scrim), url("/brand/hero.jpg") center / cover, var(--galaxy); - color: #fff; padding: var(--space-band) 0; position: relative; - /* NO overflow:hidden here — it would clip the search-suggest dropdown. - The animated glow is clipped by its own .band-bg frame instead. */ -} -.band-bg { position: absolute; inset: 0; overflow: hidden; pointer-events: none; } -.hero { position: relative; } /* content above the animated glow layer */ -.hero h1 { font-size: var(--fs-display); margin: 0 0 12px; } -.hero p { color: var(--cherenkov); font-size: var(--fs-l); max-width: 62ch; margin: 0; } /* subtitle per BRAND D.3.4 */ -.eyebrow { - color: var(--on-galaxy-muted); font-weight: 600; text-transform: uppercase; - letter-spacing: 0.14em; font-size: var(--fs-xs); margin-bottom: 8px; +a { + color: var(--link); + text-decoration: none; } -/* Hero CTAs — the two core scientist tasks (SPEC §3.1): one Cherenkov primary, - one quiet ghost. Links styled as buttons because they navigate. */ -.hero-cta { display: flex; flex-wrap: wrap; gap: var(--space-s); margin-top: var(--space-l); } -a.btn { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; } -/* Compound selector: outranks the later-defined .btn base (source-order tie - would otherwise fill the ghost with Cherenkov) */ -.btn.btn--ghost { background: transparent; color: #fff; border: 1px solid rgba(255, 255, 255, 0.55); } -.btn.btn--ghost:hover { background: rgba(255, 255, 255, 0.12); color: #fff; border-color: #fff; } - -/* Section-landing head (news/search/proposals/dashboard/support) — white page, - display-scale Galaxy h1 + standfirst; the navy band is reserved for / and /login. */ -.page-head { padding-block: var(--space-band) var(--space-l); } -.page-head h1 { color: var(--galaxy); font-size: var(--fs-h1); margin: 0 0 10px; } -.page-head .standfirst { margin: 0; max-width: 62ch; } -.page-head + .page { padding-block-start: 0; } /* white-on-white: head already spaces */ - -/* Search field (/search page) — large pill input with inline magnifier; real GET form */ -.search-form { position: relative; display: flex; gap: 8px; max-width: 560px; } -.search-ico { position: absolute; left: 16px; top: 50%; translate: 0 -50%; color: var(--muted); pointer-events: none; } -.search-form input { - flex: 1; min-width: 0; font: inherit; color: var(--text); - background: #fff; border: 1px solid var(--border); - border-radius: var(--radius-pill); padding: 13px 18px 13px 44px; -} -.search-form input:focus { border-color: var(--azure); } -/* Live suggestions — plain links, Tab-accessible */ -.suggest { - position: absolute; top: calc(100% + 8px); left: 0; right: 0; - z-index: 11; /* above the sticky header (10) — never slides under it on scroll */ - list-style: none; margin: 0; padding: 8px; text-align: left; - background: #fff; border: 1px solid var(--border); border-radius: var(--radius); - box-shadow: var(--shadow-lifted); - max-height: min(60vh, 480px); overflow: auto; /* long lists scroll on mobile */ -} -.suggest:empty { display: none; } -/* Live-updating lists must never become the browser's scroll anchor — - Chrome otherwise "compensates" each keystroke's DOM swap with a scroll - adjustment (the page visibly jumps while typing). */ -.suggest, .search-results, #search-status { overflow-anchor: none; } -.suggest li { display: flex; align-items: flex-start; gap: var(--space-s); padding: 0 12px; border-radius: calc(var(--radius) - 8px); } -.suggest li + li { margin-top: 2px; } /* rows breathe — hover fills read as discrete items */ -.suggest li:hover, .suggest li:focus-within { background: var(--moon); } -/* Focused-empty state labels ("Recent" / "Latest news") — quiet, non-interactive */ -li.suggest-label { - color: var(--muted); font-size: var(--fs-xs); font-weight: 600; - text-transform: uppercase; letter-spacing: 0.08em; padding-top: 8px; -} -.suggest li.suggest-label:hover { background: none; } -/* Each later group opens with a hairline — label + its rows read as one unit */ -.suggest li.suggest-label:not(:first-child) { - margin-top: 6px; padding-top: 12px; border-top: 1px solid var(--border); border-radius: 0; -} -.search-results li.suggest-label { background: none; border: 0; box-shadow: none; padding: 8px 0 0; } -/* The link holds -split text nodes — it must lay out as flowing text, - never as a flex row (flex would turn every fragment into its own item and - stop titles from wrapping). ≥44px target comes from the block padding. */ -.suggest a { - flex: 1; min-width: 0; display: block; padding-block: 12px; /* row ≥44px — real tap target */ - color: var(--text); font-weight: 500; font-size: var(--fs-s); text-decoration: none; - overflow-wrap: break-word; -} -.suggest small { margin-top: 14px; } /* date tracks the first title line */ -/* "Press Enter for all results" — a real list row (built by search-client.js, - aria-hidden like the group labels so screen-reader exposure is consistent) */ -.suggest li.suggest-hint { - margin-top: 4px; padding: 8px 12px 2px; border-top: 1px solid var(--border); - border-radius: 0; color: var(--muted); font-size: var(--fs-xs); -} -.suggest li.suggest-hint:hover { background: none; } - -/* Section header row (h2 + optional "All news →" link) */ -.section-head { - display: flex; align-items: baseline; justify-content: space-between; - gap: var(--space-s); margin: var(--space-l) 0 var(--space-m); -} -.section-head:first-child { margin-top: 0; } -.section-head h2 { margin: 0; color: var(--galaxy); font-size: var(--fs-h2); } -.more { font-weight: 600; text-decoration: none; white-space: nowrap; } -.more:hover { text-decoration: underline; } - -/* Responsive 1/2/3-column grid (news cards + service tiles); container for @container */ -.grid { display: grid; gap: var(--space-m); grid-template-columns: 1fr; container-type: inline-size; } -@media (min-width: 640px) { .grid { grid-template-columns: repeat(2, 1fr); } } -@media (min-width: 940px) { .grid { grid-template-columns: repeat(3, 1fr); } } -/* Subgrid: cover/title/desc/meta rows align across each card row */ -@supports (grid-template-rows: subgrid) { - .grid > .card:not(.card--featured, .tile):has(> .cover) { display: grid; grid-template-rows: subgrid; grid-row: span 4; gap: 0; } - .grid > .card:not(.card--featured, .tile):has(> .cover) .body { display: grid; grid-template-rows: subgrid; grid-row: span 3; gap: 10px; } - /* Coverless cards (some pre-2025 articles have no cover:) keep the base - flex layout — content starts at the top instead of falling into the - empty cover track — but still span all 4 rows so neighbours align. */ - .grid > .card:not(.card--featured, .tile):not(:has(> .cover)) { grid-row: span 4; } +a:hover, +a:focus-visible { + text-decoration: underline; + text-underline-offset: 0.15em; } -/* News cards — image + clamped title/description +