probe
+
+
diff --git a/.tmp/2026-09-03-css-polish/after/art6-390.png b/.tmp/2026-09-03-css-polish/after/art6-390.png
new file mode 100644
index 0000000..b68c977
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/art6-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/art6.png b/.tmp/2026-09-03-css-polish/after/art6.png
new file mode 100644
index 0000000..5fce24b
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/art6.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/artwomen-1280.png b/.tmp/2026-09-03-css-polish/after/artwomen-1280.png
new file mode 100644
index 0000000..0fe9cb4
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/artwomen-1280.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/artwomen-390.png b/.tmp/2026-09-03-css-polish/after/artwomen-390.png
new file mode 100644
index 0000000..c3d1cb1
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/artwomen-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/artwomen.png b/.tmp/2026-09-03-css-polish/after/artwomen.png
new file mode 100644
index 0000000..9e09c64
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/artwomen.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/dashboard-480.png b/.tmp/2026-09-03-css-polish/after/dashboard-480.png
new file mode 100644
index 0000000..1b66b9f
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/dashboard-480.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/dashboard.png b/.tmp/2026-09-03-css-polish/after/dashboard.png
new file mode 100644
index 0000000..b8c6b57
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/dashboard.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/hdr-640.png b/.tmp/2026-09-03-css-polish/after/hdr-640.png
new file mode 100644
index 0000000..82611e1
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/hdr-640.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/home-390.png b/.tmp/2026-09-03-css-polish/after/home-390.png
new file mode 100644
index 0000000..7c98577
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/home-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/home.png b/.tmp/2026-09-03-css-polish/after/home.png
new file mode 100644
index 0000000..3d9d078
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/home.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/lead-1025.png b/.tmp/2026-09-03-css-polish/after/lead-1025.png
new file mode 100644
index 0000000..8e5f8b1
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/lead-1025.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/lead-1440.png b/.tmp/2026-09-03-css-polish/after/lead-1440.png
new file mode 100644
index 0000000..8b507a7
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/lead-1440.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/lead-640.png b/.tmp/2026-09-03-css-polish/after/lead-640.png
new file mode 100644
index 0000000..82611e1
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/lead-640.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/lead-700.png b/.tmp/2026-09-03-css-polish/after/lead-700.png
new file mode 100644
index 0000000..d83b1c1
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/lead-700.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/lead-768.png b/.tmp/2026-09-03-css-polish/after/lead-768.png
new file mode 100644
index 0000000..55182d5
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/lead-768.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/lead-940.png b/.tmp/2026-09-03-css-polish/after/lead-940.png
new file mode 100644
index 0000000..2fcaa97
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/lead-940.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/login-390.png b/.tmp/2026-09-03-css-polish/after/login-390.png
new file mode 100644
index 0000000..2530593
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/login-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/login.png b/.tmp/2026-09-03-css-polish/after/login.png
new file mode 100644
index 0000000..ff92b03
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/login.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/news-1025.png b/.tmp/2026-09-03-css-polish/after/news-1025.png
new file mode 100644
index 0000000..9f43ee7
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/news-1025.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/news-1100.png b/.tmp/2026-09-03-css-polish/after/news-1100.png
new file mode 100644
index 0000000..96586f3
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/news-1100.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/news-390.png b/.tmp/2026-09-03-css-polish/after/news-390.png
new file mode 100644
index 0000000..61b95b4
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/news-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/news-768.png b/.tmp/2026-09-03-css-polish/after/news-768.png
new file mode 100644
index 0000000..d531589
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/news-768.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/news.png b/.tmp/2026-09-03-css-polish/after/news.png
new file mode 100644
index 0000000..0762c84
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/news.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/nf-390.png b/.tmp/2026-09-03-css-polish/after/nf-390.png
new file mode 100644
index 0000000..9ac8665
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/nf-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/nf.png b/.tmp/2026-09-03-css-polish/after/nf.png
new file mode 100644
index 0000000..9c79cf7
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/nf.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/pag-360.png b/.tmp/2026-09-03-css-polish/after/pag-360.png
new file mode 100644
index 0000000..41fe496
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/pag-360.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/privacy-390.png b/.tmp/2026-09-03-css-polish/after/privacy-390.png
new file mode 100644
index 0000000..3d293bb
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/privacy-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/privacy.png b/.tmp/2026-09-03-css-polish/after/privacy.png
new file mode 100644
index 0000000..4e2f337
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/privacy.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/proposals-390.png b/.tmp/2026-09-03-css-polish/after/proposals-390.png
new file mode 100644
index 0000000..f27e216
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/proposals-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/proposals.png b/.tmp/2026-09-03-css-polish/after/proposals.png
new file mode 100644
index 0000000..c0c9f9e
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/proposals.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/search-390.png b/.tmp/2026-09-03-css-polish/after/search-390.png
new file mode 100644
index 0000000..0e88265
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/search-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/search.png b/.tmp/2026-09-03-css-polish/after/search.png
new file mode 100644
index 0000000..37638eb
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/search.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/support-360.png b/.tmp/2026-09-03-css-polish/after/support-360.png
new file mode 100644
index 0000000..538ea76
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/support-360.png differ
diff --git a/.tmp/2026-09-03-css-polish/after/support.png b/.tmp/2026-09-03-css-polish/after/support.png
new file mode 100644
index 0000000..88f1742
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/after/support.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/art6-390-pagination.png b/.tmp/2026-09-03-css-polish/before/art6-390-pagination.png
new file mode 100644
index 0000000..d33a0f4
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/art6-390-pagination.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/art6-390-tall.png b/.tmp/2026-09-03-css-polish/before/art6-390-tall.png
new file mode 100644
index 0000000..a963510
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/art6-390-tall.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/art6-390.png b/.tmp/2026-09-03-css-polish/before/art6-390.png
new file mode 100644
index 0000000..b68c977
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/art6-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/art6-pagination.png b/.tmp/2026-09-03-css-polish/before/art6-pagination.png
new file mode 100644
index 0000000..776e66c
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/art6-pagination.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/art6-tall.png b/.tmp/2026-09-03-css-polish/before/art6-tall.png
new file mode 100644
index 0000000..776e66c
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/art6-tall.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/art6.png b/.tmp/2026-09-03-css-polish/before/art6.png
new file mode 100644
index 0000000..5fce24b
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/art6.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/artwomen-1280.png b/.tmp/2026-09-03-css-polish/before/artwomen-1280.png
new file mode 100644
index 0000000..0fe9cb4
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/artwomen-1280.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/artwomen-390.png b/.tmp/2026-09-03-css-polish/before/artwomen-390.png
new file mode 100644
index 0000000..797cf85
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/artwomen-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/artwomen.png b/.tmp/2026-09-03-css-polish/before/artwomen.png
new file mode 100644
index 0000000..9e09c64
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/artwomen.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/dashboard-480.png b/.tmp/2026-09-03-css-polish/before/dashboard-480.png
new file mode 100644
index 0000000..efb72b3
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/dashboard-480.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/dashboard.png b/.tmp/2026-09-03-css-polish/before/dashboard.png
new file mode 100644
index 0000000..d701792
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/dashboard.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/hdr-1025.png b/.tmp/2026-09-03-css-polish/before/hdr-1025.png
new file mode 100644
index 0000000..e79e781
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/hdr-1025.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/hdr-1100.png b/.tmp/2026-09-03-css-polish/before/hdr-1100.png
new file mode 100644
index 0000000..f504322
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/hdr-1100.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/hdr-1200.png b/.tmp/2026-09-03-css-polish/before/hdr-1200.png
new file mode 100644
index 0000000..5221b80
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/hdr-1200.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/hdr-640.png b/.tmp/2026-09-03-css-polish/before/hdr-640.png
new file mode 100644
index 0000000..755281e
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/hdr-640.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/home-390.png b/.tmp/2026-09-03-css-polish/before/home-390.png
new file mode 100644
index 0000000..0341a0d
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/home-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/home.png b/.tmp/2026-09-03-css-polish/before/home.png
new file mode 100644
index 0000000..8fe4d3d
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/home.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/login-390.png b/.tmp/2026-09-03-css-polish/before/login-390.png
new file mode 100644
index 0000000..98ba9b6
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/login-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/login.png b/.tmp/2026-09-03-css-polish/before/login.png
new file mode 100644
index 0000000..cc99bdf
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/login.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/news-390.png b/.tmp/2026-09-03-css-polish/before/news-390.png
new file mode 100644
index 0000000..61b95b4
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/news-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/news-768.png b/.tmp/2026-09-03-css-polish/before/news-768.png
new file mode 100644
index 0000000..a554b2f
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/news-768.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/news.png b/.tmp/2026-09-03-css-polish/before/news.png
new file mode 100644
index 0000000..75576f2
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/news.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/nf-390.png b/.tmp/2026-09-03-css-polish/before/nf-390.png
new file mode 100644
index 0000000..e52751f
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/nf-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/nf.png b/.tmp/2026-09-03-css-polish/before/nf.png
new file mode 100644
index 0000000..656c64f
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/nf.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/pag-360.png b/.tmp/2026-09-03-css-polish/before/pag-360.png
new file mode 100644
index 0000000..136255f
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/pag-360.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/pag-390.png b/.tmp/2026-09-03-css-polish/before/pag-390.png
new file mode 100644
index 0000000..fc1bc51
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/pag-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/pag-480.png b/.tmp/2026-09-03-css-polish/before/pag-480.png
new file mode 100644
index 0000000..fb89836
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/pag-480.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/privacy-390.png b/.tmp/2026-09-03-css-polish/before/privacy-390.png
new file mode 100644
index 0000000..f29e97c
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/privacy-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/privacy.png b/.tmp/2026-09-03-css-polish/before/privacy.png
new file mode 100644
index 0000000..25f3d21
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/privacy.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/proposals-390.png b/.tmp/2026-09-03-css-polish/before/proposals-390.png
new file mode 100644
index 0000000..8665aa9
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/proposals-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/proposals.png b/.tmp/2026-09-03-css-polish/before/proposals.png
new file mode 100644
index 0000000..c1c5a52
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/proposals.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/search-390.png b/.tmp/2026-09-03-css-polish/before/search-390.png
new file mode 100644
index 0000000..a432562
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/search-390.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/search.png b/.tmp/2026-09-03-css-polish/before/search.png
new file mode 100644
index 0000000..37638eb
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/search.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/support-360.png b/.tmp/2026-09-03-css-polish/before/support-360.png
new file mode 100644
index 0000000..130af12
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/support-360.png differ
diff --git a/.tmp/2026-09-03-css-polish/before/support.png b/.tmp/2026-09-03-css-polish/before/support.png
new file mode 100644
index 0000000..2f2818e
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/before/support.png differ
diff --git a/.tmp/2026-09-03-css-polish/probe/frame390.png b/.tmp/2026-09-03-css-polish/probe/frame390.png
new file mode 100644
index 0000000..7e6cfb1
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/probe/frame390.png differ
diff --git a/.tmp/2026-09-03-css-polish/probe/new-390x2000.png b/.tmp/2026-09-03-css-polish/probe/new-390x2000.png
new file mode 100644
index 0000000..a3f6aa3
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/probe/new-390x2000.png differ
diff --git a/.tmp/2026-09-03-css-polish/probe/new-390x900.png b/.tmp/2026-09-03-css-polish/probe/new-390x900.png
new file mode 100644
index 0000000..3bc8f3b
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/probe/new-390x900.png differ
diff --git a/.tmp/2026-09-03-css-polish/probe/old-390x900.png b/.tmp/2026-09-03-css-polish/probe/old-390x900.png
new file mode 100644
index 0000000..3bc8f3b
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/probe/old-390x900.png differ
diff --git a/.tmp/2026-09-03-css-polish/probe/p390-scroll.png b/.tmp/2026-09-03-css-polish/probe/p390-scroll.png
new file mode 100644
index 0000000..b56f0c9
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/probe/p390-scroll.png differ
diff --git a/.tmp/2026-09-03-css-polish/probe/p500.png b/.tmp/2026-09-03-css-polish/probe/p500.png
new file mode 100644
index 0000000..f8cae89
Binary files /dev/null and b/.tmp/2026-09-03-css-polish/probe/p500.png differ
diff --git a/.tmp/2026-09-03-css-polish/shoot.sh b/.tmp/2026-09-03-css-polish/shoot.sh
new file mode 100755
index 0000000..86ee267
--- /dev/null
+++ b/.tmp/2026-09-03-css-polish/shoot.sh
@@ -0,0 +1,45 @@
+#!/bin/zsh
+# Screenshot helper for the CSS polish pass. $1 = output dir (before|after)
+# Narrow widths go through dist/__probe.html (a fixed-width iframe): headless
+# Chrome clamps --window-size below ~500px, so 360/390/480 shots would silently
+# be 500px-wide renders cropped to the window.
+set -e
+CH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+OUT="$(cd "$(dirname "$0")" && pwd)/$1"
+mkdir -p "$OUT"
+cp "$(dirname "$0")/__probe.html" "$ROOT/dist/__probe.html"
+shot() { # name url w h
+ "$CH" --headless=new --disable-gpu --hide-scrollbars --window-size=$3,$4 \
+ --screenshot="$OUT/$1.png" "$2" >/dev/null 2>&1
+}
+wide() { shot "$1" "http://localhost:4330$2" $3 $4; } # name path w h
+narrow() { # name path w h (h = iframe height)
+ shot "$1" "http://localhost:4330/__probe.html?w=$3&h=$4&p=$2" $(($3 + 20)) $(($4 + 10))
+}
+W='/news/ctao-and-cabildo-of-la-palma-organise-women-of-ctao-2026'
+wide home "/" 1440 2600
+narrow home-390 "/" 390 2200
+wide news "/news" 1440 2600
+narrow news-390 "/news" 390 2400
+wide news-768 "/news" 768 2200
+wide art6 "/news/6" 1440 2600
+narrow art6-390 "/news/6" 390 2600
+wide artwomen "$W" 1440 2600
+narrow artwomen-390 "$W" 390 2600
+wide artwomen-1280 "$W" 1280 2000
+wide search "/search?q=lst" 1440 2000
+narrow search-390 "/search?q=lst" 390 1800
+wide support "/support" 1440 1800
+narrow support-360 "/support" 360 1500
+wide privacy "/pages/privacy" 1440 1400
+narrow privacy-390 "/pages/privacy" 390 1500
+wide nf "/404-nonexistent" 1440 1200
+narrow nf-390 "/404-nonexistent" 390 1300
+wide proposals "/proposals" 1440 1600
+narrow proposals-390 "/proposals" 390 1700
+wide dashboard "/dashboard" 1440 1600
+narrow dashboard-480 "/dashboard" 480 1900
+wide login "/login" 1440 1200
+narrow login-390 "/login" 390 1100
+echo "shots in $OUT"
diff --git a/DESIGN.md b/DESIGN.md
index c281ca5..0a4990c 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -1,337 +1,32 @@
-# CTAO Science Portal — design system
+# Design handoff
-Single source of truth for look-and-feel. All values live as tokens in
-`src/styles/global.css`; this file states the rules. Brand basis: CTAO Brand
-Guidelines (Apr 2024) + techniques observed on ctao.org (photography-led hero,
-nebula motion, generous spacing, 15/30/60 rounding, quiet-fade hovers).
-No frameworks, no JS for presentation.
+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).
-## Color tokens (brand-fixed — never add hues)
+## Brand (official: CTAO Brand Guidelines, April 2024 — PDF in the project docs)
-| Token | Value | Use |
-|---|---|---|
-| `--galaxy` | `#00004A` | Headings, header/footer bands, hero scrim |
-| `--cherenkov` | `#00E4D8` | ONLY: primary CTA, flash, hero subtitle, logo cluster (+ functional: ``, TOC active rule). Never small text on light bg |
-| `--moon` | `#F5F5F5` | Utility surfaces (bands, neutral hovers) |
-| `--azure` | `#007AFF` | Focus rings and field-focus borders ONLY (no hover borders) |
-| `--indigo` | `#00009C` | Gradient depth, link hover |
-| `--text` / `--muted` / `--border` / `--link` | derived | Body ink; `--link #0057C2` is azure darkened to pass AA |
+- Colors: Galaxy Blue `#00004A`, Cherenkov Blue `#00E4D8`, Moon Gray
+ `#F5F5F5`, Cosmic Azure `#007AFF` (links/focus), Interstellar Indigo
+ `#00009C`. Cherenkov is an ACCENT (brand book rations it — currently:
+ primary CTA, hero subtitle, logo flash); it fails AA as text on white.
+- 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.
-Accent discipline: Cherenkov is rationed to the four brand moments above.
-Status pills, category/date chips and metadata are plain muted text — the
-label carries the meaning, not the chrome.
+## Practical notes (the non-obvious ones)
-## Typography
-
-| Role | Font | Rule |
-|---|---|---|
-| h1, h2 | Space Grotesk (`--font-display`) | Headlines ONLY (brand D.3.2), weight **500** (D.3.4 regular/medium), letter-spacing −0.025em, line-height 1.1 |
-| Everything else | Inter (`--font-body`) | Includes h3+ (weight 600), cards, UI |
-| Exception: `.card--featured h3` | Space Grotesk | The news-lead card IS a communicative headline (D.3.2 role, not outline level) — weight 500, `--fs-xl` |
-| Exception: `.panel-title` (h2) | Inter | Panel titles are UI labels, not headlines — weight 600, `--fs-l`. These are the ONLY two exceptions |
-
-Type scale — the ONLY font sizes in the codebase (no ad-hoc rem values):
-
-| Token | Value | Use |
-|---|---|---|
-| `--fs-xs` | 0.8125rem | Meta, badges, labels, eyebrow, dates |
-| `--fs-s` | 0.9rem | Secondary text, nav, forms, footer, TOC |
-| (base) | 1rem | Body, line-height 1.5 (1.65 only on `.prose`; 1.55 `.standfirst`; 1.45 cards/footer) |
-| `--fs-l` | 1.125rem | Card titles, standfirst, panel titles, hero subtitle |
-| `--fs-xl` | 1.5rem | Prose h2, featured title, auth h1 |
-| `--fs-h2` | clamp(1.75–2.5rem) | Section heads, article headlines (news titles run long — full `--fs-h1` would fill the viewport) |
-| `--fs-h1` | clamp(2.25–3.5rem) | Short page heads only ("News & Announcements") |
-| `--fs-display` | clamp(2.5–4.75rem) | Home hero only |
-
-Font tokens end with a generic (`sans-serif`); use them bare —
-`var(--font-body), sans-serif` doubles the generic.
-
-Font loading (prevents any navigation blink): BOTH families are inlined as
-base64 `data:` URIs inside the render-blocking `global.css` — no separate
-font fetch exists, so there is no swap window and no font flash between
-navigations. Metric-matched Arial fallbacks (`Inter Fallback`,
-`Space Grotesk Fallback` — size-adjust/ascent/descent computed from the real
-font metrics, capsize method) cover only the pre-CSS paint. The standalone
-`public/fonts/*.woff2` files are referenced ONLY by
-`public/admin/preview.css` (CMS entry preview) — they look unused but aren't.
-
-## Spacing scale (fluid; mobile ≈ min, desktop ≈ max)
-
-| Token | Value | Use |
-|---|---|---|
-| `--gutter` | `clamp(18px, 4vw, 28px)` | Container/menu side padding |
-| `--space-s` | `12px` | Chip gaps, small stacks |
-| `--space-m` | `24px` | Card/panel padding, grid gaps |
-| `--space-l` | `24–40px` | Article top, meta/cover margins, featured body |
-| `--space-xl` | `clamp(56px, 7vw, 120px)` | Section rhythm: every `.page` block, footer top |
-| `--space-band` | `clamp(72px, 10vw, 160px)` | Hero/auth band + landing-head padding |
-
-Rule: section/container/card padding comes from this table — no ad-hoc px.
-Prose-internal margins (h2/p/blockquote) are content-local and stay as written.
-
-## Radius & elevation
-
-Radius: `--radius 16px` (cards/panels), `--radius-pill` (buttons, chips, inputs).
-
-Only two shadows exist, both Galaxy-tinted, tuned soft/large/low-alpha
-(2025/26 elevation convention: big blur, single-digit alpha at rest):
-
-| Token | Use |
-|---|---|
-| `--shadow-ambient` | Resting cards, panels, search rows |
-| `--shadow-lifted` | Hover/focus cards, dropdowns (suggest, menu, search row), auth card |
-
-Never write a literal `box-shadow`; compose focus rings as
-`0 0 0 2px var(--azure), var(--shadow-lifted)`.
-
-## Surfaces
-
-ONE surface per page — surface differentiation is by page archetype, never a
-mid-page seam (consistency heuristic: a surface change must read as deliberate
-system, not "why is this one gray?"):
-
-- **Editorial pages** (`/news`, articles, static pages, home news section) are
- white — reading surfaces.
-- **Utility/app pages** (`/search`, `/proposals`, `/dashboard`, `/support`)
- are `.band--moon` from the landing head down to the footer — the app canvas
- against which borderless white cards/panels pop (standard app-vs-editorial
- surface split; home's Services section uses the same Moon band).
-- `/` and `/login` own the navy bands (photo hero / nebula).
-
-Cards and panels are always white; ON MOON they are borderless (white-on-moon
-separates by itself), on white they keep the hairline. Chrome is
-single-signal: hover = the elevate gesture (shadow + 2px lift as ONE motion);
-no hover borders, no cover zoom.
-
-## Motion policy (CSS-only, Apple-tier restraint)
-
-ctao.org drifts looping nebula videos under Galaxy vignettes; our translation:
-
-1. **Hero glow (home)** — the photo hero keeps its scrimmed photograph; a
- `--nebula-glow` layer (Cherenkov .30 / Azure .28, no indigo over
- photography) drifts above it inside the `.band-bg` clip frame: 16s
- alternate, transform-only, clearly perceptible within ~10s. `.band-bg`
- (first child of the band) owns `overflow: hidden` — NEVER put overflow on
- the band itself (it would clip the search-suggest dropdown).
-2. **Auth nebula (/login)** — the full `--nebula` stack (Cherenkov .38 /
- Azure .33 / Indigo .80), same 16s drifting layer; static gradient is the
- reduced-motion fallback.
-3. **Whole-page ambient** — `.has-ambient::before`: fixed brand-hue wash
- (Cherenkov .16 / Azure .12 / Indigo .10) drifting on a 36s alternate
- cycle. Opt-in via ``: **home only**. Body text NEVER sits on
- a moving gradient — cards, panels and bands are solid layers above it.
-4. One-shot hero `rise` stagger; unified hover system (below).
-5. **CSS scroll-driven effects** (2026 pass; `animation-timeline`, no JS —
- "no scroll-triggered motion" below means no JS/observer motion): card
- reveal on viewport entry (`view()`, entry 0–35%), header shadow fading in
- over the first 80px of scroll, article reading-progress bar (2px Cherenkov
- under the header — functional navigation state, same budget line as the
- TOC scrollspy). All behind `@supports (animation-timeline: view())`,
- compositor-only (the header shadow animates a pseudo-element's OPACITY,
- never box-shadow in keyframes), auto-killed by the reduce block.
-6. Micro-interactions: nav underline-grow (2px white rule, `scaleX` 0→1 on
- hover, held on `aria-current`) and 1px press-down on `.btn`/navlink
- `:active`. Nothing else.
-
-**One hover language** (mid-pass audit: hovers were inconsistent per element;
-ctao.org uses a single quiet-fade token — we do the same): every interactive
-element transitions color/background/border/shadow on `--dur: 0.25s` +
-`--ease: ease-out` — buttons and links fade, cards/search rows elevate
-(shadow + 2px lift), pagination fills Moon. No width/layout animation.
-
-Rules:
-- ALL `animation`/`transition` declarations live inside the single
- `@media (prefers-reduced-motion: no-preference)` block at the end of
- global.css; the `reduce` block additionally force-disables everything.
-- NO cross-document view transitions (`@view-transition`). The root crossfade
- snapshots the incoming page at its first render opportunity — on slow
- networks that is a half-parsed page, so every navigation read as a
- full-page blink (the diagnosed symptom); scrolled→top navigations crossfade
- two unrelated frames even when it works. Navigation feel comes from static
- HTML + Astro prefetch, not from animating the swap.
-- Animate `transform`/`opacity` only (compositor-only); never
- background-position/box-shadow in keyframes. No `will-change`. No
- `filter: blur` on large layers and no `background-attachment: fixed`
- (broken on iOS). No JS, no scroll-triggered motion.
-- If an effect calls attention to itself as an effect, cut it.
-
-## Photographic hero (home)
-
-Image-led hero per brand D.6 (photography theme: the project): LST-1 under
-the night sky, copied from the content library to `public/brand/hero.jpg`
-(NEVER link `public/uploads` directly — that directory is content-synced).
-Text safety comes from `--hero-scrim` (Galaxy, `to right`:
-.84 → .76 @52% → .44 @78% → .26); ≤640px a flat .80 scrim replaces it (text
-spans the full width on mobile). The logo never sits on the photograph — it
-stays in the header bar (B.1.2 satisfied by construction).
-
-Computed worst-case contrast over photo + scrim + drifting glow (glow fringe
-α .15 Cherenkov / core .28 Azure over the text zone; Cherenkov core is pinned
-≥80% band width by the blob geometry + drift range):
-
-- Small-text zone (eyebrow/subtitle/CTA labels, ≤52% band width): white
- 7.68:1 · Cherenkov subtitle 4.79:1 · eyebrow `#C7CDE9` 4.88:1 (all ≥4.5).
-- Display h1 zone (≤72% band width): white ≥5.20:1 (large text needs 3.0).
-- Mobile flat scrim: white 8.47:1 · subtitle 5.28:1 · eyebrow 5.38:1.
-
-Recompute (script kept in the session scratchpad pattern: composite scrim →
-glow → WCAG luminance per pixel) if the photo, scrim stops, glow alphas or
-blob positions change.
-
-## WCAG 2.1 AA notes (computed, worst-case animation frames)
-
-- Photo hero: see table above. Auth nebula over Galaxy: white 7.9:1,
- Cherenkov subtitle 4.9:1 at the worst blob frame; Cherenkov/Azure CORES stay
- in opposite corners. Ceilings: nebula ≤ .38 Cherenkov / ≤ .35 Azure; glow
- ≤ .30/.28; wash ≤ .16/.12/.10 — recompute if any alpha or placement changes.
-- Translucent header (`color-mix` 88% Galaxy + backdrop blur, solid fallback
- via `@supports`): worst underlying content (white) → bar ≈ rgb(31,31,96);
- white text 14.8:1, muted nav `#C7CDE9` 9.4:1, Cherenkov sub-brand 9.2:1.
-- On Moon: `--link` 6.1:1, `--muted` 7.1:1, `--text` 16.9:1. Ambient wash
- worst overlap: `--link` 5.3:1, `--muted` 6.2:1.
-- Focus visible everywhere; tap targets ≥ 44px; skip link first.
-
-## Article TOC pattern
-
-Rendered only when ≥3 h2/h3. Two renderings of the same list (only one ever
-displayed), both in the quiet Stripe/Apple-docs voice — muted small links,
-Galaxy on hover, no heavy box:
-
-- `