blob: bde9e7a033f83bdc8c420b5118ba8ef352cade5f [file]
---
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import '@fontsource-variable/geist';
import '@fontsource-variable/geist-mono';
import '../styles/site.css';
import { Image } from 'astro:assets';
import logo from '../../../assets/logo.png';
import incubator from '../assets/incubator.png';
import Preview from '../components/Preview.astro';
import { links } from '../copy/links';
import type { Copy, Locale } from '../copy/types';
interface Props {
copy: Copy;
page: 'home' | 'downloads';
title: string;
description: string;
}
const { copy, page, title, description } = Astro.props;
const locales: Locale[] = ['en', 'zh-CN'];
const path = page === 'home' ? '' : `${page}/`;
const href = (locale: Locale) => `/${locale}/${path}`;
const canonical = new URL(href(copy.locale), Astro.site);
const home = `/${copy.locale}/`;
const downloads = `/${copy.locale}/downloads/`;
// The site's own links: a row in the nav on wide screens, a menu on narrow ones.
const primary = [
[links.docs, copy.nav.docs],
[downloads, copy.nav.downloads],
[links.discussions, copy.nav.community],
[links.security, copy.nav.security],
];
const current = (href: string) => (href === downloads && page === 'downloads' ? 'page' : undefined);
// The ASF links every project site must carry, shown in the nav menu and the footer.
const asf = [
[links.asf, copy.footer.foundation],
[links.incubator, copy.footer.incubator],
[links.license, copy.footer.license],
[links.events, copy.footer.events],
[links.privacy, copy.footer.privacy],
[links.asfSecurity, copy.footer.security],
[links.sponsorship, copy.footer.sponsorship],
[links.thanks, copy.footer.thanks],
[links.conduct, copy.footer.conduct],
];
---
<html lang={copy.locale}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
<Preview copy={copy} url={canonical} title={title} description={description} />
{locales.map((locale) => <link rel="alternate" hreflang={locale} href={new URL(href(locale), Astro.site)} />)}
<link rel="alternate" hreflang="x-default" href={new URL(href('en'), Astro.site)} />
<link rel="icon" type="image/png" href={logo.src} />
<script is:inline>
// Resolve the theme before first paint: a remembered choice wins, otherwise the OS setting.
(() => {
let theme = null;
try {
theme = localStorage.getItem('theme');
} catch {}
if (theme !== 'light' && theme !== 'dark') {
theme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.dataset.theme = theme;
})();
</script>
</head>
<body>
<div class="wrap">
<nav class="nav" aria-label="Site">
<a class="brand" href={home}>
<Image src={logo} width={52} height={52} alt="" />
{copy.siteName.replace(' (Incubating)', '')}
<small>Incubating</small>
</a>
<div class="links">
{primary.map(([href, label]) => <a href={href} aria-current={current(href)}>{label}</a>)}
<details class="asf">
<summary>{copy.nav.asf}</summary>
<div class="menu">{asf.map(([href, label]) => <a href={href}>{label}</a>)}</div>
</details>
</div>
<div class="sp"></div>
<div class="lang">
{locales.map((locale) =>
locale === copy.locale ? (
<span class="on" aria-current="true">{locale === 'en' ? 'EN' : '中文'}</span>
) : (
<a href={href(locale)} hreflang={locale}>{locale === 'en' ? 'EN' : '中文'}</a>
),
)}
</div>
<button class="theme" type="button" hidden aria-label={copy.theme.toDark} data-to-dark={copy.theme.toDark} data-to-light={copy.theme.toLight}>
<svg class="sun" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="4" /><path d="M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4m11.4-11.4 1.4-1.4" /></svg>
<svg class="moon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" /></svg>
</button>
<a class="btn primary sm" href={downloads}>{copy.nav.getMaka}</a>
<details class="more">
<summary aria-label={copy.nav.menu}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16" /></svg>
</summary>
<div class="menu">
{primary.map(([href, label]) => <a href={href} aria-current={current(href)}>{label}</a>)}
<a href={downloads}>{copy.nav.getMaka}</a>
</div>
</details>
</nav>
<slot />
<footer class="foot">
<div class="links">{asf.map(([href, label]) => <a href={href}>{label}</a>)}</div>
<a class="inc" href={links.incubator}><Image src={incubator} height={36} densities={[1, 2]} alt="Apache Incubator" /></a>
<p class="disc">{copy.footer.disclaimer}</p>
<p class="tm">{copy.footer.trademark}</p>
</footer>
</div>
<script is:inline>
// The nav menus close on a click anywhere outside them.
(() => {
const menus = document.querySelectorAll('.nav details');
document.addEventListener('click', (event) => {
for (const menu of menus) if (!menu.contains(event.target)) menu.removeAttribute('open');
});
})();
// The toggle flips between light and dark and remembers the choice in this browser.
// Without JS it does nothing, so it stays hidden until this runs.
(() => {
const root = document.documentElement;
const button = document.querySelector('.theme');
button.hidden = false;
const label = () => {
button.setAttribute('aria-label', root.dataset.theme === 'dark' ? button.dataset.toLight : button.dataset.toDark);
};
label();
button.addEventListener('click', () => {
root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark';
try {
localStorage.setItem('theme', root.dataset.theme);
} catch {}
label();
});
})();
</script>
</body>
</html>