Added content
This commit is contained in:
parent
8fc4b6a5c4
commit
133fa015e5
10
README.md
10
README.md
|
@ -8,27 +8,29 @@
|
||||||
- `when-dark-theme`: only display element if the current theme is dark (manually or automatically)
|
- `when-dark-theme`: only display element if the current theme is dark (manually or automatically)
|
||||||
- `when-light-theme`: only display element if the current theme is light (manually or automatically)
|
- `when-light-theme`: only display element if the current theme is light (manually or automatically)
|
||||||
|
|
||||||
|
- `when-no-print`: only display when not printing
|
||||||
|
|
||||||
- `hide-scrollbar`: hide the element scrollbar
|
- `hide-scrollbar`: hide the element scrollbar
|
||||||
- `texture-dots`: add a background paper like texture to the element
|
- `texture-dots`: add a background paper like texture to the element
|
||||||
|
|
||||||
- `font-serif`: by default, everything use sans-serif. Use this class to make the font serif.
|
- `font-serif`: by default, everything use sans-serif. Use this class to make the font serif.
|
||||||
|
- `high-contrast-text`: add a shadow around the text to increase perceived contrast.
|
||||||
|
- `prose`: apply typography rules. Useful for main text content
|
||||||
|
|
||||||
## CSS Component classes
|
## CSS Component classes
|
||||||
|
|
||||||
- `pressable-icon`: used to make a SVG/Text look pressable
|
- `pressable-icon`: used to make a SVG/Text look pressable
|
||||||
- `keycap`: used to make an element look like a pressable keycap
|
- `pressable`: used to make a container look pressable
|
||||||
|
|
||||||
## CSS Global Variables
|
## CSS Global Variables
|
||||||
|
|
||||||
- `--color-base-X`: the current theme colors. X can be between 0 and 1000, available in increments of 50.
|
- `--color-base-X`: the current theme colors. X can be between 0 and 1000, available in increments of 50.
|
||||||
- `--font-serif`: by default, everything use sans-serif. Use this variable to make the font serif.
|
- `--font-serif`: by default, everything use sans-serif. Use this variable to make the font serif.
|
||||||
|
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
For all the following exemples, the spaces within the double curly braces are important.
|
For all the following exemples, the spaces within the double curly braces are important.
|
||||||
|
|
||||||
|
|
||||||
### Variables
|
### Variables
|
||||||
|
|
||||||
Variables allow to embed strings or numbers within a translation.
|
Variables allow to embed strings or numbers within a translation.
|
||||||
|
@ -116,4 +118,4 @@ If then you call:
|
||||||
It will produce
|
It will produce
|
||||||
|
|
||||||
`Return to Home`
|
`Return to Home`
|
||||||
`Return back`
|
`Return back`
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
After Width: | Height: | Size: 199 KiB |
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"background_color": "#27231e",
|
||||||
|
"theme_color": "#27231e",
|
||||||
|
"categories": ["books", "education", "entertainment", "news", "games"],
|
||||||
|
"description": "Accord's Library aims at gathering and archiving all of Yoko Taro’s work. Yoko Taro is a Japanese video game director and scenario writer.",
|
||||||
|
"dir": "auto",
|
||||||
|
"display": "standalone",
|
||||||
|
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/img/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/img/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"name": "Accord's Library",
|
||||||
|
"short_name": "Accord's Lib",
|
||||||
|
"start_url": ".",
|
||||||
|
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "Library",
|
||||||
|
"url": "/library",
|
||||||
|
"description": "Browse all physical and digital media"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Contents",
|
||||||
|
"url": "/contents",
|
||||||
|
"description": "Explore all content and filter by type or category"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Wiki",
|
||||||
|
"url": "/wiki",
|
||||||
|
"description": "An encyclopedia for everything related to DrakeNieR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chronicles",
|
||||||
|
"url": "/chronicles",
|
||||||
|
"description": "Experience all events and content in chronological order"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "News",
|
||||||
|
"url": "/news",
|
||||||
|
"description": "All the latest info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gallery",
|
||||||
|
"url": "/gallery",
|
||||||
|
"description": "Thousands of offcial artworks"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
import Html from "./components/Html.astro";
|
||||||
|
import Topbar from "./components/Topbar.astro";
|
||||||
|
import Footer from "./components/Footer.astro";
|
||||||
|
import type { ComponentProps } from "astro/types";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
parentPages?: ComponentProps<typeof Topbar>["parentPages"];
|
||||||
|
metaTitle?: string;
|
||||||
|
hideFooterLinks?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { metaTitle, hideFooterLinks = false, parentPages } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||||
|
}
|
||||||
|
|
||||||
|
<Html title={metaTitle}>
|
||||||
|
<header>
|
||||||
|
<Topbar parentPages={parentPages} />
|
||||||
|
</header>
|
||||||
|
<main><slot /></main>
|
||||||
|
<Footer withLinks={!hideFooterLinks} />
|
||||||
|
</Html>
|
||||||
|
|
||||||
|
{
|
||||||
|
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||||
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding-top: 1em;
|
||||||
|
padding-bottom: 8em;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,24 +1,37 @@
|
||||||
---
|
---
|
||||||
import Html from "./components/Html.astro";
|
import Html from "./components/Html.astro";
|
||||||
|
import AppLayoutBackgroundImg from "./components/AppLayoutBackgroundImg.astro";
|
||||||
import Topbar from "./components/Topbar.astro";
|
import Topbar from "./components/Topbar.astro";
|
||||||
import Footer from "./components/Footer.astro";
|
import Footer from "./components/Footer.astro";
|
||||||
|
import AppLayoutTitle from "./components/AppLayoutTitle.astro";
|
||||||
|
import type { ComponentProps } from "astro/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
breadcrumb?: { name: string; slug: string }[];
|
parentPages?: ComponentProps<typeof Topbar>["parentPages"];
|
||||||
title?: string | undefined;
|
pretitle?: string | undefined;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string | undefined;
|
||||||
description?: string | undefined;
|
description?: string | undefined;
|
||||||
illustration?: string;
|
illustration?: string;
|
||||||
illustrationSize?: string;
|
illustrationSize?: string;
|
||||||
illustrationPosition?: string;
|
illustrationPosition?: string;
|
||||||
|
backgroundIllustration?: string | undefined;
|
||||||
|
hideFooterLinks?: boolean;
|
||||||
|
hideHomeButton?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = "Accord’s Library",
|
title,
|
||||||
|
subtitle,
|
||||||
|
pretitle,
|
||||||
description,
|
description,
|
||||||
illustration,
|
illustration,
|
||||||
breadcrumb = [],
|
backgroundIllustration,
|
||||||
|
parentPages,
|
||||||
illustrationSize = "contain",
|
illustrationSize = "contain",
|
||||||
illustrationPosition = "center",
|
illustrationPosition = "center",
|
||||||
|
hideFooterLinks = false,
|
||||||
|
hideHomeButton = false,
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -28,24 +41,38 @@ const {
|
||||||
|
|
||||||
<Html title={title}>
|
<Html title={title}>
|
||||||
<header>
|
<header>
|
||||||
<Topbar breadcrumb={breadcrumb} />
|
{
|
||||||
<div id="header-content">
|
backgroundIllustration && (
|
||||||
<div id="header-left">
|
<AppLayoutBackgroundImg src={backgroundIllustration} />
|
||||||
<slot name="header-title">
|
)
|
||||||
<h1>{title}</h1>
|
}
|
||||||
</slot>
|
|
||||||
|
|
||||||
<div id="description">
|
<Topbar parentPages={parentPages} hideHomeButton={hideHomeButton} />
|
||||||
<slot name="header-description">
|
{
|
||||||
<p>{description}</p>
|
(
|
||||||
</slot>
|
<div id="header-content">
|
||||||
|
<div id="header-left">
|
||||||
|
<slot name="header-title">
|
||||||
|
<AppLayoutTitle
|
||||||
|
pretitle={pretitle}
|
||||||
|
title={title}
|
||||||
|
subtitle={subtitle}
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
|
||||||
|
<div class="prose">
|
||||||
|
<slot name="header-description">
|
||||||
|
<p>{description}</p>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{illustration && <div id="image-container" />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
{illustration && <div id="image-container" />}
|
}
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<main><slot /></main>
|
<main><slot /></main>
|
||||||
<Footer withLinks={breadcrumb.length > 0} />
|
<Footer withLinks={!hideFooterLinks} />
|
||||||
</Html>
|
</Html>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -74,20 +101,6 @@ const {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2em;
|
gap: 2em;
|
||||||
place-items: flex-start;
|
place-items: flex-start;
|
||||||
|
|
||||||
& > h1 {
|
|
||||||
font-family: var(--font-serif);
|
|
||||||
font-size: 3em;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > #description {
|
|
||||||
max-width: 35em;
|
|
||||||
|
|
||||||
& > p {
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& > #image-container {
|
& > #image-container {
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
---
|
||||||
|
import { getRandomId } from "src/utils/random";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
src: string;
|
||||||
|
alt?: string;
|
||||||
|
id?: string;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { src, alt } = Astro.props;
|
||||||
|
const uniqueId = getRandomId();
|
||||||
|
|
||||||
|
const styleNoScript = `
|
||||||
|
<style>
|
||||||
|
#${uniqueId} {
|
||||||
|
opacity: 1;
|
||||||
|
transition: unset;
|
||||||
|
}
|
||||||
|
</style>`;
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||||
|
}
|
||||||
|
|
||||||
|
<img id={uniqueId} src={src} alt={alt} class="when-no-print" />
|
||||||
|
|
||||||
|
<noscript set:html={styleNoScript} />
|
||||||
|
|
||||||
|
{
|
||||||
|
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||||
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
opacity: 0;
|
||||||
|
transition: 3s opacity;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: -1;
|
||||||
|
height: 100vh;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: 50% 0;
|
||||||
|
width: 100%;
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(0, 0, 0, 0.3) 0%,
|
||||||
|
rgba(0, 0, 0, 0) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script define:vars={{ uniqueId }}>
|
||||||
|
const element = document.getElementById(uniqueId);
|
||||||
|
|
||||||
|
element.addEventListener("load", () => {
|
||||||
|
element.style.opacity = 1;
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
pretitle?: string | undefined;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, subtitle, pretitle } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<h1 class="high-contrast-text">
|
||||||
|
{pretitle && <span id="pretitle">{pretitle} </span>}
|
||||||
|
<span id="title">{title} </span>
|
||||||
|
{subtitle && <span id="subtitle">{subtitle}</span>}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
line-height: 0.8;
|
||||||
|
display: grid;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
font-size: clamp(1em, 0.7em + 1.5vw, 2em);
|
||||||
|
|
||||||
|
& > #pretitle {
|
||||||
|
font-family: var(--font-sans-serifs);
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > #title {
|
||||||
|
font-family: var(--font-serif);
|
||||||
|
font-size: 200%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > #subtitle {
|
||||||
|
font-family: var(--font-serif);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,6 +3,7 @@ import Button from "components/Button.astro";
|
||||||
import Tooltip from "components/Tooltip.astro";
|
import Tooltip from "components/Tooltip.astro";
|
||||||
import { cache } from "src/utils/cachedPayload";
|
import { cache } from "src/utils/cachedPayload";
|
||||||
import { getI18n } from "translations/translations";
|
import { getI18n } from "translations/translations";
|
||||||
|
import { formatCurrency } from "src/utils/currencies";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
withTitle?: boolean | undefined;
|
withTitle?: boolean | undefined;
|
||||||
|
@ -28,7 +29,7 @@ const { currentCurrency } = Astro.locals;
|
||||||
href={`?action-currency=${id}`}
|
href={`?action-currency=${id}`}
|
||||||
data-astro-prefetch="tap"
|
data-astro-prefetch="tap"
|
||||||
>
|
>
|
||||||
{id}
|
{`${id} (${formatCurrency(id)})`}
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
||||||
<footer>
|
<footer>
|
||||||
{
|
{
|
||||||
withLinks && (
|
withLinks && (
|
||||||
<div id="nav">
|
<div id="nav" class="when-no-print">
|
||||||
<p class="font-serif">{t("global.siteName")}</p>
|
<p class="font-serif">{t("global.siteName")}</p>
|
||||||
<div>
|
<div>
|
||||||
<a href="/">
|
<a href="/">
|
||||||
|
@ -41,11 +41,11 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
||||||
<p>{t("footer.links.home.title")}</p>
|
<p>{t("footer.links.home.title")}</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="/archives">
|
<a href="/archives">
|
||||||
<Icon name="material-symbols:browse-outline" />
|
<Icon name="material-symbols:browse" />
|
||||||
<p>{"Contents"}</p>
|
<p>{"Contents"}</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="/chronicles">
|
<a href="/chronicles">
|
||||||
<Icon name="material-symbols:book-2-outline" />
|
<Icon name="material-symbols:book-2" />
|
||||||
<p>{"Chronicles"}</p>
|
<p>{"Chronicles"}</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="/changelog">
|
<a href="/changelog">
|
||||||
|
@ -53,19 +53,19 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
||||||
<p>{"Changelog"}</p>
|
<p>{"Changelog"}</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="/timeline">
|
<a href="/timeline">
|
||||||
<Icon name="material-symbols:calendar-month-outline" />
|
<Icon name="material-symbols:calendar-month" />
|
||||||
<p>{t("footer.links.timeline.title")}</p>
|
<p>{t("footer.links.timeline.title")}</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://gallery.accords-library.com/posts">
|
<a href="https://gallery.accords-library.com/posts">
|
||||||
<Icon name="material-symbols:perm-media-outline" />
|
<Icon name="material-symbols:perm-media" />
|
||||||
<p>{t("footer.links.gallery.title")}</p>
|
<p>{t("footer.links.gallery.title")}</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="/videos">
|
<a href="/videos">
|
||||||
<Icon name="material-symbols:movie-outline" />
|
<Icon name="material-symbols:movie" />
|
||||||
<p>{t("footer.links.videos.title")}</p>
|
<p>{t("footer.links.videos.title")}</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="/archives">
|
<a href="/archives">
|
||||||
<Icon name="material-symbols:folder-zip-outline" />
|
<Icon name="material-symbols:folder-zip" />
|
||||||
<p>{t("footer.links.webArchives.title")}</p>
|
<p>{t("footer.links.webArchives.title")}</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,7 +90,7 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
||||||
|
|
||||||
{
|
{
|
||||||
withLinks && (
|
withLinks && (
|
||||||
<div id="socials">
|
<div id="socials" class="when-no-print">
|
||||||
<a
|
<a
|
||||||
href="/discord"
|
href="/discord"
|
||||||
class="pressable-icon"
|
class="pressable-icon"
|
||||||
|
@ -236,14 +236,16 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
||||||
gap: 3em;
|
gap: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.with-links {
|
@media screen {
|
||||||
border-left: 0.1em solid var(--color-base-1000);
|
&.with-links {
|
||||||
grid-template-areas: "license" "socials";
|
border-left: 0.1em solid var(--color-base-1000);
|
||||||
|
grid-template-areas: "license" "socials";
|
||||||
|
|
||||||
@media (max-width: 35rem) {
|
@media (max-width: 35rem) {
|
||||||
grid-template-areas: "socials" "license";
|
grid-template-areas: "socials" "license";
|
||||||
border-left: unset;
|
border-left: unset;
|
||||||
padding-left: unset;
|
padding-left: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,10 @@ import "@fontsource-variable/vollkorn";
|
||||||
import "@fontsource-variable/murecho";
|
import "@fontsource-variable/murecho";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title } = Astro.props;
|
const { title = "Accord’s Library" } = Astro.props;
|
||||||
|
|
||||||
const userAgent = Astro.request.headers.get("user-agent") ?? "";
|
const userAgent = Astro.request.headers.get("user-agent") ?? "";
|
||||||
const parser = new UAParser(userAgent);
|
const parser = new UAParser(userAgent);
|
||||||
|
@ -25,6 +25,7 @@ const { currentTheme } = Astro.locals;
|
||||||
"manual-theme": currentTheme !== "auto",
|
"manual-theme": currentTheme !== "auto",
|
||||||
"light-theme": currentTheme === "light",
|
"light-theme": currentTheme === "light",
|
||||||
"dark-theme": currentTheme === "dark",
|
"dark-theme": currentTheme === "dark",
|
||||||
|
"texture-dots": !isIOS,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<head>
|
<head>
|
||||||
|
@ -33,6 +34,17 @@ const { currentTheme } = Astro.locals;
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/css/tippy.css" />
|
<link rel="stylesheet" href="/css/tippy.css" />
|
||||||
|
<meta
|
||||||
|
name="theme-color"
|
||||||
|
media="(prefers-color-scheme: light)"
|
||||||
|
content="#fdebd4"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="theme-color"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
content="#27231e"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
|
||||||
<noscript>
|
<noscript>
|
||||||
<style>
|
<style>
|
||||||
|
@ -47,7 +59,7 @@ const { currentTheme } = Astro.locals;
|
||||||
<ViewTransitions />
|
<ViewTransitions />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class:list={{ "texture-dots": !isIOS }}>
|
<body>
|
||||||
<slot />
|
<slot />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -57,154 +69,63 @@ const { currentTheme } = Astro.locals;
|
||||||
}
|
}
|
||||||
|
|
||||||
<style is:global>
|
<style is:global>
|
||||||
|
@media print {
|
||||||
|
.when-no-print {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
&.light-theme {
|
--color-base-0: #ffffff;
|
||||||
--color-base-0: #ffffff;
|
--color-base-50: #fffaf3;
|
||||||
--color-base-50: #fffaf3;
|
--color-base-100: #fff4e6;
|
||||||
--color-base-100: #fff4e6;
|
--color-base-125: #fef0dd;
|
||||||
--color-base-125: #fef0dd;
|
--color-base-150: #fdebd4;
|
||||||
--color-base-150: #fdebd4;
|
--color-base-200: #f7ddc2;
|
||||||
--color-base-200: #f7ddc2;
|
--color-base-250: #efcfb0;
|
||||||
--color-base-250: #efcfb0;
|
--color-base-300: #e5be9e;
|
||||||
--color-base-300: #e5be9e;
|
--color-base-350: #ddb08e;
|
||||||
--color-base-350: #ddb08e;
|
--color-base-400: #d3a07c;
|
||||||
--color-base-400: #d3a07c;
|
--color-base-450: #ca926c;
|
||||||
--color-base-450: #ca926c;
|
--color-base-500: #c0835d;
|
||||||
--color-base-500: #c0835d;
|
--color-base-550: #b3754f;
|
||||||
--color-base-550: #b3754f;
|
--color-base-600: #a26a47;
|
||||||
--color-base-600: #a26a47;
|
--color-base-650: #905e3f;
|
||||||
--color-base-650: #905e3f;
|
--color-base-700: #805438;
|
||||||
--color-base-700: #805438;
|
--color-base-750: #6e4a31;
|
||||||
--color-base-750: #6e4a31;
|
--color-base-800: #5e402b;
|
||||||
--color-base-800: #5e402b;
|
--color-base-850: #4d3625;
|
||||||
--color-base-850: #4d3625;
|
--color-base-900: #3c2d1e;
|
||||||
--color-base-900: #3c2d1e;
|
--color-base-950: #2f2419;
|
||||||
--color-base-950: #2f2419;
|
--color-base-1000: #1f1a13;
|
||||||
--color-base-1000: #1f1a13;
|
|
||||||
|
|
||||||
--color-elevation-2: var(--color-base-100);
|
--color-elevation-2: var(--color-base-100);
|
||||||
--color-elevation-1: var(--color-base-125);
|
--color-elevation-1: var(--color-base-125);
|
||||||
--color-elevation-0: var(--color-base-150);
|
--color-elevation-0: var(--color-base-150);
|
||||||
|
|
||||||
--color-shadow: var(--color-base-500);
|
--color-shadow: var(--color-base-500);
|
||||||
--color-shadow-1: var(--color-base-350);
|
--color-shadow-1: var(--color-base-350);
|
||||||
--color-shadow-2: var(--color-base-300);
|
--color-shadow-2: var(--color-base-300);
|
||||||
|
|
||||||
--texture-dots: url(/img/paper-dots.webp);
|
--texture-dots: url(/img/paper-dots.webp);
|
||||||
--texture-dots-blend: multiply;
|
--texture-dots-blend: multiply;
|
||||||
|
|
||||||
& .when-light-theme {
|
& .when-light-theme {
|
||||||
display: initial !important;
|
display: initial !important;
|
||||||
}
|
|
||||||
|
|
||||||
& .when-dark-theme {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
font-weight: 430;
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dark-theme {
|
& .when-dark-theme {
|
||||||
--color-base-1000: #ebeae7;
|
display: none !important;
|
||||||
--color-base-950: #eae5e0;
|
|
||||||
--color-base-900: #e8dfd8;
|
|
||||||
--color-base-850: #e4d1c4;
|
|
||||||
--color-base-800: #e0bfaa;
|
|
||||||
--color-base-750: #dcb095;
|
|
||||||
--color-base-700: #d4a07f;
|
|
||||||
--color-base-650: #cb916c;
|
|
||||||
--color-base-600: #bf835d;
|
|
||||||
--color-base-550: #b07751;
|
|
||||||
--color-base-500: #a06b48;
|
|
||||||
--color-base-450: #8f5f40;
|
|
||||||
--color-base-400: #7d5539;
|
|
||||||
--color-base-350: #6b4a33;
|
|
||||||
--color-base-300: #5c412e;
|
|
||||||
--color-base-250: #4a3728;
|
|
||||||
--color-base-200: #3a2d22;
|
|
||||||
--color-base-175: #312820;
|
|
||||||
--color-base-150: #27231e;
|
|
||||||
--color-base-100: #1c1b16;
|
|
||||||
--color-base-50: #11110d;
|
|
||||||
--color-base-0: #000000;
|
|
||||||
|
|
||||||
--color-elevation-2: var(--color-base-200);
|
|
||||||
--color-elevation-1: var(--color-base-175);
|
|
||||||
--color-elevation-0: var(--color-base-150);
|
|
||||||
|
|
||||||
--color-shadow: var(--color-base-0);
|
|
||||||
--color-shadow-1: var(--color-base-0);
|
|
||||||
--color-shadow-2: var(--color-base-50);
|
|
||||||
|
|
||||||
--texture-dots: url(/img/paper-dots-dark.webp);
|
|
||||||
--texture-dots-blend: overlay;
|
|
||||||
|
|
||||||
& .when-light-theme {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .when-dark-theme {
|
|
||||||
display: initial !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.manual-theme) {
|
font-weight: 430;
|
||||||
/* Get in between colors with https://colorkit.io/ */
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
--color-base-0: #ffffff;
|
|
||||||
--color-base-50: #fffaf3;
|
|
||||||
--color-base-100: #fff4e6;
|
|
||||||
--color-base-125: #fef0dd;
|
|
||||||
--color-base-150: #fdebd4;
|
|
||||||
--color-base-200: #f7ddc2;
|
|
||||||
--color-base-250: #efcfb0;
|
|
||||||
--color-base-300: #e5be9e;
|
|
||||||
--color-base-350: #ddb08e;
|
|
||||||
--color-base-400: #d3a07c;
|
|
||||||
--color-base-450: #ca926c;
|
|
||||||
--color-base-500: #c0835d;
|
|
||||||
--color-base-550: #b3754f;
|
|
||||||
--color-base-600: #a26a47;
|
|
||||||
--color-base-650: #905e3f;
|
|
||||||
--color-base-700: #805438;
|
|
||||||
--color-base-750: #6e4a31;
|
|
||||||
--color-base-800: #5e402b;
|
|
||||||
--color-base-850: #4d3625;
|
|
||||||
--color-base-900: #3c2d1e;
|
|
||||||
--color-base-950: #2f2419;
|
|
||||||
--color-base-1000: #1f1a13;
|
|
||||||
|
|
||||||
--color-elevation-2: var(--color-base-100);
|
strong {
|
||||||
--color-elevation-1: var(--color-base-125);
|
font-weight: 600;
|
||||||
--color-elevation-0: var(--color-base-150);
|
}
|
||||||
|
|
||||||
--color-shadow: var(--color-base-500);
|
@media screen {
|
||||||
--color-shadow-1: var(--color-base-350);
|
&.dark-theme {
|
||||||
--color-shadow-2: var(--color-base-300);
|
|
||||||
|
|
||||||
--texture-dots: url(/img/paper-dots.webp);
|
|
||||||
--texture-dots-blend: multiply;
|
|
||||||
|
|
||||||
& .when-light-theme {
|
|
||||||
display: initial !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .when-dark-theme {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
font-weight: 430;
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
--color-base-1000: #ebeae7;
|
--color-base-1000: #ebeae7;
|
||||||
--color-base-950: #eae5e0;
|
--color-base-950: #eae5e0;
|
||||||
--color-base-900: #e8dfd8;
|
--color-base-900: #e8dfd8;
|
||||||
|
@ -247,25 +168,134 @@ const { currentTheme } = Astro.locals;
|
||||||
display: initial !important;
|
display: initial !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.manual-theme) {
|
||||||
|
/* Get in between colors with https://colorkit.io/ */
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
--color-base-0: #ffffff;
|
||||||
|
--color-base-50: #fffaf3;
|
||||||
|
--color-base-100: #fff4e6;
|
||||||
|
--color-base-125: #fef0dd;
|
||||||
|
--color-base-150: #fdebd4;
|
||||||
|
--color-base-200: #f7ddc2;
|
||||||
|
--color-base-250: #efcfb0;
|
||||||
|
--color-base-300: #e5be9e;
|
||||||
|
--color-base-350: #ddb08e;
|
||||||
|
--color-base-400: #d3a07c;
|
||||||
|
--color-base-450: #ca926c;
|
||||||
|
--color-base-500: #c0835d;
|
||||||
|
--color-base-550: #b3754f;
|
||||||
|
--color-base-600: #a26a47;
|
||||||
|
--color-base-650: #905e3f;
|
||||||
|
--color-base-700: #805438;
|
||||||
|
--color-base-750: #6e4a31;
|
||||||
|
--color-base-800: #5e402b;
|
||||||
|
--color-base-850: #4d3625;
|
||||||
|
--color-base-900: #3c2d1e;
|
||||||
|
--color-base-950: #2f2419;
|
||||||
|
--color-base-1000: #1f1a13;
|
||||||
|
|
||||||
|
--color-elevation-2: var(--color-base-100);
|
||||||
|
--color-elevation-1: var(--color-base-125);
|
||||||
|
--color-elevation-0: var(--color-base-150);
|
||||||
|
|
||||||
|
--color-shadow: var(--color-base-500);
|
||||||
|
--color-shadow-1: var(--color-base-350);
|
||||||
|
--color-shadow-2: var(--color-base-300);
|
||||||
|
|
||||||
|
--texture-dots: url(/img/paper-dots.webp);
|
||||||
|
--texture-dots-blend: multiply;
|
||||||
|
|
||||||
|
& .when-light-theme {
|
||||||
|
display: initial !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .when-dark-theme {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
font-weight: 430;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
--color-base-1000: #ebeae7;
|
||||||
|
--color-base-950: #eae5e0;
|
||||||
|
--color-base-900: #e8dfd8;
|
||||||
|
--color-base-850: #e4d1c4;
|
||||||
|
--color-base-800: #e0bfaa;
|
||||||
|
--color-base-750: #dcb095;
|
||||||
|
--color-base-700: #d4a07f;
|
||||||
|
--color-base-650: #cb916c;
|
||||||
|
--color-base-600: #bf835d;
|
||||||
|
--color-base-550: #b07751;
|
||||||
|
--color-base-500: #a06b48;
|
||||||
|
--color-base-450: #8f5f40;
|
||||||
|
--color-base-400: #7d5539;
|
||||||
|
--color-base-350: #6b4a33;
|
||||||
|
--color-base-300: #5c412e;
|
||||||
|
--color-base-250: #4a3728;
|
||||||
|
--color-base-200: #3a2d22;
|
||||||
|
--color-base-175: #312820;
|
||||||
|
--color-base-150: #27231e;
|
||||||
|
--color-base-100: #1c1b16;
|
||||||
|
--color-base-50: #11110d;
|
||||||
|
--color-base-0: #000000;
|
||||||
|
|
||||||
|
--color-elevation-2: var(--color-base-200);
|
||||||
|
--color-elevation-1: var(--color-base-175);
|
||||||
|
--color-elevation-0: var(--color-base-150);
|
||||||
|
|
||||||
|
--color-shadow: var(--color-base-0);
|
||||||
|
--color-shadow-1: var(--color-base-0);
|
||||||
|
--color-shadow-2: var(--color-base-50);
|
||||||
|
|
||||||
|
--texture-dots: url(/img/paper-dots-dark.webp);
|
||||||
|
--texture-dots-blend: overlay;
|
||||||
|
|
||||||
|
& .when-light-theme {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .when-dark-theme {
|
||||||
|
display: initial !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* THEMING */
|
/* THEMING */
|
||||||
|
|
||||||
html,
|
*::selection {
|
||||||
body {
|
color: var(--color-elevation-0);
|
||||||
padding: 0;
|
background: var(--color-base-600);
|
||||||
margin: 0;
|
}
|
||||||
|
|
||||||
|
@media screen {
|
||||||
|
.high-contrast-text {
|
||||||
|
text-shadow: 0 0 0.6em var(--color-elevation-0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
color: var(--color-base-1000);
|
color: var(--color-base-1000);
|
||||||
background-color: var(--color-base-150);
|
|
||||||
|
@media screen {
|
||||||
|
background-color: var(--color-base-150);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
padding: clamp(12px, 3vmin, 24px) clamp(24px, 4vw, 64px);
|
margin: clamp(12px, 3vmin, 24px) clamp(24px, 4vw, 64px);
|
||||||
min-height: 100vb;
|
min-height: 100vb;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
|
@ -275,7 +305,8 @@ const { currentTheme } = Astro.locals;
|
||||||
h5,
|
h5,
|
||||||
h6,
|
h6,
|
||||||
p,
|
p,
|
||||||
button {
|
button,
|
||||||
|
html {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -293,6 +324,10 @@ const { currentTheme } = Astro.locals;
|
||||||
--font-serif: "Vollkorn Variable", serif;
|
--font-serif: "Vollkorn Variable", serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
body {
|
body {
|
||||||
font-family: "Murecho Variable", sans-serif;
|
font-family: "Murecho Variable", sans-serif;
|
||||||
|
@ -311,12 +346,14 @@ const { currentTheme } = Astro.locals;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.texture-dots {
|
@media screen {
|
||||||
background-size: 10cm;
|
.texture-dots {
|
||||||
background-attachment: local;
|
background-size: 10cm;
|
||||||
background-image: var(--texture-dots);
|
background-attachment: local;
|
||||||
background-blend-mode: var(--texture-dots-blend);
|
background-image: var(--texture-dots);
|
||||||
background-repeat: repeat;
|
background-blend-mode: var(--texture-dots-blend);
|
||||||
|
background-repeat: repeat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pressable-icon {
|
.pressable-icon {
|
||||||
|
@ -332,21 +369,21 @@ const { currentTheme } = Astro.locals;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.keycap {
|
.pressable {
|
||||||
--foreground-color: var(--color-base-650);
|
--foreground-color: var(--color-base-650);
|
||||||
color: var(--foreground-color);
|
color: var(--foreground-color);
|
||||||
border: 0.1rem solid var(--foreground-color);
|
border: 0.1rem solid var(--foreground-color);
|
||||||
background-color: var(--color-elevation-0);
|
backdrop-filter: blur(10px);
|
||||||
|
|
||||||
transition-duration: 250ms;
|
transition-duration: 250ms;
|
||||||
transition-property: padding-top, box-shadow, background-color, color,
|
transition-property: padding-top, box-shadow, background-color, color,
|
||||||
border-color;
|
border-color, translate;
|
||||||
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
--foreground-color: var(--color-base-1000);
|
--foreground-color: var(--color-base-1000);
|
||||||
box-shadow: 0 2px 2px var(--color-shadow-2);
|
box-shadow: 0 2px 2px var(--color-shadow-2);
|
||||||
background-color: var(--color-elevation-1);
|
background-color: var(--color-elevation-1);
|
||||||
|
translate: 0 -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
|
@ -354,6 +391,7 @@ const { currentTheme } = Astro.locals;
|
||||||
--foreground-color: var(--color-base-1000);
|
--foreground-color: var(--color-base-1000);
|
||||||
background-color: var(--color-elevation-2);
|
background-color: var(--color-elevation-2);
|
||||||
box-shadow: 0 6px 12px 2px var(--color-shadow-2);
|
box-shadow: 0 6px 12px 2px var(--color-shadow-2);
|
||||||
|
translate: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,4 +406,50 @@ const { currentTheme } = Astro.locals;
|
||||||
.when-no-js {
|
.when-no-js {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prose {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.75;
|
||||||
|
max-width: 35rem;
|
||||||
|
|
||||||
|
> *:first-child {
|
||||||
|
margin-top: unset;
|
||||||
|
padding-top: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
> *:last-child {
|
||||||
|
margin-bottom: unset;
|
||||||
|
padding-bottom: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
> p {
|
||||||
|
margin-top: 1.25em;
|
||||||
|
margin-bottom: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
> h2 {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> h2,
|
||||||
|
> h3,
|
||||||
|
> h4,
|
||||||
|
> h5,
|
||||||
|
> h6 {
|
||||||
|
margin-top: 2em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> h2 + h3,
|
||||||
|
> h3 + h4,
|
||||||
|
> h4 + h5,
|
||||||
|
> h5 + h6 {
|
||||||
|
margin-top: -0.75em;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -12,7 +12,7 @@ interface Props {
|
||||||
const { withTitle, class: className } = Astro.props;
|
const { withTitle, class: className } = Astro.props;
|
||||||
|
|
||||||
const { currentLocale } = Astro.locals;
|
const { currentLocale } = Astro.locals;
|
||||||
const { t } = await getI18n(currentLocale);
|
const { t, formatLocale } = await getI18n(currentLocale);
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -22,13 +22,13 @@ const { t } = await getI18n(currentLocale);
|
||||||
<Tooltip trigger="click" class={className}>
|
<Tooltip trigger="click" class={className}>
|
||||||
<div id="content" slot="tooltip-content">
|
<div id="content" slot="tooltip-content">
|
||||||
{
|
{
|
||||||
cache.locales.map(id => (
|
cache.locales.map(({ id }) => (
|
||||||
<a
|
<a
|
||||||
class:list={{ current: currentLocale === id }}
|
class:list={{ current: currentLocale === id }}
|
||||||
href={`?action-lang=${id}`}
|
href={`?action-lang=${id}`}
|
||||||
data-astro-prefetch="tap"
|
data-astro-prefetch="tap"
|
||||||
>
|
>
|
||||||
{id}
|
{formatLocale(id)}
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,45 +5,49 @@ import ThemeSelector from "components/AppLayout/components/ThemeSelector.astro";
|
||||||
import LanguageSelector from "components/AppLayout/components/LanguageSelector.astro";
|
import LanguageSelector from "components/AppLayout/components/LanguageSelector.astro";
|
||||||
import CurrencySelector from "components/AppLayout/components/CurrencySelector.astro";
|
import CurrencySelector from "components/AppLayout/components/CurrencySelector.astro";
|
||||||
import { getI18n } from "translations/translations";
|
import { getI18n } from "translations/translations";
|
||||||
|
import Tooltip from "components/Tooltip.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
breadcrumb: { name: string; slug: string }[];
|
parentPages?: { name: string; slug: string; type: string }[] | undefined;
|
||||||
|
hideHomeButton?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { breadcrumb } = Astro.props;
|
const { parentPages = [], hideHomeButton = false } = Astro.props;
|
||||||
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|
||||||
|
|
||||||
|
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
|
||||||
<nav id="topbar">
|
<nav id="topbar" class="when-no-print">
|
||||||
{
|
{
|
||||||
breadcrumb.length > 0 && (
|
(!hideHomeButton || parentPages.length > 0) && (
|
||||||
<div id="breadcrumb" class="hide-scrollbar">
|
<div id="breadcrumb" class="hide-scrollbar high-contrast-text">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<>
|
<Icon name="material-symbols:home" width={16} height={16} />
|
||||||
<Icon name="accords" width={16} height={16} />
|
<p>{t("home.title")}</p>
|
||||||
<p>{t("home.title")}</p>
|
|
||||||
</>
|
|
||||||
</a>
|
</a>
|
||||||
{breadcrumb.map(({ name, slug }) => (
|
|
||||||
<>
|
{parentPages.length > 0 && (
|
||||||
<Icon
|
<Tooltip trigger="click">
|
||||||
name="material-symbols:arrow-forward-ios"
|
<div slot="tooltip-content">
|
||||||
width={12}
|
<p>This content is part of these pages:</p>
|
||||||
height={12}
|
<p>NieR / Concert</p>
|
||||||
/>
|
<p>NieR:Automata / Concert</p>
|
||||||
<a href={slug}>
|
<p>NieR:Theatrical Orchestra Concert 12020 Bluray</p>
|
||||||
<p>{name}</p>
|
</div>
|
||||||
</a>
|
<div>
|
||||||
</>
|
<Icon name="material-symbols:keyboard-return" />
|
||||||
))}
|
<p>4 parent pages</p>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
<div id="toolbar">
|
<div id="toolbar">
|
||||||
<a href={getLocalizedUrl("/search")}>
|
<a href={getLocalizedUrl("/search")}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -89,19 +93,25 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
|
gap: 8px;
|
||||||
|
margin-left: -0.8em;
|
||||||
|
|
||||||
& > svg {
|
& > svg {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > a {
|
& > a,
|
||||||
|
& > :global(tippy-tooltip > div) {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
gap: 0.4em;
|
gap: 0.4em;
|
||||||
padding: 0.4em 0.6em;
|
padding: 0.7em 0.8em;
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
|
||||||
transition: 150ms background-color;
|
transition: 150ms background-color;
|
||||||
|
|
||||||
|
@ -112,10 +122,6 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
&:active {
|
&:active {
|
||||||
background-color: var(--color-base-300);
|
background-color: var(--color-base-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ const icons =
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id={id}
|
id={id}
|
||||||
class:list={[{ "with-title": !!title }, className]}
|
class:list={["pressable", "high-contrast-text", { "with-title": !!title }, className]}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
title={ariaLabel}
|
title={ariaLabel}
|
||||||
>
|
>
|
||||||
|
@ -35,15 +35,13 @@ const icons =
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
button {
|
button {
|
||||||
--foreground-color: var(--color-base-650);
|
|
||||||
color: var(--foreground-color);
|
|
||||||
border: 0.1em solid var(--foreground-color);
|
|
||||||
background-color: var(--color-elevation-0);
|
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
height: 2.5em;
|
height: 2.5em;
|
||||||
|
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
place-content: center;
|
place-content: center;
|
||||||
|
@ -56,7 +54,6 @@ const icons =
|
||||||
transition-duration: 250ms;
|
transition-duration: 250ms;
|
||||||
transition-property: padding-top, box-shadow, background-color, color,
|
transition-property: padding-top, box-shadow, background-color, color,
|
||||||
border-color;
|
border-color;
|
||||||
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
|
|
||||||
&.with-title > svg {
|
&.with-title > svg {
|
||||||
width: 1.2em;
|
width: 1.2em;
|
||||||
|
@ -69,16 +66,12 @@ const icons =
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
--foreground-color: var(--color-base-1000);
|
|
||||||
box-shadow: inset 0 0.1em 0.1em 0 var(--color-shadow-2);
|
box-shadow: inset 0 0.1em 0.1em 0 var(--color-shadow-2);
|
||||||
|
translate: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transition-duration: 75ms;
|
|
||||||
--foreground-color: var(--color-base-1000);
|
|
||||||
background-color: var(--color-elevation-2);
|
|
||||||
box-shadow: inset 0 0.1em 0.1em 0.1em var(--color-shadow-2);
|
box-shadow: inset 0 0.1em 0.1em 0.1em var(--color-shadow-2);
|
||||||
|
|
||||||
padding-top: 0.2em;
|
padding-top: 0.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
import { getRandomId } from "src/utils/random";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
src: string;
|
||||||
|
alt?: string;
|
||||||
|
id?: string;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { src, alt } = Astro.props;
|
||||||
|
|
||||||
|
const uniqueId = getRandomId();
|
||||||
|
---
|
||||||
|
|
||||||
|
<img id={uniqueId} src={src} alt={alt} />
|
||||||
|
|
||||||
|
<script define:vars={{ uniqueId }}>
|
||||||
|
const element = document.getElementById(uniqueId);
|
||||||
|
|
||||||
|
element.addEventListener("load", () => {
|
||||||
|
element.style.opacity = 1;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
opacity: 0;
|
||||||
|
transition: 3s opacity;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,21 +1,11 @@
|
||||||
---
|
|
||||||
interface Props {
|
|
||||||
id?: string;
|
|
||||||
class?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { class:className, id } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
{
|
{
|
||||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
|
||||||
<maso-target class={className} id={id}>
|
<maso-target>
|
||||||
<slot />
|
<slot />
|
||||||
</maso-target>
|
</maso-target>
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
/* ------------------------------------------- JS --------------------------------------------- */
|
/* ------------------------------------------- JS --------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,26 @@
|
||||||
---
|
---
|
||||||
|
import type { RichTextContent } from "src/shared/payload/payload-sdk";
|
||||||
import RTNode from "./components/RTNode.astro";
|
import RTNode from "./components/RTNode.astro";
|
||||||
|
import RTProse from "./components/RTProse.astro";
|
||||||
|
import { type RichTextContext, defaultContext } from "src/utils/richText";
|
||||||
|
import ConditionalWrapper from "components/ConditionalWrapper.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
content: {
|
content: RichTextContent;
|
||||||
root: {
|
context?: RichTextContext;
|
||||||
children: {
|
|
||||||
type: string;
|
|
||||||
version: number;
|
|
||||||
[k: string]: unknown;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { content } = Astro.props;
|
const { content, context = defaultContext } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="rich-text">
|
<ConditionalWrapper condition={context.depth === 1} wrapper={RTProse}>
|
||||||
{content.root.children.map((node) => <RTNode node={node} />)}
|
{
|
||||||
</div>
|
content.root.children.map((node) => (
|
||||||
|
<RTNode node={node} context={context} />
|
||||||
{
|
))
|
||||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
|
||||||
}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.rich-text {
|
|
||||||
& li[checkbox]::marker {
|
|
||||||
content: "☐";
|
|
||||||
}
|
|
||||||
|
|
||||||
& li[checkbox][checked]::marker {
|
|
||||||
content: "☒";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</ConditionalWrapper>
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
|
import RTSection from "./components/RTSection.astro";
|
||||||
|
import RTTranscript from "./components/RTTranscript.astro";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
node: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
format: number;
|
||||||
|
text: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
fields: {
|
||||||
|
id: string;
|
||||||
|
blockName: string;
|
||||||
|
blockType: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
context: RichTextContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { node, context } = Astro.props;
|
||||||
|
|
||||||
|
let NodeElement;
|
||||||
|
switch (node.fields.blockType) {
|
||||||
|
case "sectionBlock":
|
||||||
|
NodeElement = RTSection;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "transcriptBlock":
|
||||||
|
NodeElement = RTTranscript;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
NodeElement ? (
|
||||||
|
<NodeElement node={node} context={context} />
|
||||||
|
) : (
|
||||||
|
<p>{`Unknown block type: ${node.fields.blockType}. Please contact website technical administrator.`}</p>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
import RichText from "components/RichText/RichText.astro";
|
||||||
|
import type { RichTextContent } from "src/shared/payload/payload-sdk";
|
||||||
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
block: {
|
||||||
|
id: string;
|
||||||
|
content: RichTextContent;
|
||||||
|
blockType: string;
|
||||||
|
blockName: string;
|
||||||
|
};
|
||||||
|
context: RichTextContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { block, context } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<RichText content={block.content} context={context} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
import RichText from "components/RichText/RichText.astro";
|
||||||
|
import type { RichTextContent } from "src/shared/payload/payload-sdk";
|
||||||
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
block: {
|
||||||
|
id: string;
|
||||||
|
content: RichTextContent;
|
||||||
|
blockType: string;
|
||||||
|
blockName: string;
|
||||||
|
};
|
||||||
|
context: RichTextContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { block, context } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>{block.blockName}</p>
|
||||||
|
<div>
|
||||||
|
<RichText content={block.content} context={context} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
color: var(--color-base-650);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 35rem) {
|
||||||
|
p {
|
||||||
|
grid-column: 1;
|
||||||
|
margin-bottom: -1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
grid-column: 1;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
import RichText from "components/RichText/RichText.astro";
|
||||||
|
import type { RichTextContent } from "src/shared/payload/payload-sdk";
|
||||||
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
node: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
format: number;
|
||||||
|
text: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
fields: {
|
||||||
|
id: string;
|
||||||
|
blockName: string;
|
||||||
|
blockType: string;
|
||||||
|
lines: RichTextContent;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
context: RichTextContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { node, context } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
context.depth < 2 ? (
|
||||||
|
<h2>{node.fields.blockName}</h2>
|
||||||
|
) : context.depth === 2 ? (
|
||||||
|
<h3>{node.fields.blockName}</h3>
|
||||||
|
) : context.depth === 3 ? (
|
||||||
|
<h4>{node.fields.blockName}</h4>
|
||||||
|
) : context.depth === 4 ? (
|
||||||
|
<h5>{node.fields.blockName}</h5>
|
||||||
|
) : (
|
||||||
|
<h6>{node.fields.blockName}</h6>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<RichText
|
||||||
|
content={node.fields.lines}
|
||||||
|
context={{ ...context, depth: context.depth + 1 }}
|
||||||
|
/>
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
|
import RTLine from "./RTLine.astro";
|
||||||
|
import RTCue from "./RTCue.astro";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
node: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
format: number;
|
||||||
|
text: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
fields: {
|
||||||
|
id: string;
|
||||||
|
blockName: string;
|
||||||
|
blockType: string;
|
||||||
|
lines: { blockType: string }[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
context: RichTextContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { node, context } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
node.fields.lines.map((block) => {
|
||||||
|
switch (block.blockType) {
|
||||||
|
case "lineBlock":
|
||||||
|
return <RTLine block={block} context={context} />;
|
||||||
|
|
||||||
|
case "cueBlock":
|
||||||
|
return <RTCue block={block} context={context} />;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<p>{`Unknown block type: ${block.blockType}. Please contact website technical administrator.`}</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
padding-block: 1em;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: 1.5em 2em;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,19 +0,0 @@
|
||||||
---
|
|
||||||
type BasicNode = {
|
|
||||||
type: string;
|
|
||||||
version: number;
|
|
||||||
[k: string]: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
node: BasicNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { node } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{
|
|
||||||
`Unknown node type: ${node.type}. Please contact website technical administrator.`
|
|
||||||
}
|
|
||||||
</p>
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import RTError from "../RTError.astro";
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
import RTNode from "../RTNode.astro";
|
import RTNode from "../RTNode.astro";
|
||||||
import RTCustomLink from "./components/RTCustomLink.astro";
|
import RTCustomLink from "./components/RTCustomLink.astro";
|
||||||
import RTInternalLink from "./components/RTInternalLink.astro";
|
import RTInternalLink from "./components/RTInternalLink.astro";
|
||||||
|
@ -21,25 +21,26 @@ interface Props {
|
||||||
};
|
};
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
|
context: RichTextContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { node } = Astro.props;
|
const { node, context } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
node.fields.linkType === "custom" ? (
|
node.fields.linkType === "custom" ? (
|
||||||
<RTCustomLink href={node.fields.url} newTab={node.fields.newTab}>
|
<RTCustomLink href={node.fields.url} newTab={node.fields.newTab}>
|
||||||
{node.children.map((node) => (
|
{node.children.map((node) => (
|
||||||
<RTNode node={node} />
|
<RTNode node={node} context={context} />
|
||||||
))}
|
))}
|
||||||
</RTCustomLink>
|
</RTCustomLink>
|
||||||
) : node.fields.linkType === "internal" ? (
|
) : node.fields.linkType === "internal" ? (
|
||||||
<RTInternalLink doc={node.fields.doc}>
|
<RTInternalLink doc={node.fields.doc}>
|
||||||
{node.children.map((node) => (
|
{node.children.map((node) => (
|
||||||
<RTNode node={node} />
|
<RTNode node={node} context={context} />
|
||||||
))}
|
))}
|
||||||
</RTInternalLink>
|
</RTInternalLink>
|
||||||
) : (
|
) : (
|
||||||
<RTError node={node} />
|
<p>{`Unknown link type: ${node.fields.linkType}. Please contact website technical administrator.`}</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
---
|
---
|
||||||
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
import RTBasicListItem from "./components/RTBasicListItem.astro";
|
import RTBasicListItem from "./components/RTBasicListItem.astro";
|
||||||
import RTCheckListItem from "./components/RTCheckListItem.astro";
|
import RTCheckListItem from "./components/RTCheckListItem.astro";
|
||||||
|
|
||||||
|
@ -6,38 +7,42 @@ interface Props {
|
||||||
node: {
|
node: {
|
||||||
type: string;
|
type: string;
|
||||||
version: number;
|
version: number;
|
||||||
format: number;
|
|
||||||
text: string;
|
|
||||||
listType: string;
|
listType: string;
|
||||||
children: {
|
children: {
|
||||||
type: string;
|
type: string;
|
||||||
version: number;
|
version: number;
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
|
children: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
}[];
|
}[];
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
|
context: RichTextContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { node } = Astro.props;
|
const { node, context } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
node.listType === "number" ? (
|
node.listType === "number" ? (
|
||||||
<ol>
|
<ol>
|
||||||
{node.children.map((node) => (
|
{node.children.map((node) => (
|
||||||
<RTBasicListItem node={node} />
|
<RTBasicListItem node={node} context={context} />
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
) : node.listType === "bullet" ? (
|
) : node.listType === "bullet" ? (
|
||||||
<ul>
|
<ul>
|
||||||
{node.children.map((node) => (
|
{node.children.map((node) => (
|
||||||
<RTBasicListItem node={node} />
|
<RTBasicListItem node={node} context={context} />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : node.listType === "check" ? (
|
) : node.listType === "check" ? (
|
||||||
<ul>
|
<ul>
|
||||||
{node.children.map((node) => (
|
{node.children.map((node) => (
|
||||||
<RTCheckListItem node={node} />
|
<RTCheckListItem node={node} context={context} />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
---
|
---
|
||||||
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
import RTNode from "../../RTNode.astro";
|
import RTNode from "../../RTNode.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -10,14 +11,12 @@ interface Props {
|
||||||
}[];
|
}[];
|
||||||
type: string;
|
type: string;
|
||||||
version: number;
|
version: number;
|
||||||
format: number;
|
|
||||||
text: string;
|
|
||||||
listType: string;
|
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
|
context: RichTextContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { node } = Astro.props;
|
const { node, context } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<li>{node.children.map((node) => <RTNode node={node} />)}</li>
|
<li>{node.children.map((node) => <RTNode node={node} context={context} />)}</li>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import RTNode from "../../RTNode.astro";
|
import RTNode from "../../RTNode.astro";
|
||||||
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
node: {
|
node: {
|
||||||
|
@ -11,14 +12,12 @@ interface Props {
|
||||||
}[];
|
}[];
|
||||||
type: string;
|
type: string;
|
||||||
version: number;
|
version: number;
|
||||||
format: number;
|
|
||||||
text: string;
|
|
||||||
listType: string;
|
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
|
context: RichTextContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { node } = Astro.props;
|
const { node, context } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
|
@ -27,14 +26,14 @@ const { node } = Astro.props;
|
||||||
? "material-symbols:check-box"
|
? "material-symbols:check-box"
|
||||||
: "material-symbols:check-box-outline-blank"}
|
: "material-symbols:check-box-outline-blank"}
|
||||||
/>
|
/>
|
||||||
{node.children.map((node) => <RTNode node={node} />)}
|
{node.children.map((node) => <RTNode node={node} context={context} />)}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
li {
|
li {
|
||||||
&::marker {
|
&::marker {
|
||||||
content: ""
|
content: "";
|
||||||
}
|
}
|
||||||
margin-left: -16px;
|
margin-left: -16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
---
|
---
|
||||||
import RTParagraph from "./RTParagraph.astro";
|
import RTParagraph from "./RTParagraph.astro";
|
||||||
import RTList from "./RTList/RTList.astro";
|
import RTList from "./RTList/RTList.astro";
|
||||||
import RTError from "./RTError.astro";
|
|
||||||
import RTText from "./RTText/RTText.astro";
|
import RTText from "./RTText/RTText.astro";
|
||||||
import RTLink from "./RTLink/RTLink.astro";
|
import RTLink from "./RTLink/RTLink.astro";
|
||||||
|
import RTBlock from "./RTBlock/RTBlock.astro";
|
||||||
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
node: {
|
node: {
|
||||||
|
@ -11,9 +12,10 @@ interface Props {
|
||||||
version: number;
|
version: number;
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
|
context: RichTextContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { node } = Astro.props;
|
const { node, context } = Astro.props;
|
||||||
|
|
||||||
let NodeElement;
|
let NodeElement;
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
|
@ -33,10 +35,16 @@ switch (node.type) {
|
||||||
NodeElement = RTLink;
|
NodeElement = RTLink;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
case "block":
|
||||||
NodeElement = RTError;
|
NodeElement = RTBlock;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<NodeElement node={node} />
|
{
|
||||||
|
NodeElement ? (
|
||||||
|
<NodeElement node={node} context={context} />
|
||||||
|
) : (
|
||||||
|
<p>{`Unknown node type: ${node.type}. Please contact website technical administrator.`}</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
---
|
---
|
||||||
|
import type { RichTextContext } from "src/utils/richText";
|
||||||
import RTNode from "./RTNode.astro";
|
import RTNode from "./RTNode.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -12,23 +13,19 @@ interface Props {
|
||||||
}[];
|
}[];
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
|
context: RichTextContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { node } = Astro.props;
|
const { node, context } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
|
||||||
<p>{node.children.map((node) => <RTNode node={node} />)}</p>
|
<p>{node.children.map((node) => <RTNode node={node} context={context} />)}</p>
|
||||||
|
|
||||||
{
|
{
|
||||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
|
||||||
<style>
|
|
||||||
p {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="prose">
|
||||||
|
<slot />
|
||||||
|
</div>
|
|
@ -0,0 +1,94 @@
|
||||||
|
---
|
||||||
|
import MasoActor from "components/Maso/MasoActor.astro";
|
||||||
|
import Tooltip from "components/Tooltip.astro";
|
||||||
|
import Button from "components/Button.astro";
|
||||||
|
import { getI18n } from "translations/translations";
|
||||||
|
import Metadata from "pages/[locale]/api/contents/_components/Metadata.astro";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
currentLang: string;
|
||||||
|
getPartialUrl: (locale: string) => string;
|
||||||
|
availableLanguages: string[];
|
||||||
|
translators?: string[] | undefined;
|
||||||
|
transcribers?: string[] | undefined;
|
||||||
|
proofreaders?: string[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
currentLang,
|
||||||
|
getPartialUrl,
|
||||||
|
availableLanguages,
|
||||||
|
translators = [],
|
||||||
|
transcribers = [],
|
||||||
|
proofreaders = [],
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
const { formatLocale, formatRecorder } = await getI18n(
|
||||||
|
Astro.locals.currentLocale
|
||||||
|
);
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
availableLanguages.length > 1 && (
|
||||||
|
<div id="lang-selector" class="when-js when-no-print">
|
||||||
|
<Tooltip trigger="click">
|
||||||
|
<Button
|
||||||
|
icon="material-symbols:translate"
|
||||||
|
title={currentLang.toUpperCase()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div id="tooltip-content" slot="tooltip-content">
|
||||||
|
{availableLanguages.map((id) => (
|
||||||
|
<MasoActor
|
||||||
|
class:list={{ current: id === currentLang }}
|
||||||
|
href={getPartialUrl(id)}
|
||||||
|
>
|
||||||
|
{formatLocale(id)}
|
||||||
|
</MasoActor>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
<p class="high-contrast-text">This content is available is {availableLanguages.length} languages.</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<Metadata
|
||||||
|
icon="material-symbols:person-outline"
|
||||||
|
title="Translators"
|
||||||
|
values={translators.map((id) => formatRecorder(id))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Metadata
|
||||||
|
icon="material-symbols:person-edit-outline"
|
||||||
|
title="Transcribers"
|
||||||
|
values={transcribers.map((id) => formatRecorder(id))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Metadata
|
||||||
|
icon="material-symbols:person-check-outline"
|
||||||
|
title="Proofreaders"
|
||||||
|
values={proofreaders.map((id) => formatRecorder(id))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||||
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#lang-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1em;
|
||||||
|
|
||||||
|
#tooltip-content {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
& > .current {
|
||||||
|
color: var(--color-base-750);
|
||||||
|
text-decoration: underline 0.08em var(--color-base-650);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
values: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { icon, title, values } = Astro.props;
|
||||||
|
|
||||||
|
if (values.length === 0) return;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div id="container">
|
||||||
|
<div id="title">
|
||||||
|
<Icon name={icon} width={24} height={24} />
|
||||||
|
<p>{title}</p>
|
||||||
|
</div>
|
||||||
|
<div id="values">
|
||||||
|
{values.map((value) => <div class="pill">{value}</div>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: 0.5em 1em;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@media (max-width: 35em) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > #title {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
& > p {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: 600;
|
||||||
|
translate: 0px -0.1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > #values {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
& > .pill {
|
||||||
|
border: 1px solid var(--color-base-1000);
|
||||||
|
border-radius: 9999px;
|
||||||
|
padding-top: 0.15em;
|
||||||
|
padding-bottom: 0.25em;
|
||||||
|
padding-inline: 0.6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,216 @@
|
||||||
|
---
|
||||||
|
import RichText from "components/RichText/RichText.astro";
|
||||||
|
import { payload } from "src/shared/payload/payload-sdk";
|
||||||
|
import { getI18n } from "translations/translations";
|
||||||
|
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
||||||
|
import Metadata from "pages/[locale]/api/contents/_components/Metadata.astro";
|
||||||
|
import MasoTarget from "components/Maso/MasoTarget.astro";
|
||||||
|
|
||||||
|
import AppLayoutBackgroundImg from "components/AppLayout/components/AppLayoutBackgroundImg.astro";
|
||||||
|
import LangCredits from "./_components/LangCredits.astro";
|
||||||
|
|
||||||
|
export const partial = true;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
lang?: string;
|
||||||
|
slug?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reqUrl = new URL(Astro.request.url);
|
||||||
|
const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!;
|
||||||
|
const slug = Astro.props.slug ?? reqUrl.searchParams.get("slug")!;
|
||||||
|
|
||||||
|
const { getLocalizedUrl, formatCategory, formatContentType } = await getI18n(
|
||||||
|
Astro.locals.currentLocale
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getLocalizedMatch } = await getI18n(lang);
|
||||||
|
|
||||||
|
const content = await payload.getContent(slug);
|
||||||
|
const translation = getLocalizedMatch(content.translations, {
|
||||||
|
title: slug,
|
||||||
|
format: {},
|
||||||
|
sourceLanguage: "",
|
||||||
|
});
|
||||||
|
---
|
||||||
|
|
||||||
|
<MasoTarget>
|
||||||
|
{content.thumbnail && <AppLayoutBackgroundImg src={content.thumbnail.url} />}
|
||||||
|
|
||||||
|
<div id="layout">
|
||||||
|
<div id="left">
|
||||||
|
<AppLayoutTitle
|
||||||
|
title={translation.title}
|
||||||
|
pretitle={translation.pretitle}
|
||||||
|
subtitle={translation.subtitle}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
content.thumbnail && (
|
||||||
|
<img
|
||||||
|
id="thumbnail"
|
||||||
|
class="when-not-large"
|
||||||
|
src={content.thumbnail.url}
|
||||||
|
width={content.thumbnail.width}
|
||||||
|
height={content.thumbnail.height}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
translation.summary && (
|
||||||
|
<div id="summary" class="high-contrast-text">
|
||||||
|
<RichText content={translation.summary} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
(content.type || content.categories.length > 0) && (
|
||||||
|
<div class="meta-container">
|
||||||
|
{content.type && (
|
||||||
|
<Metadata
|
||||||
|
icon="material-symbols:shape-line-outline"
|
||||||
|
title="Type"
|
||||||
|
values={[formatContentType(content.type)]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Metadata
|
||||||
|
icon="material-symbols:workspaces-outline"
|
||||||
|
title="Categories"
|
||||||
|
values={content.categories.map((id) =>
|
||||||
|
formatCategory(id, "default")
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="when-not-large meta-container">
|
||||||
|
<LangCredits
|
||||||
|
currentLang={lang}
|
||||||
|
availableLanguages={content.translations.map(
|
||||||
|
({ language }) => language
|
||||||
|
)}
|
||||||
|
getPartialUrl={(lang) =>
|
||||||
|
getLocalizedUrl(`/api/contents/partial?lang=${lang}&slug=${slug}`)}
|
||||||
|
translators={translation.format.text?.translators}
|
||||||
|
transcribers={translation.format.text?.transcribers}
|
||||||
|
proofreaders={translation.format.text?.proofreaders}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
translation.format.text && (
|
||||||
|
<>
|
||||||
|
<hr />
|
||||||
|
<div id="text">
|
||||||
|
<RichText content={translation.format.text.content} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="right" class="when-large">
|
||||||
|
{
|
||||||
|
content.thumbnail && (
|
||||||
|
<img
|
||||||
|
id="thumbnail"
|
||||||
|
src={content.thumbnail.url}
|
||||||
|
width={content.thumbnail.width}
|
||||||
|
height={content.thumbnail.height}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="meta-container">
|
||||||
|
<LangCredits
|
||||||
|
currentLang={lang}
|
||||||
|
availableLanguages={content.translations.map(
|
||||||
|
({ language }) => language
|
||||||
|
)}
|
||||||
|
getPartialUrl={(lang) =>
|
||||||
|
getLocalizedUrl(`/api/contents/partial?lang=${lang}&slug=${slug}`)}
|
||||||
|
translators={translation.format.text?.translators}
|
||||||
|
transcribers={translation.format.text?.transcribers}
|
||||||
|
proofreaders={translation.format.text?.proofreaders}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MasoTarget>
|
||||||
|
|
||||||
|
{
|
||||||
|
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||||
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#layout {
|
||||||
|
display: grid;
|
||||||
|
justify-content: space-between;
|
||||||
|
container-type: inline-size;
|
||||||
|
|
||||||
|
@media (min-width: 80rem) {
|
||||||
|
grid-template-columns: 35rem 35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > #left {
|
||||||
|
& > #thumbnail {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 35rem;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 5px 20px -10px var(--color-shadow);
|
||||||
|
margin-block: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > #summary {
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
padding: 1.5em;
|
||||||
|
margin: -1.5em;
|
||||||
|
margin-block: 1em;
|
||||||
|
border-radius: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 3px dotted var(--color-base-500);
|
||||||
|
margin-block: 3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > #right {
|
||||||
|
& > #thumbnail {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 5px 20px -10px var(--color-shadow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-container {
|
||||||
|
@media (max-width: 35rem) {
|
||||||
|
margin-block: 5em;
|
||||||
|
gap: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-block: 2em;
|
||||||
|
display: grid;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.when-large {
|
||||||
|
@media (max-width: 80rem) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.when-not-large {
|
||||||
|
@media (min-width: 80rem) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
import AppEmptyLayout from "components/AppLayout/AppEmptyLayout.astro";
|
||||||
|
import Content from "src/pages/[locale]/api/contents/partial.astro";
|
||||||
|
|
||||||
|
const { slug } = Astro.params;
|
||||||
|
|
||||||
|
if (!slug) {
|
||||||
|
return Astro.redirect("/en/404");
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<AppEmptyLayout>
|
||||||
|
<Content slug={slug} lang={Astro.locals.currentLocale} />
|
||||||
|
</AppLayout>
|
|
@ -1,18 +0,0 @@
|
||||||
---
|
|
||||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
|
||||||
import Content from "pages/api/content.astro";
|
|
||||||
|
|
||||||
const { currentLocale } = Astro.locals;
|
|
||||||
---
|
|
||||||
|
|
||||||
{
|
|
||||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
|
||||||
}
|
|
||||||
|
|
||||||
<AppLayout
|
|
||||||
breadcrumb={[{ name: "About us", slug: "about" }]}
|
|
||||||
title="About us"
|
|
||||||
description="This is a page to test the Temporary Language Override™ feature"
|
|
||||||
>
|
|
||||||
<Content lang={currentLocale} />
|
|
||||||
</AppLayout>
|
|
|
@ -6,7 +6,9 @@ import RichText from "components/RichText/RichText.astro";
|
||||||
import FoldersSection from "./_components/FoldersSection.astro";
|
import FoldersSection from "./_components/FoldersSection.astro";
|
||||||
|
|
||||||
const { slug } = Astro.params;
|
const { slug } = Astro.params;
|
||||||
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
|
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(
|
||||||
|
Astro.locals.currentLocale
|
||||||
|
);
|
||||||
|
|
||||||
if (!slug) {
|
if (!slug) {
|
||||||
return Astro.redirect("/en/404");
|
return Astro.redirect("/en/404");
|
||||||
|
@ -16,7 +18,7 @@ const folder = await payload.getFolder(slug);
|
||||||
const meta = getLocalizedMatch(folder.translations, { name: slug });
|
const meta = getLocalizedMatch(folder.translations, { name: slug });
|
||||||
|
|
||||||
// TODO: handle folder not found
|
// TODO: handle folder not found
|
||||||
// TODO: handle rich text description
|
// TODO: send description as RichTextContent instead of string
|
||||||
// TODO: handle light and dark illustration for applayout
|
// TODO: handle light and dark illustration for applayout
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -25,7 +27,6 @@ const meta = getLocalizedMatch(folder.translations, { name: slug });
|
||||||
}
|
}
|
||||||
|
|
||||||
<AppLayout title={meta.name}>
|
<AppLayout title={meta.name}>
|
||||||
|
|
||||||
{
|
{
|
||||||
meta.description && (
|
meta.description && (
|
||||||
<div slot="header-description">
|
<div slot="header-description">
|
||||||
|
@ -33,26 +34,49 @@ const meta = getLocalizedMatch(folder.translations, { name: slug });
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
<div id="main">
|
||||||
|
{
|
||||||
|
folder.sections.type === "single" ? (
|
||||||
|
<FoldersSection folders={folder.sections.subfolders} />
|
||||||
|
) : (
|
||||||
|
<div id="sections">
|
||||||
|
{folder.sections.sections.map(({ subfolders, translations }) => (
|
||||||
|
<FoldersSection
|
||||||
|
folders={subfolders}
|
||||||
|
title={
|
||||||
|
getLocalizedMatch<{
|
||||||
|
language: string;
|
||||||
|
name: string | undefined;
|
||||||
|
}>(translations, { name: undefined }).name
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
{
|
<div>
|
||||||
folder.sections.type === "single" ? (
|
{
|
||||||
<FoldersSection folders={folder.sections.subfolders} />
|
folder.files.map(({ relationTo, value }) => {
|
||||||
) : (
|
if (relationTo === "contents") {
|
||||||
<div id="sections">
|
return (
|
||||||
{folder.sections.sections.map(({ subfolders, translations }) => (
|
<a
|
||||||
<FoldersSection
|
class="pressable"
|
||||||
folders={subfolders}
|
href={getLocalizedUrl(`/contents/${value.slug}`)}
|
||||||
title={
|
>
|
||||||
getLocalizedMatch<{
|
{value.slug}
|
||||||
language: string;
|
</a>
|
||||||
name: string | undefined;
|
);
|
||||||
}>(translations, { name: undefined }).name
|
}
|
||||||
}
|
return (
|
||||||
/>
|
<a href={getLocalizedUrl(`/library-item/${value.slug}`)}>
|
||||||
))}
|
{value.slug}
|
||||||
</div>
|
</a>
|
||||||
)
|
);
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -60,8 +84,13 @@ const meta = getLocalizedMatch(folder.translations, { name: slug });
|
||||||
}
|
}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#sections {
|
#main {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 2.5em;
|
gap: 4em;
|
||||||
|
|
||||||
|
#sections {
|
||||||
|
display: grid;
|
||||||
|
gap: 2.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -9,7 +9,7 @@ interface Props {
|
||||||
const { icon = "material-symbols:folder-outline", title, href } = Astro.props;
|
const { icon = "material-symbols:folder-outline", title, href } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<a href={href} class="keycap">
|
<a href={href} class="pressable">
|
||||||
<Icon name={icon} />
|
<Icon name={icon} />
|
||||||
<div id="right">
|
<div id="right">
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
|
|
|
@ -17,9 +17,9 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
|
||||||
<AppLayout
|
<AppLayout
|
||||||
title="Accord’s Library"
|
title="Accord’s Library"
|
||||||
illustration="/img/bg-home.webp"
|
backgroundIllustration="/img/bg-home2.webp"
|
||||||
illustrationSize="60vw"
|
hideFooterLinks
|
||||||
illustrationPosition="20%"
|
hideHomeButton
|
||||||
>
|
>
|
||||||
<div id="title" slot="header-title">
|
<div id="title" slot="header-title">
|
||||||
<Icon name="accords" />
|
<Icon name="accords" />
|
||||||
|
@ -29,7 +29,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="description" slot="header-description">
|
<div id="description" slot="header-description">
|
||||||
<p set:html={t("home.description")} />
|
<p set:html={t("home.description")} class="high-contrast-text" />
|
||||||
<a href={getLocalizedUrl("/about")}>
|
<a href={getLocalizedUrl("/about")}>
|
||||||
<Button
|
<Button
|
||||||
title={t("home.aboutUsButton")}
|
title={t("home.aboutUsButton")}
|
||||||
|
@ -39,7 +39,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<section id="library">
|
<section id="library" class="high-contrast-text">
|
||||||
<h2>{t("home.librarySection.title")}</h2>
|
<h2>{t("home.librarySection.title")}</h2>
|
||||||
<p set:html={t("home.librarySection.description")} />
|
<p set:html={t("home.librarySection.description")} />
|
||||||
<a href={getLocalizedUrl("/search")}>
|
<a href={getLocalizedUrl("/search")}>
|
||||||
|
@ -186,8 +186,17 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
margin-bottom: 128px;
|
margin-bottom: 128px;
|
||||||
|
|
||||||
|
> p {
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
padding: 1em;
|
||||||
|
margin: -1em;
|
||||||
|
|
||||||
|
border-radius: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 35rem) {
|
@media (max-width: 35rem) {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
@ -267,7 +276,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
|
||||||
& > p {
|
& > p {
|
||||||
max-width: 35em;
|
max-width: 35em;
|
||||||
line-height: 1.4;
|
line-height: 1.5;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,13 @@ const { currentLocale, currentTheme, currentCurrency } = Astro.locals;
|
||||||
const { t } = await getI18n(currentLocale);
|
const { t } = await getI18n(currentLocale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<AppLayout
|
<AppLayout title={t("settings.title")}>
|
||||||
title={t("settings.title")}
|
|
||||||
breadcrumb={[{ name: t("settings.title"), slug: "settings" }]}
|
|
||||||
>
|
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>{t("settings.language.title")}</h2>
|
<h2>{t("settings.language.title")}</h2>
|
||||||
<p>{t("settings.language.description")}</p><br />
|
<p>{t("settings.language.description")}</p><br />
|
||||||
{
|
{
|
||||||
cache.locales.map((id) => (
|
cache.locales.map(({ id }) => (
|
||||||
<a
|
<a
|
||||||
class:list={{ current: currentLocale === id }}
|
class:list={{ current: currentLocale === id }}
|
||||||
href={`?action-lang=${id}`}
|
href={`?action-lang=${id}`}
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface Props {
|
||||||
const { img, name, href } = Astro.props;
|
const { img, name, href } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<a href={href} aria-label={name} class="keycap">
|
<a href={href} aria-label={name} class="pressable">
|
||||||
{
|
{
|
||||||
img ? (
|
img ? (
|
||||||
<>
|
<>
|
||||||
|
@ -33,10 +33,10 @@ const { img, name, href } = Astro.props;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
aspect-ratio: 2;
|
||||||
|
|
||||||
& > img {
|
& > img {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
aspect-ratio: 2;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ interface Props {
|
||||||
const { pretitle, subtitle, title, href } = Astro.props;
|
const { pretitle, subtitle, title, href } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<a href={href} class="keycap">
|
<a href={href} class="pressable">
|
||||||
<p class="pretitle">{pretitle}</p>
|
<p class="pretitle">{pretitle}</p>
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
<p>{subtitle}</p>
|
<p>{subtitle}</p>
|
||||||
|
|
|
@ -10,7 +10,7 @@ interface Props {
|
||||||
const { icon, subtitle, title, href } = Astro.props;
|
const { icon, subtitle, title, href } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<a href={href} class="keycap">
|
<a href={href} class="pressable">
|
||||||
<Icon name={icon} />
|
<Icon name={icon} />
|
||||||
<div id="right">
|
<div id="right">
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
|
|
|
@ -1336,6 +1336,16 @@ export type EndpointFolder = {
|
||||||
};
|
};
|
||||||
lightThumbnail?: PayloadImage;
|
lightThumbnail?: PayloadImage;
|
||||||
darkThumbnail?: PayloadImage;
|
darkThumbnail?: PayloadImage;
|
||||||
|
files: (
|
||||||
|
| {
|
||||||
|
relationTo: "library-items";
|
||||||
|
value: LibraryItem;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
relationTo: "contents";
|
||||||
|
value: Content;
|
||||||
|
}
|
||||||
|
)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EndpointFolderPreview = {
|
export type EndpointFolderPreview = {
|
||||||
|
@ -1350,6 +1360,59 @@ export type EndpointFolderPreview = {
|
||||||
darkThumbnail?: PayloadImage;
|
darkThumbnail?: PayloadImage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EndpointContent = {
|
||||||
|
slug: string;
|
||||||
|
thumbnail?: PayloadImage;
|
||||||
|
translations: {
|
||||||
|
language: string;
|
||||||
|
sourceLanguage: string;
|
||||||
|
pretitle?: string;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
summary?: RichTextContent;
|
||||||
|
format: {
|
||||||
|
text?: {
|
||||||
|
content: RichTextContent;
|
||||||
|
toc: TableOfContentEntry[];
|
||||||
|
transcribers: string[];
|
||||||
|
translators: string[];
|
||||||
|
proofreaders: string[];
|
||||||
|
notes?: RichTextContent;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
categories: string[];
|
||||||
|
type?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EndpointRecorder = {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
avatar?: PayloadImage;
|
||||||
|
languages: string[];
|
||||||
|
biographies: {
|
||||||
|
language: string;
|
||||||
|
biography: RichTextContent;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EndpointKey = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: Key["type"];
|
||||||
|
translations: {
|
||||||
|
language: string;
|
||||||
|
name: string;
|
||||||
|
short: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TableOfContentEntry = {
|
||||||
|
prefix: string;
|
||||||
|
title: string;
|
||||||
|
children: TableOfContentEntry[];
|
||||||
|
};
|
||||||
|
|
||||||
export type PayloadImage = {
|
export type PayloadImage = {
|
||||||
url: string;
|
url: string;
|
||||||
width: number;
|
width: number;
|
||||||
|
@ -1358,6 +1421,22 @@ export type PayloadImage = {
|
||||||
filename: string;
|
filename: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RichTextContent = {
|
||||||
|
root: {
|
||||||
|
children: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ("ltr" | "rtl") | null;
|
||||||
|
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||||
|
indent: number;
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
export const payload = {
|
export const payload = {
|
||||||
getWeapon: async (slug: string): Promise<EndpointWeapon> =>
|
getWeapon: async (slug: string): Promise<EndpointWeapon> =>
|
||||||
await (await request(payloadApiUrl(Collections.Weapons, `slug/${slug}`))).json(),
|
await (await request(payloadApiUrl(Collections.Weapons, `slug/${slug}`))).json(),
|
||||||
|
@ -1371,4 +1450,10 @@ export const payload = {
|
||||||
await (await request(payloadApiUrl(Collections.Languages, `all`))).json(),
|
await (await request(payloadApiUrl(Collections.Languages, `all`))).json(),
|
||||||
getCurrencies: async (): Promise<Currency[]> =>
|
getCurrencies: async (): Promise<Currency[]> =>
|
||||||
await (await request(payloadApiUrl(Collections.Currencies, `all`))).json(),
|
await (await request(payloadApiUrl(Collections.Currencies, `all`))).json(),
|
||||||
|
getContent: async (slug: string): Promise<EndpointContent> =>
|
||||||
|
await (await request(payloadApiUrl(Collections.Contents, `slug/${slug}`))).json(),
|
||||||
|
getKeys: async (): Promise<EndpointKey[]> =>
|
||||||
|
await (await request(payloadApiUrl(Collections.Keys, `all`))).json(),
|
||||||
|
getRecorders: async (): Promise<EndpointRecorder[]> =>
|
||||||
|
await (await request(payloadApiUrl(Collections.Recorders, `all`))).json(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +1,27 @@
|
||||||
import { payload } from "src/shared/payload/payload-sdk";
|
import {
|
||||||
|
payload,
|
||||||
|
type EndpointKey,
|
||||||
|
type EndpointRecorder,
|
||||||
|
type Language,
|
||||||
|
} from "src/shared/payload/payload-sdk";
|
||||||
|
|
||||||
type Cache = {
|
type Cache = {
|
||||||
locales: string[];
|
locales: Language[];
|
||||||
currencies: string[];
|
currencies: string[];
|
||||||
|
keys: EndpointKey[];
|
||||||
|
recorders: EndpointRecorder[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchNewData = async (): Promise<Cache> => ({
|
const fetchNewData = async (): Promise<Cache> => ({
|
||||||
locales: (await payload.getLanguages()).map(({ id }) => id),
|
locales: (await payload.getLanguages()),
|
||||||
currencies: (await payload.getCurrencies()).map(({ id }) => id),
|
currencies: (await payload.getCurrencies()).map(({ id }) => id),
|
||||||
|
keys: await payload.getKeys(),
|
||||||
|
recorders: await payload.getRecorders(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export let cache = await fetchNewData();
|
export let cache = await fetchNewData();
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
console.log("Refreshing cached Payload data")
|
console.log("Refreshing cached Payload data");
|
||||||
cache = await fetchNewData();
|
cache = await fetchNewData();
|
||||||
}, 1000_000);
|
}, 1000_000);
|
||||||
|
|
|
@ -30,9 +30,7 @@ export const getCookieCurrency = (
|
||||||
cookies: AstroCookies
|
cookies: AstroCookies
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
const cookieValue = cookies.get(CookieKeys.Currency)?.value;
|
const cookieValue = cookies.get(CookieKeys.Currency)?.value;
|
||||||
return isValidCurrency(cookieValue)
|
return isValidCurrency(cookieValue) ? cookieValue : undefined;
|
||||||
? cookieValue
|
|
||||||
: undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCookieTheme = (
|
export const getCookieTheme = (
|
||||||
|
@ -53,4 +51,6 @@ export const isValidCurrency = (
|
||||||
export const isValidLocale = (
|
export const isValidLocale = (
|
||||||
locale: string | null | undefined
|
locale: string | null | undefined
|
||||||
): locale is string =>
|
): locale is string =>
|
||||||
locale !== null && locale != undefined && cache.locales.includes(locale);
|
locale !== null &&
|
||||||
|
locale != undefined &&
|
||||||
|
cache.locales.map(({ id }) => id).includes(locale);
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import currencies from "src/shared/openExchange/currencies.json";
|
||||||
|
import { rates } from "src/shared/openExchange/rates.json";
|
||||||
|
|
||||||
|
type CurrencyCode = keyof typeof rates;
|
||||||
|
|
||||||
|
export const convert = (from: string, to: string, amount: number) => {
|
||||||
|
if (!isCurrencyCode(from)) return NaN;
|
||||||
|
if (!isCurrencyCode(to)) return NaN;
|
||||||
|
|
||||||
|
return (amount / rates[from]) * rates[to];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatCurrency = (code: string) => {
|
||||||
|
if (!isCurrencyCode(code)) return code;
|
||||||
|
return currencies[code];
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCurrencyCode = (code: string): code is CurrencyCode => code in rates;
|
|
@ -0,0 +1 @@
|
||||||
|
export const getRandomId = () => Math.random().toString(36).substring(2);
|
|
@ -0,0 +1,5 @@
|
||||||
|
export type RichTextContext = {
|
||||||
|
depth: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultContext: RichTextContext = { depth: 1 };
|
|
@ -4,6 +4,7 @@ import fr from "./fr.json";
|
||||||
import ja from "./ja.json";
|
import ja from "./ja.json";
|
||||||
|
|
||||||
import acceptLanguage from "accept-language";
|
import acceptLanguage from "accept-language";
|
||||||
|
import { KeysTypes } from "src/shared/payload/payload-sdk";
|
||||||
|
|
||||||
type WordingKeys = keyof typeof en;
|
type WordingKeys = keyof typeof en;
|
||||||
const translationFiles: Record<string, Record<WordingKeys, string>> = {
|
const translationFiles: Record<string, Record<WordingKeys, string>> = {
|
||||||
|
@ -109,6 +110,39 @@ export const getI18n = async (locale: string) => {
|
||||||
return template;
|
return template;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getLocalizedMatch = <T extends { language: string }>(
|
||||||
|
options: T[],
|
||||||
|
fallback: Omit<T, "language">
|
||||||
|
): Omit<T, "language"> & { language?: string } =>
|
||||||
|
options.find(({ language }) => language === locale) ??
|
||||||
|
options.find(({ language }) => language === defaultLocale) ?? {
|
||||||
|
...fallback,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLocalizedKey = (
|
||||||
|
keyType: KeysTypes,
|
||||||
|
keyId: string,
|
||||||
|
format: "short" | "default"
|
||||||
|
) => {
|
||||||
|
const category = cache.keys.find(
|
||||||
|
({ id, type }) => id === keyId && type === keyType
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
if (!category.translations) {
|
||||||
|
return category.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const translation = getLocalizedMatch(category.translations, {
|
||||||
|
name: category.name,
|
||||||
|
short: category.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
return format === "default" ? translation.name : translation.short;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
t: (key: WordingKeys, values: Record<string, any> = {}): string => {
|
t: (key: WordingKeys, values: Record<string, any> = {}): string => {
|
||||||
if (translations && key in translations) {
|
if (translations && key in translations) {
|
||||||
|
@ -117,13 +151,24 @@ export const getI18n = async (locale: string) => {
|
||||||
return `«${key}»`;
|
return `«${key}»`;
|
||||||
},
|
},
|
||||||
getLocalizedUrl: (url: string): string => `/${locale}${url}`,
|
getLocalizedUrl: (url: string): string => `/${locale}${url}`,
|
||||||
getLocalizedMatch: <T extends { language: string }>(
|
getLocalizedMatch,
|
||||||
options: T[],
|
formatCategory: (
|
||||||
fallback: Omit<T, "language">
|
id: string,
|
||||||
): Omit<T, "language"> =>
|
format: "short" | "default" = "default"
|
||||||
options.find(({ language }) => language === locale) ??
|
): string => getLocalizedKey(KeysTypes.Categories, id, format),
|
||||||
options.find(({ language }) => language === defaultLocale) ??
|
formatContentType: (id: string): string =>
|
||||||
fallback,
|
getLocalizedKey(KeysTypes.Contents, id, "default"),
|
||||||
|
formatRecorder: (recorderId: string): string => {
|
||||||
|
const result = cache.recorders.find(({ id }) => id === recorderId);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.username;
|
||||||
|
},
|
||||||
|
formatLocale: (code: string): string =>
|
||||||
|
cache.locales.find(({ id }) => id === code)?.name ?? code,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -151,8 +196,8 @@ export const defaultLocale: Locale = "en";
|
||||||
|
|
||||||
export const getCurrentLocale = (pathname: string): Locale | undefined => {
|
export const getCurrentLocale = (pathname: string): Locale | undefined => {
|
||||||
for (const locale of cache.locales) {
|
for (const locale of cache.locales) {
|
||||||
if (pathname.startsWith(`/${locale}`)) {
|
if (pathname.startsWith(`/${locale.id}`)) {
|
||||||
return locale;
|
return locale.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -161,7 +206,7 @@ export const getCurrentLocale = (pathname: string): Locale | undefined => {
|
||||||
export const getBestAcceptedLanguage = (
|
export const getBestAcceptedLanguage = (
|
||||||
request: Request
|
request: Request
|
||||||
): Locale | undefined => {
|
): Locale | undefined => {
|
||||||
acceptLanguage.languages(cache.locales);
|
acceptLanguage.languages(cache.locales.map(({ id }) => id));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(acceptLanguage.get(
|
(acceptLanguage.get(
|
||||||
|
|
Loading…
Reference in New Issue