Compare commits
4
Commits
061f94c85f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26224b2ab7 | ||
|
|
fff248f25f | ||
|
|
6897103041 | ||
|
|
d4769e9a33 |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Static Astro site with a git-based CMS (Sveltia). Code lives here; editorial
|
Static Astro site with a git-based CMS (Sveltia). Code lives here; editorial
|
||||||
content lives in the separate [`ctao/content`](https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content)
|
content lives in the separate [`ctao/content`](https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content)
|
||||||
repository. Four Example pages are editable; all other pages are placeholders.
|
repository. The Home page and four Example pages are editable; the rest are placeholders.
|
||||||
|
|
||||||
- Live demo: https://astro.isl-dev.grid.cyfronet.pl
|
- Live demo: https://astro.isl-dev.grid.cyfronet.pl
|
||||||
- Editor: https://astro.isl-dev.grid.cyfronet.pl/admin/ (Gitea sign-in)
|
- Editor: https://astro.isl-dev.grid.cyfronet.pl/admin/ (Gitea sign-in)
|
||||||
@@ -42,7 +42,7 @@ Server setup is in [deploy/README.md](deploy/README.md).
|
|||||||
| Path | Purpose |
|
| Path | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `src/layouts/Base.astro` | Header, navigation, footer, page shell |
|
| `src/layouts/Base.astro` | Header, navigation, footer, page shell |
|
||||||
| `src/pages/example/[slug].astro` | Template for the editable Example pages |
|
| `src/layouts/ContentPage.astro` | Layout of the editable pages (Home and Examples) |
|
||||||
| `src/data/examples.json` | Example pages: menu labels, order, slugs |
|
| `src/data/examples.json` | Example pages: menu labels, order, slugs |
|
||||||
| `src/data/placeholders.json` | Placeholder pages |
|
| `src/data/placeholders.json` | Placeholder pages |
|
||||||
| `src/styles/global.css` | All site styles |
|
| `src/styles/global.css` | All site styles |
|
||||||
|
|||||||
@@ -44,6 +44,30 @@ media_libraries:
|
|||||||
optimize: true
|
optimize: true
|
||||||
|
|
||||||
collections:
|
collections:
|
||||||
|
- name: home
|
||||||
|
label: "Home page"
|
||||||
|
files:
|
||||||
|
- name: home
|
||||||
|
label: "Home page"
|
||||||
|
file: "pages/home.md"
|
||||||
|
format: frontmatter
|
||||||
|
fields:
|
||||||
|
- { name: title, label: "Title", widget: string }
|
||||||
|
- { name: introduction, label: "Introduction", widget: text, required: false }
|
||||||
|
- name: images
|
||||||
|
label: "Images"
|
||||||
|
label_singular: "image"
|
||||||
|
widget: list
|
||||||
|
required: false
|
||||||
|
max: 2
|
||||||
|
hint: "Up to two images, shown side by side."
|
||||||
|
fields:
|
||||||
|
- { name: image, label: "Image", widget: image }
|
||||||
|
- { name: alt, label: "Alternative text", widget: string, required: false, hint: "Describe the image; leave empty only for decorative images." }
|
||||||
|
- { name: caption, label: "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: examples
|
- name: examples
|
||||||
label: "Example pages"
|
label: "Example pages"
|
||||||
files:
|
files:
|
||||||
|
|||||||
+27
-46
@@ -53,9 +53,12 @@
|
|||||||
--gutter: clamp(1rem, 4vw, 2.5rem);
|
--gutter: clamp(1rem, 4vw, 2.5rem);
|
||||||
--header-height: 7rem;
|
--header-height: 7rem;
|
||||||
--header-height-scrolled: 5rem;
|
--header-height-scrolled: 5rem;
|
||||||
--radius-lg: 60px;
|
/* Large decorative corners (hero, header, search panel): grows with the
|
||||||
--radius-md: 30px;
|
viewport instead of switching at breakpoints */
|
||||||
|
--radius-lg: min(15vw, 180px);
|
||||||
--radius-sm: 15px;
|
--radius-sm: 15px;
|
||||||
|
/* How far photos are pulled up into the hero */
|
||||||
|
--hero-overlap: clamp(6rem, 11.25vw, 9rem);
|
||||||
/* Dropdown submenus (Example, language) */
|
/* Dropdown submenus (Example, language) */
|
||||||
--radius-menu: 8px;
|
--radius-menu: 8px;
|
||||||
--transition: 0.3s ease-in-out;
|
--transition: 0.3s ease-in-out;
|
||||||
@@ -190,21 +193,10 @@ button:hover .arrow {
|
|||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
.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));
|
margin-top: calc(-1 * var(--header-height));
|
||||||
padding: calc(var(--header-height) + 3rem) 0 12rem;
|
padding: calc(var(--header-height) + 3rem) 0 calc(var(--hero-overlap) + 3rem);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
background:
|
background: var(--grain), var(--galaxy-blue);
|
||||||
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);
|
border-bottom-right-radius: var(--radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +257,7 @@ button:hover .arrow {
|
|||||||
|
|
||||||
/* Photo pulled up over the hero; flat, no shadow */
|
/* Photo pulled up over the hero; flat, no shadow */
|
||||||
.page-figure {
|
.page-figure {
|
||||||
margin: -9rem 0 4rem;
|
margin: calc(-1 * var(--hero-overlap)) 0 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-figure img {
|
.page-figure img {
|
||||||
@@ -279,6 +271,25 @@ button:hover .arrow {
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Two photos side by side (Home), pulled up over the hero together; they
|
||||||
|
stack by themselves once each would be narrower than 18rem */
|
||||||
|
.page-figures {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin: calc(-1 * var(--hero-overlap)) 0 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-figures .page-figure {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Same shape for both photos, whatever the uploads' proportions */
|
||||||
|
.page-figures img {
|
||||||
|
aspect-ratio: 3 / 2;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
/* Prose
|
/* Prose
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
@@ -430,31 +441,6 @@ button:hover .arrow {
|
|||||||
filter: brightness(1.1);
|
filter: brightness(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive page
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
@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
|
/* Preferences
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
@@ -482,8 +468,3 @@ button:hover .arrow {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
padding-top: 3rem;
|
padding-top: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sveltia wraps the image preview in a paragraph */
|
|
||||||
.page-figure p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ for (const { from, to } of targets) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pages = join(root, 'src/content/pages');
|
const pages = join(root, 'src/content/pages');
|
||||||
for (const { slug } of examples) {
|
for (const slug of ['home', ...examples.map(({ slug }) => slug)]) {
|
||||||
if (!existsSync(join(pages, `${slug}.md`))) {
|
if (!existsSync(join(pages, `${slug}.md`))) {
|
||||||
throw new Error(`Missing ${join(pages, `${slug}.md`)}. Check the content checkout, or run npm run content:link -- <path>.`);
|
throw new Error(`Missing ${join(pages, `${slug}.md`)}. Check the content checkout, or run npm run content:link -- <path>.`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const between = (start, end) => {
|
|||||||
const preview = [
|
const preview = [
|
||||||
between('/* ===', '/* Header'), // fonts, tokens, base, utilities
|
between('/* ===', '/* Header'), // fonts, tokens, base, utilities
|
||||||
between('/* Page hero', '/* Footer'), // hero, figure, prose, buttons
|
between('/* Page hero', '/* Footer'), // hero, figure, prose, buttons
|
||||||
between('/* Responsive page'), // responsive page, preferences
|
between('/* Preferences'), // high contrast, reduced motion
|
||||||
`/* Preview frame
|
`/* Preview frame
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
@@ -21,11 +21,6 @@ const preview = [
|
|||||||
.page-hero {
|
.page-hero {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
padding-top: 3rem;
|
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),
|
writeFileSync(new URL('../public/admin/preview.css', import.meta.url),
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
---
|
---
|
||||||
interface Props { title: string; introduction?: string; example?: boolean; hasImage?: boolean }
|
interface Props { title: string; introduction?: string; example?: boolean; breadcrumb?: boolean; hasImage?: boolean }
|
||||||
const { title, introduction, example = false, hasImage = false } = Astro.props;
|
const { title, introduction, example = false, breadcrumb = true, hasImage = false } = Astro.props;
|
||||||
---
|
---
|
||||||
<section class:list={['page-hero', { 'page-hero--compact': !hasImage }]}>
|
<section class:list={['page-hero', { 'page-hero--compact': !hasImage }]}>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<nav class="breadcrumb" aria-label="Breadcrumb">
|
{breadcrumb && <nav class="breadcrumb" aria-label="Breadcrumb">
|
||||||
<ol>
|
<ol>
|
||||||
{Astro.url.pathname !== '/' && <li><a href="/">Home</a></li>}
|
{Astro.url.pathname !== '/' && <li><a href="/">Home</a></li>}
|
||||||
{example && <li>Example</li>}
|
{example && <li>Example</li>}
|
||||||
<li aria-current="page">{title}</li>
|
<li aria-current="page">{title}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>}
|
||||||
<h1 class="page-hero__title">{title}</h1>
|
<h1 class="page-hero__title">{title}</h1>
|
||||||
{introduction && <p class="page-hero__lead">{introduction}</p>}
|
{introduction && <p class="page-hero__lead">{introduction}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,17 +8,24 @@ const destination = z.string().refine(
|
|||||||
'Use a site path, https:// URL, mailto: address, or fragment.',
|
'Use a site path, https:// URL, mailto: address, or fragment.',
|
||||||
);
|
);
|
||||||
const pages = defineCollection({
|
const pages = defineCollection({
|
||||||
// Only these four files can become public content pages.
|
// Only the Home page and the four Example files can become public pages.
|
||||||
loader: glob({
|
loader: glob({
|
||||||
pattern: `{${examples.map(({ slug }) => slug).join(',')}}.md`,
|
pattern: `{home,${examples.map(({ slug }) => slug).join(',')}}.md`,
|
||||||
base: './src/content/pages',
|
base: './src/content/pages',
|
||||||
}),
|
}),
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
title: z.string().min(1),
|
title: z.string().min(1),
|
||||||
introduction: z.string().optional(),
|
introduction: z.string().optional(),
|
||||||
|
// Example pages: one photo
|
||||||
image: z.string().optional(),
|
image: z.string().optional(),
|
||||||
imageAlt: z.string().optional(),
|
imageAlt: z.string().optional(),
|
||||||
imageCaption: z.string().optional(),
|
imageCaption: z.string().optional(),
|
||||||
|
// Home: up to two photos side by side
|
||||||
|
images: z.array(z.object({
|
||||||
|
image: z.string().min(1),
|
||||||
|
alt: z.string().optional(),
|
||||||
|
caption: z.string().optional(),
|
||||||
|
})).max(2).optional(),
|
||||||
buttonLabel: z.string().optional(),
|
buttonLabel: z.string().optional(),
|
||||||
buttonUrl: destination.optional(),
|
buttonUrl: destination.optional(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
// Editable content page: hero, one photo (Example pages) or two side by side
|
||||||
|
// (Home), Markdown body and optional button. The CMS preview template in
|
||||||
|
// src/pages/admin/index.astro mirrors this markup.
|
||||||
|
import type { CollectionEntry } from 'astro:content';
|
||||||
|
import { render } from 'astro:content';
|
||||||
|
import Base from './Base.astro';
|
||||||
|
import PageHero from '../components/PageHero.astro';
|
||||||
|
|
||||||
|
interface Props { page: CollectionEntry<'pages'>; example?: boolean; breadcrumb?: boolean }
|
||||||
|
const { page, example = false, breadcrumb = true } = Astro.props;
|
||||||
|
const { Content } = await render(page);
|
||||||
|
const { title, introduction, image, imageAlt, imageCaption, images, buttonLabel, buttonUrl } = page.data;
|
||||||
|
const photos = images ?? (image ? [{ image, alt: imageAlt, caption: imageCaption }] : []);
|
||||||
|
---
|
||||||
|
<Base title={example ? title : undefined} description={introduction}>
|
||||||
|
<PageHero title={title} introduction={introduction} example={example} breadcrumb={breadcrumb} hasImage={photos.length > 0} />
|
||||||
|
{photos.length > 0 && <div class="container">
|
||||||
|
<div class:list={{ 'page-figures': photos.length > 1 }}>
|
||||||
|
{photos.map(({ image, alt, caption }) => (
|
||||||
|
<figure class="page-figure">
|
||||||
|
<img src={image} alt={alt || ''} />
|
||||||
|
{caption && <figcaption>{caption}</figcaption>}
|
||||||
|
</figure>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
<article class:list={['container', 'prose', { 'prose--no-image': photos.length === 0 }]}>
|
||||||
|
<Content />
|
||||||
|
{buttonLabel && buttonUrl && <p>
|
||||||
|
<a class="btn btn--primary" href={buttonUrl}>
|
||||||
|
<span class="btn__label">{buttonLabel}</span><span class="arrow" aria-hidden="true">→</span>
|
||||||
|
</a>
|
||||||
|
</p>}
|
||||||
|
</article>
|
||||||
|
</Base>
|
||||||
+33
-12
@@ -36,40 +36,61 @@ const previewNames = examples.map(({ slug }) => slug);
|
|||||||
window.CMS?.registerPreviewStyle?.('/admin/preview.css');
|
window.CMS?.registerPreviewStyle?.('/admin/preview.css');
|
||||||
</script>
|
</script>
|
||||||
<script is:inline define:vars={{ previewNames }}>
|
<script is:inline define:vars={{ previewNames }}>
|
||||||
// Preview pane: the same markup as src/pages/example/[slug].astro and
|
// Preview pane: the same markup as src/layouts/ContentPage.astro and
|
||||||
// PageHero.astro, without field labels. Sveltia looks templates up by
|
// PageHero.astro, without field labels. Sveltia looks templates up by
|
||||||
// file name for file collections, so register one per Example page.
|
// file name for file collections, so register one per page.
|
||||||
const { h, rf } = window;
|
const { h, rf } = window;
|
||||||
const PagePreview = ({ entry, widgetFor }) => {
|
// Our own <img>: Sveltia's image widget keeps the image transparent until
|
||||||
|
// a visibility check that does not complete inside a custom template.
|
||||||
|
// The asset URL starts as the public path and becomes a blob URL once
|
||||||
|
// Sveltia has fetched the file, which covers images not deployed yet.
|
||||||
|
const PreviewImage = ({ asset, fallback, alt }) => h('img', {
|
||||||
|
src: asset ? String(asset) : fallback,
|
||||||
|
alt,
|
||||||
|
onError: ({ currentTarget: img }) => {
|
||||||
|
const retry = (tries) => {
|
||||||
|
const url = asset && String(asset);
|
||||||
|
if (url && url !== img.getAttribute('src')) img.src = url;
|
||||||
|
else if (tries) setTimeout(() => retry(tries - 1), 500);
|
||||||
|
};
|
||||||
|
retry(10);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const PagePreview = ({ entry, widgetFor, getAsset, home = false }) => {
|
||||||
const get = (key) => entry.getIn(['data', key]);
|
const get = (key) => entry.getIn(['data', key]);
|
||||||
const title = get('title') || '';
|
const title = get('title') || '';
|
||||||
const introduction = get('introduction');
|
const introduction = get('introduction');
|
||||||
const image = get('image');
|
|
||||||
const imageCaption = get('imageCaption');
|
|
||||||
const buttonLabel = get('buttonLabel');
|
const buttonLabel = get('buttonLabel');
|
||||||
const buttonUrl = get('buttonUrl');
|
const buttonUrl = get('buttonUrl');
|
||||||
|
// Home: a list of up to two photos; Example pages: a single photo
|
||||||
|
const photos = home
|
||||||
|
? (get('images')?.toJS?.() ?? []).filter((item) => item?.image)
|
||||||
|
: get('image') ? [{ image: get('image'), alt: get('imageAlt'), caption: get('imageCaption') }] : [];
|
||||||
return h(rf, null,
|
return h(rf, null,
|
||||||
h('section', { className: image ? 'page-hero' : 'page-hero page-hero--compact' },
|
h('section', { className: photos.length ? 'page-hero' : 'page-hero page-hero--compact' },
|
||||||
h('div', { className: 'container' },
|
h('div', { className: 'container' },
|
||||||
h('nav', { className: 'breadcrumb', 'aria-label': 'Breadcrumb' },
|
!home && h('nav', { className: 'breadcrumb', 'aria-label': 'Breadcrumb' },
|
||||||
h('ol', null,
|
h('ol', null,
|
||||||
h('li', null, h('a', { href: '/' }, 'Home')),
|
h('li', null, h('a', { href: '/' }, 'Home')),
|
||||||
h('li', null, 'Example'),
|
h('li', null, 'Example'),
|
||||||
h('li', { 'aria-current': 'page' }, title))),
|
h('li', { 'aria-current': 'page' }, title))),
|
||||||
h('h1', { className: 'page-hero__title' }, title),
|
h('h1', { className: 'page-hero__title' }, title),
|
||||||
introduction && h('p', { className: 'page-hero__lead' }, introduction))),
|
introduction && h('p', { className: 'page-hero__lead' }, introduction))),
|
||||||
image && h('div', { className: 'container' },
|
photos.length > 0 && h('div', { className: 'container' },
|
||||||
h('figure', { className: 'page-figure' },
|
h('div', { className: photos.length > 1 ? 'page-figures' : undefined },
|
||||||
widgetFor('image'),
|
photos.map(({ image, alt, caption }, index) => h('figure', { className: 'page-figure', key: index },
|
||||||
imageCaption && h('figcaption', null, imageCaption))),
|
h(PreviewImage, { key: image, asset: getAsset(image), fallback: image, alt: alt || '' }),
|
||||||
h('article', { className: image ? 'container prose' : 'container prose prose--no-image' },
|
caption && h('figcaption', null, caption))))),
|
||||||
|
h('article', { className: photos.length ? 'container prose' : 'container prose prose--no-image' },
|
||||||
widgetFor('body'),
|
widgetFor('body'),
|
||||||
buttonLabel && buttonUrl && h('p', null,
|
buttonLabel && buttonUrl && h('p', null,
|
||||||
h('a', { className: 'btn btn--primary', href: buttonUrl },
|
h('a', { className: 'btn btn--primary', href: buttonUrl },
|
||||||
h('span', { className: 'btn__label' }, buttonLabel),
|
h('span', { className: 'btn__label' }, buttonLabel),
|
||||||
h('span', { className: 'arrow', 'aria-hidden': 'true' }, '→')))));
|
h('span', { className: 'arrow', 'aria-hidden': 'true' }, '→')))));
|
||||||
};
|
};
|
||||||
|
const HomePreview = (props) => h(PagePreview, { ...props, home: true });
|
||||||
if (window.CMS?.registerPreviewTemplate && h) {
|
if (window.CMS?.registerPreviewTemplate && h) {
|
||||||
|
window.CMS.registerPreviewTemplate('home', HomePreview);
|
||||||
for (const name of previewNames) window.CMS.registerPreviewTemplate(name, PagePreview);
|
for (const name of previewNames) window.CMS.registerPreviewTemplate(name, PagePreview);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { getCollection, render } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import Base from '../../layouts/Base.astro';
|
import ContentPage from '../../layouts/ContentPage.astro';
|
||||||
import PageHero from '../../components/PageHero.astro';
|
|
||||||
import examples from '../../data/examples.json';
|
import examples from '../../data/examples.json';
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
@@ -13,23 +12,5 @@ export async function getStaticPaths() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { page } = Astro.props;
|
const { page } = Astro.props;
|
||||||
const { Content } = await render(page);
|
|
||||||
const { title, introduction, image, imageAlt, imageCaption, buttonLabel, buttonUrl } = page.data;
|
|
||||||
---
|
---
|
||||||
<Base title={title} description={introduction}>
|
<ContentPage page={page} example />
|
||||||
<PageHero title={title} introduction={introduction} example hasImage={Boolean(image)} />
|
|
||||||
{image && <div class="container">
|
|
||||||
<figure class="page-figure">
|
|
||||||
<img src={image} alt={imageAlt || ''} />
|
|
||||||
{imageCaption && <figcaption>{imageCaption}</figcaption>}
|
|
||||||
</figure>
|
|
||||||
</div>}
|
|
||||||
<article class:list={['container', 'prose', { 'prose--no-image': !image }]}>
|
|
||||||
<Content />
|
|
||||||
{buttonLabel && buttonUrl && <p>
|
|
||||||
<a class="btn btn--primary" href={buttonUrl}>
|
|
||||||
<span class="btn__label">{buttonLabel}</span><span class="arrow" aria-hidden="true">→</span>
|
|
||||||
</a>
|
|
||||||
</p>}
|
|
||||||
</article>
|
|
||||||
</Base>
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
---
|
---
|
||||||
import Placeholder from '../layouts/Placeholder.astro';
|
import { getEntry } from 'astro:content';
|
||||||
|
import ContentPage from '../layouts/ContentPage.astro';
|
||||||
|
|
||||||
|
const page = await getEntry('pages', 'home');
|
||||||
|
if (!page) throw new Error('Missing Home page: pages/home.md. Check the content checkout (npm run content:link -- <path>).');
|
||||||
---
|
---
|
||||||
<Placeholder title="CTAO Science Portal" />
|
<ContentPage page={page} breadcrumb={false} />
|
||||||
|
|||||||
+31
-64
@@ -51,9 +51,10 @@
|
|||||||
--gutter: clamp(1rem, 4vw, 2.5rem);
|
--gutter: clamp(1rem, 4vw, 2.5rem);
|
||||||
--header-height: 7rem;
|
--header-height: 7rem;
|
||||||
--header-height-scrolled: 5rem;
|
--header-height-scrolled: 5rem;
|
||||||
--radius-lg: 60px;
|
--radius-lg: min(15vw, 180px);
|
||||||
--radius-md: 30px;
|
|
||||||
--radius-sm: 15px;
|
--radius-sm: 15px;
|
||||||
|
/* How far photos are pulled up into the hero */
|
||||||
|
--hero-overlap: clamp(6rem, 11.25vw, 9rem);
|
||||||
/* Dropdown submenus (Example, language) */
|
/* Dropdown submenus (Example, language) */
|
||||||
--radius-menu: 8px;
|
--radius-menu: 8px;
|
||||||
--transition: 0.3s ease-in-out;
|
--transition: 0.3s ease-in-out;
|
||||||
@@ -196,17 +197,17 @@ button:hover .arrow {
|
|||||||
transition: background-color var(--transition);
|
transition: background-color var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Solid once the page scrolls or a full-width panel is open */
|
|
||||||
.site-header.is-scrolled,
|
.site-header.is-scrolled,
|
||||||
.site-header.is-open {
|
.site-header.is-open {
|
||||||
background: var(--galaxy-blue);
|
background: var(--galaxy-blue);
|
||||||
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-header__inner {
|
.site-header__inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 1.5rem;
|
gap: clamp(0.5rem, 4vw, 1.5rem);
|
||||||
width: min(100% - 2 * var(--gutter), var(--content-width));
|
width: min(100% - 2 * var(--gutter), var(--content-width));
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
@@ -223,8 +224,8 @@ button:hover .arrow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.site-header__logo img {
|
.site-header__logo img {
|
||||||
width: 153px;
|
width: clamp(120px, 32vw, 153px);
|
||||||
height: 46px;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Submenus: one disclosure dropdown shared by Example and the language menu
|
/* Submenus: one disclosure dropdown shared by Example and the language menu
|
||||||
@@ -380,7 +381,7 @@ button:hover .arrow {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.625rem;
|
gap: clamp(0.25rem, 1.5vw, 0.625rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pill {
|
.pill {
|
||||||
@@ -503,7 +504,7 @@ button:hover .arrow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.site-header:not(.is-open) {
|
.site-header:not(.is-open) {
|
||||||
border-bottom-right-radius: var(--radius-md);
|
border-bottom-right-radius: var(--radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-header__signin {
|
.site-header__signin {
|
||||||
@@ -580,42 +581,14 @@ button:hover .arrow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.site-header__logo img {
|
|
||||||
width: 120px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 359px) {
|
|
||||||
.site-header__inner {
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-header__tools {
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page hero
|
/* Page hero
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
.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));
|
margin-top: calc(-1 * var(--header-height));
|
||||||
padding: calc(var(--header-height) + 3rem) 0 12rem;
|
padding: calc(var(--header-height) + 3rem) 0 calc(var(--hero-overlap) + 3rem);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
background:
|
background: var(--grain), var(--galaxy-blue);
|
||||||
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);
|
border-bottom-right-radius: var(--radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +649,7 @@ button:hover .arrow {
|
|||||||
|
|
||||||
/* Photo pulled up over the hero; flat, no shadow */
|
/* Photo pulled up over the hero; flat, no shadow */
|
||||||
.page-figure {
|
.page-figure {
|
||||||
margin: -9rem 0 4rem;
|
margin: calc(-1 * var(--hero-overlap)) 0 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-figure img {
|
.page-figure img {
|
||||||
@@ -690,6 +663,25 @@ button:hover .arrow {
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Two photos side by side (Home), pulled up over the hero together; they
|
||||||
|
stack by themselves once each would be narrower than 18rem */
|
||||||
|
.page-figures {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin: calc(-1 * var(--hero-overlap)) 0 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-figures .page-figure {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Same shape for both photos, whatever the uploads' proportions */
|
||||||
|
.page-figures img {
|
||||||
|
aspect-ratio: 3 / 2;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
/* Prose
|
/* Prose
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
@@ -896,31 +888,6 @@ main {
|
|||||||
color: rgba(255, 255, 255, 0.6);
|
color: rgba(255, 255, 255, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive page
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
@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
|
/* Preferences
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user