| --- | |
| /* | |
| * 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> |