From 8e3cbb6530e89950f2515b4210b91806b0722c13 Mon Sep 17 00:00:00 2001 From: Kuba Sto Date: Thu, 24 Sep 2026 12:54:30 +0200 Subject: [PATCH] Preview styles --- public/admin/preview.css | 299 +++++++++++++++++++++++++++++++++--- scripts/preview-styles.mjs | 39 +++-- src/pages/admin/index.astro | 41 +++++ src/styles/global.css | 134 +++++++++++++--- 4 files changed, 458 insertions(+), 55 deletions(-) diff --git a/public/admin/preview.css b/public/admin/preview.css index cb82dd6..d3c66c4 100644 --- a/public/admin/preview.css +++ b/public/admin/preview.css @@ -1,7 +1,10 @@ /* 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). + The editor preview (public/admin/preview.css) is generated from the + hero, prose and button sections below by scripts/preview-styles.mjs. ========================================================================== */ /* Fonts @@ -126,19 +129,174 @@ button { outline-color: var(--cherenkov-blue); } +/* Utilities + ========================================================================== */ + +.container { + width: min(100% - 2 * var(--gutter), var(--content-width)); + margin-inline: auto; +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border: 0; +} + +.skip-link { + position: absolute; + top: -4rem; + left: 1rem; + z-index: 100; + padding: 0.5rem 1rem; + color: var(--galaxy-blue); + background: var(--white); + border-radius: var(--radius-sm); +} + +.skip-link:focus { + top: 1rem; +} + +.arrow { + display: inline-block; + transition: transform 0.25s ease; +} + +a:hover .arrow, +button:hover .arrow { + transform: translateX(4px); +} + +@keyframes panel-in { + from { + opacity: 0; + transform: translateY(-6px); + } + + to { + opacity: 1; + transform: none; + } +} + +/* Page hero + ========================================================================== */ + +.page-hero { + --hero-veil: linear-gradient( + 90deg, + rgba(0, 0, 74, 0.72) 0%, + rgba(0, 0, 74, 0.45) 45%, + rgba(0, 0, 74, 0) 75% + ); + + margin-top: calc(-1 * var(--header-height)); + padding: calc(var(--header-height) + 3rem) 0 12rem; + color: var(--white); + background: + var(--grain), + linear-gradient(180deg, rgba(0, 0, 74, 0.55) 0, rgba(0, 0, 74, 0) 10rem), + var(--hero-veil), + #00004a; + border-bottom-right-radius: var(--radius-lg); +} + +/* Pages without a photo: no room needed for the figure to overlap */ +.page-hero--compact { + padding-bottom: 4rem; +} + +.breadcrumb ol { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 0 0 1rem; + padding: 0; + font-size: 0.875rem; + list-style: none; +} + +.breadcrumb li + li::before { + content: "›"; + margin-right: 0.5rem; + color: rgba(255, 255, 255, 0.5); +} + +.breadcrumb a { + color: rgba(255, 255, 255, 0.6); +} + +.breadcrumb a:hover { + color: var(--white); +} + +.breadcrumb [aria-current] { + font-weight: 500; + color: var(--white); +} + +/* Headline: Space Grotesk medium, white on dark (D.3.4) */ +.page-hero__title { + max-width: 24ch; + margin: 0 0 1.25rem; + font-family: var(--font-head); + font-size: clamp(2rem, 5vw, 3.75rem); + font-weight: 500; + line-height: 1.05; + letter-spacing: -0.02em; + text-wrap: balance; + overflow-wrap: anywhere; +} + +.page-hero__lead { + max-width: 70ch; + margin: 0; + font-size: 1.0625rem; + font-weight: 500; + line-height: 1.4; +} + +/* Photo pulled up over the hero; flat, no shadow */ +.page-figure { + margin: -9rem 0 4rem; +} + +.page-figure img { + width: 100%; + border-radius: var(--radius-sm); +} + +.page-figure figcaption { + margin-top: 0.75rem; + font-size: 0.8125rem; + color: var(--text-muted); +} + /* Prose ========================================================================== */ -body { +.prose { padding-bottom: 5rem; + overflow-wrap: anywhere; } -body > * { +.prose--no-image { + padding-top: 4rem; +} + +.prose > * { max-width: 64rem; } /* Galaxy Blue headline on light background (D.3.4) */ -body h2 { +.prose h2 { margin: 3rem 0 1rem; font-family: var(--font-head); font-size: clamp(1.75rem, 3vw, 2.375rem); @@ -148,26 +306,26 @@ body h2 { color: var(--galaxy-blue); } -body h2:first-child { +.prose h2:first-child { margin-top: 0; } -body p { +.prose 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 { +.prose a:not(.btn):hover, +.prose 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 { +.prose .highlight, +.prose blockquote { margin: 2.5rem 0; padding-left: 1.5rem; font-size: 1.375rem; @@ -177,6 +335,61 @@ body blockquote { border-left: 4px solid var(--cherenkov-blue); } +.prose blockquote p { + font-size: inherit; + line-height: inherit; +} + +.prose h3, +.prose h4, +.prose h5, +.prose h6 { + font-family: var(--font-head); + color: var(--galaxy-blue); +} + +.prose li { + margin-bottom: 0.5rem; +} + +.prose img { + border-radius: var(--radius-sm); +} + +.prose pre { + padding: 1rem; + overflow-x: auto; + background: var(--moon-gray); +} + +.prose code { + overflow-wrap: normal; +} + +.prose table { + display: block; + overflow-x: auto; + border-collapse: collapse; +} + +.prose th, +.prose td { + padding: 0.75rem; + text-align: left; + border-bottom: 1px solid #ddd; +} + +.prose hr { + margin: 2rem 0; + border: 0; + border-top: 1px solid #ddd; +} + +/* Placeholder pages: empty body until their content exists */ +.placeholder-body { + min-height: 12rem; +} + /* Buttons ========================================================================== */ @@ -217,16 +430,60 @@ body blockquote { 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); } +/* Responsive page + ========================================================================== */ -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; } +@media (max-width: 800px) { + /* Text spans the full width here, so keep at least 50% navy everywhere */ + .page-hero { + --hero-veil: linear-gradient( + 90deg, + rgba(0, 0, 74, 0.72) 0%, + rgba(0, 0, 74, 0.5) 100% + ); + + padding-bottom: 8rem; + border-bottom-right-radius: var(--radius-md); + } + + .page-hero--compact { + padding-bottom: 4rem; + } + + .page-figure { + margin-top: -6rem; + } +} + +/* Preferences + ========================================================================== */ + +/* Windows High Contrast: drop the decorative hero background behind system text */ +@media (forced-colors: active) { + .page-hero { + background-image: none; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation: none !important; + transition: none !important; + } +} + +/* Preview frame + ========================================================================== */ + +/* There is no site header above the hero */ +.page-hero { + margin-top: 0; + padding-top: 3rem; +} + +/* Sveltia wraps the image preview in a paragraph */ +.page-figure p { + margin: 0; +} diff --git a/scripts/preview-styles.mjs b/scripts/preview-styles.mjs index 74c8538..bce66d2 100644 --- a/scripts/preview-styles.mjs +++ b/scripts/preview-styles.mjs @@ -1,13 +1,32 @@ -// Derive editor body styles from the mockup CSS so the preview cannot drift. +// Derive editor preview styles from the site CSS so the preview cannot drift. +// The preview template in src/pages/admin/index.astro renders the same markup +// as the Example pages, so the page hero, figure and prose rules apply as-is. 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'); +const css = readFileSync(new URL('../src/styles/global.css', import.meta.url), 'utf8').replace(/\r\n/g, '\n'); +const between = (start, end) => { + const from = css.indexOf(start); + const to = end ? css.indexOf(end, from) : css.length; + if (from < 0 || to < 0) throw new Error(`preview-styles: marker not found in global.css: ${from < 0 ? start : end}`); + return css.slice(from, to).trim(); +}; +const preview = [ + between('/* ===', '/* Header'), // fonts, tokens, base, utilities + between('/* Page hero', '/* Footer'), // hero, figure, prose, buttons + between('/* Responsive page'), // responsive page, preferences + `/* Preview frame + ========================================================================== */ + +/* There is no site header above the hero */ +.page-hero { + margin-top: 0; + padding-top: 3rem; +} + +/* Sveltia wraps the image preview in a paragraph */ +.page-figure p { + margin: 0; +}`, +]; 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; } -`); + '/* Generated by scripts/preview-styles.mjs; edit src/styles/global.css. */\n\n' + preview.join('\n\n') + '\n'); diff --git a/src/pages/admin/index.astro b/src/pages/admin/index.astro index d0e92d7..a252274 100644 --- a/src/pages/admin/index.astro +++ b/src/pages/admin/index.astro @@ -5,6 +5,9 @@ // deliberate — see README); note the bundle still fetches its own UI fonts // from jsDelivr at runtime. Kept is:inline so Astro doesn't process/bundle it. // CTAO branding on the sign-in screen comes from the documented `logo` option. +import examples from '../../data/examples.json'; + +const previewNames = examples.map(({ slug }) => slug); --- @@ -32,5 +35,43 @@ // Generated from the site's CSS; see scripts/preview-styles.mjs. window.CMS?.registerPreviewStyle?.('/admin/preview.css'); + diff --git a/src/styles/global.css b/src/styles/global.css index e9caede..709927d 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,6 +1,8 @@ /* ========================================================================== CTAO Science Portal: content page mockup Colours and type follow the CTAO Brand Guidelines (April 2024). + The editor preview (public/admin/preview.css) is generated from the + hero, prose and button sections below by scripts/preview-styles.mjs. ========================================================================== */ /* Fonts @@ -284,6 +286,19 @@ button:hover .arrow { color: var(--cherenkov-blue); } +.submenu a[aria-current="page"] { + color: var(--cherenkov-blue); +} + +/* Languages without a translation yet */ +.submenu__unavailable { + display: block; + padding: 0.5rem 0.75rem; + font-size: 0.9375rem; + color: #b3b3c9; + cursor: default; +} + /* Main nav ========================================================================== */ @@ -321,6 +336,10 @@ button:hover .arrow { text-decoration: none; } +.site-nav__link[aria-current="page"] { + color: var(--cherenkov-blue); +} + .site-nav__label { position: relative; } @@ -445,6 +464,7 @@ button:hover .arrow { .search-panel__input { flex: 1; + min-width: 0; padding: 0.75rem 0; font: inherit; font-size: clamp(1.25rem, 3vw, 2rem); @@ -454,6 +474,11 @@ button:hover .arrow { outline: none; } +.search-panel__input:focus-visible { + outline: 2px solid var(--cherenkov-blue); + outline-offset: 4px; +} + .search-panel__input::placeholder { color: rgba(255, 255, 255, 0.5); } @@ -572,7 +597,9 @@ button:hover .arrow { } } -/* Page hero ========================================================================== */ +/* Page hero + ========================================================================== */ + .page-hero { --hero-veil: linear-gradient( 90deg, @@ -592,6 +619,11 @@ button:hover .arrow { border-bottom-right-radius: var(--radius-lg); } +/* Pages without a photo: no room needed for the figure to overlap */ +.page-hero--compact { + padding-bottom: 4rem; +} + .breadcrumb ol { display: flex; flex-wrap: wrap; @@ -631,6 +663,7 @@ button:hover .arrow { line-height: 1.05; letter-spacing: -0.02em; text-wrap: balance; + overflow-wrap: anywhere; } .page-hero__lead { @@ -662,6 +695,11 @@ button:hover .arrow { .prose { padding-bottom: 5rem; + overflow-wrap: anywhere; +} + +.prose--no-image { + padding-top: 4rem; } .prose > * { @@ -708,6 +746,61 @@ button:hover .arrow { border-left: 4px solid var(--cherenkov-blue); } +.prose blockquote p { + font-size: inherit; + line-height: inherit; +} + +.prose h3, +.prose h4, +.prose h5, +.prose h6 { + font-family: var(--font-head); + color: var(--galaxy-blue); +} + +.prose li { + margin-bottom: 0.5rem; +} + +.prose img { + border-radius: var(--radius-sm); +} + +.prose pre { + padding: 1rem; + overflow-x: auto; + background: var(--moon-gray); +} + +.prose code { + overflow-wrap: normal; +} + +.prose table { + display: block; + overflow-x: auto; + border-collapse: collapse; +} + +.prose th, +.prose td { + padding: 0.75rem; + text-align: left; + border-bottom: 1px solid #ddd; +} + +.prose hr { + margin: 2rem 0; + border: 0; + border-top: 1px solid #ddd; +} + +/* Placeholder pages: empty body until their content exists */ +.placeholder-body { + min-height: 12rem; +} + /* Buttons ========================================================================== */ @@ -751,6 +844,18 @@ button:hover .arrow { /* Footer ========================================================================== */ +/* Keep the footer at the bottom of short pages */ +body { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +main { + flex: 1; + min-width: 0; +} + .site-footer { margin-top: 2rem; padding: 3rem 0; @@ -807,6 +912,10 @@ button:hover .arrow { border-bottom-right-radius: var(--radius-md); } + .page-hero--compact { + padding-bottom: 4rem; + } + .page-figure { margin-top: -6rem; } @@ -830,26 +939,3 @@ button:hover .arrow { transition: none !important; } } - - -/* Page variants and Markdown elements. Preview styles derive from this file. */ -body { min-height: 100vh; display: flex; flex-direction: column; } -main { flex: 1; min-width: 0; } -.page-hero--compact { padding-bottom: 4rem; } -.placeholder-body { min-height: 12rem; } -.prose--no-image { padding-top: 4rem; } -.prose { overflow-wrap: anywhere; } -.prose h3, .prose h4, .prose h5, .prose h6 { color: var(--galaxy-blue); font-family: var(--font-head); } -.prose li { margin-bottom: 0.5rem; } -.prose blockquote p { font-size: inherit; line-height: inherit; } -.prose pre { padding: 1rem; overflow-x: auto; background: var(--moon-gray); } -.prose code { overflow-wrap: normal; } -.prose table { display: block; overflow-x: auto; border-collapse: collapse; } -.prose th, .prose td { padding: 0.75rem; border-bottom: 1px solid #ddd; text-align: left; } -.prose hr { margin: 2rem 0; border: 0; border-top: 1px solid #ddd; } -.prose img { border-radius: var(--radius-sm); } -.submenu a[aria-current="page"], .site-nav__link[aria-current="page"] { color: var(--cherenkov-blue); } -.submenu__unavailable { display: block; padding: 0.5rem 0.75rem; font-size: 0.9375rem; color: #b3b3c9; cursor: default; } -.search-panel__input { min-width: 0; } -.search-panel__input:focus-visible { outline: 2px solid var(--cherenkov-blue); outline-offset: 4px; } -.page-hero__title { overflow-wrap: anywhere; }