Added content
This commit is contained in:
parent
8fc4b6a5c4
commit
133fa015e5
|
@ -8,27 +8,29 @@
|
|||
- `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-no-print`: only display when not printing
|
||||
|
||||
- `hide-scrollbar`: hide the element scrollbar
|
||||
- `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.
|
||||
- `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
|
||||
|
||||
- `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
|
||||
|
||||
- `--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.
|
||||
|
||||
|
||||
## Translations
|
||||
|
||||
For all the following exemples, the spaces within the double curly braces are important.
|
||||
|
||||
|
||||
### Variables
|
||||
|
||||
Variables allow to embed strings or numbers within a translation.
|
||||
|
|
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 AppLayoutBackgroundImg from "./components/AppLayoutBackgroundImg.astro";
|
||||
import Topbar from "./components/Topbar.astro";
|
||||
import Footer from "./components/Footer.astro";
|
||||
import AppLayoutTitle from "./components/AppLayoutTitle.astro";
|
||||
import type { ComponentProps } from "astro/types";
|
||||
|
||||
interface Props {
|
||||
breadcrumb?: { name: string; slug: string }[];
|
||||
title?: string | undefined;
|
||||
parentPages?: ComponentProps<typeof Topbar>["parentPages"];
|
||||
pretitle?: string | undefined;
|
||||
title: string;
|
||||
subtitle?: string | undefined;
|
||||
description?: string | undefined;
|
||||
illustration?: string;
|
||||
illustrationSize?: string;
|
||||
illustrationPosition?: string;
|
||||
backgroundIllustration?: string | undefined;
|
||||
hideFooterLinks?: boolean;
|
||||
hideHomeButton?: boolean;
|
||||
}
|
||||
|
||||
const {
|
||||
title = "Accord’s Library",
|
||||
title,
|
||||
subtitle,
|
||||
pretitle,
|
||||
description,
|
||||
illustration,
|
||||
breadcrumb = [],
|
||||
backgroundIllustration,
|
||||
parentPages,
|
||||
illustrationSize = "contain",
|
||||
illustrationPosition = "center",
|
||||
hideFooterLinks = false,
|
||||
hideHomeButton = false,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
|
@ -28,24 +41,38 @@ const {
|
|||
|
||||
<Html title={title}>
|
||||
<header>
|
||||
<Topbar breadcrumb={breadcrumb} />
|
||||
<div id="header-content">
|
||||
<div id="header-left">
|
||||
<slot name="header-title">
|
||||
<h1>{title}</h1>
|
||||
</slot>
|
||||
{
|
||||
backgroundIllustration && (
|
||||
<AppLayoutBackgroundImg src={backgroundIllustration} />
|
||||
)
|
||||
}
|
||||
|
||||
<div id="description">
|
||||
<slot name="header-description">
|
||||
<p>{description}</p>
|
||||
</slot>
|
||||
<Topbar parentPages={parentPages} hideHomeButton={hideHomeButton} />
|
||||
{
|
||||
(
|
||||
<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>
|
||||
{illustration && <div id="image-container" />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</header>
|
||||
<main><slot /></main>
|
||||
<Footer withLinks={breadcrumb.length > 0} />
|
||||
<Footer withLinks={!hideFooterLinks} />
|
||||
</Html>
|
||||
|
||||
{
|
||||
|
@ -74,20 +101,6 @@ const {
|
|||
flex-direction: column;
|
||||
gap: 2em;
|
||||
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 {
|
||||
|
|
|
@ -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 { cache } from "src/utils/cachedPayload";
|
||||
import { getI18n } from "translations/translations";
|
||||
import { formatCurrency } from "src/utils/currencies";
|
||||
|
||||
interface Props {
|
||||
withTitle?: boolean | undefined;
|
||||
|
@ -28,7 +29,7 @@ const { currentCurrency } = Astro.locals;
|
|||
href={`?action-currency=${id}`}
|
||||
data-astro-prefetch="tap"
|
||||
>
|
||||
{id}
|
||||
{`${id} (${formatCurrency(id)})`}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
<footer>
|
||||
{
|
||||
withLinks && (
|
||||
<div id="nav">
|
||||
<div id="nav" class="when-no-print">
|
||||
<p class="font-serif">{t("global.siteName")}</p>
|
||||
<div>
|
||||
<a href="/">
|
||||
|
@ -41,11 +41,11 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
<p>{t("footer.links.home.title")}</p>
|
||||
</a>
|
||||
<a href="/archives">
|
||||
<Icon name="material-symbols:browse-outline" />
|
||||
<Icon name="material-symbols:browse" />
|
||||
<p>{"Contents"}</p>
|
||||
</a>
|
||||
<a href="/chronicles">
|
||||
<Icon name="material-symbols:book-2-outline" />
|
||||
<Icon name="material-symbols:book-2" />
|
||||
<p>{"Chronicles"}</p>
|
||||
</a>
|
||||
<a href="/changelog">
|
||||
|
@ -53,19 +53,19 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
<p>{"Changelog"}</p>
|
||||
</a>
|
||||
<a href="/timeline">
|
||||
<Icon name="material-symbols:calendar-month-outline" />
|
||||
<Icon name="material-symbols:calendar-month" />
|
||||
<p>{t("footer.links.timeline.title")}</p>
|
||||
</a>
|
||||
<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>
|
||||
</a>
|
||||
<a href="/videos">
|
||||
<Icon name="material-symbols:movie-outline" />
|
||||
<Icon name="material-symbols:movie" />
|
||||
<p>{t("footer.links.videos.title")}</p>
|
||||
</a>
|
||||
<a href="/archives">
|
||||
<Icon name="material-symbols:folder-zip-outline" />
|
||||
<Icon name="material-symbols:folder-zip" />
|
||||
<p>{t("footer.links.webArchives.title")}</p>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -90,7 +90,7 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
|
||||
{
|
||||
withLinks && (
|
||||
<div id="socials">
|
||||
<div id="socials" class="when-no-print">
|
||||
<a
|
||||
href="/discord"
|
||||
class="pressable-icon"
|
||||
|
@ -236,14 +236,16 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
gap: 3em;
|
||||
}
|
||||
|
||||
&.with-links {
|
||||
border-left: 0.1em solid var(--color-base-1000);
|
||||
grid-template-areas: "license" "socials";
|
||||
@media screen {
|
||||
&.with-links {
|
||||
border-left: 0.1em solid var(--color-base-1000);
|
||||
grid-template-areas: "license" "socials";
|
||||
|
||||
@media (max-width: 35rem) {
|
||||
grid-template-areas: "socials" "license";
|
||||
border-left: unset;
|
||||
padding-left: unset;
|
||||
@media (max-width: 35rem) {
|
||||
grid-template-areas: "socials" "license";
|
||||
border-left: unset;
|
||||
padding-left: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@ import "@fontsource-variable/vollkorn";
|
|||
import "@fontsource-variable/murecho";
|
||||
|
||||
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 parser = new UAParser(userAgent);
|
||||
|
@ -25,6 +25,7 @@ const { currentTheme } = Astro.locals;
|
|||
"manual-theme": currentTheme !== "auto",
|
||||
"light-theme": currentTheme === "light",
|
||||
"dark-theme": currentTheme === "dark",
|
||||
"texture-dots": !isIOS,
|
||||
}}
|
||||
>
|
||||
<head>
|
||||
|
@ -33,6 +34,17 @@ const { currentTheme } = Astro.locals;
|
|||
<title>{title}</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<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>
|
||||
<style>
|
||||
|
@ -47,7 +59,7 @@ const { currentTheme } = Astro.locals;
|
|||
<ViewTransitions />
|
||||
</head>
|
||||
|
||||
<body class:list={{ "texture-dots": !isIOS }}>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
@ -57,154 +69,63 @@ const { currentTheme } = Astro.locals;
|
|||
}
|
||||
|
||||
<style is:global>
|
||||
@media print {
|
||||
.when-no-print {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
&.light-theme {
|
||||
--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-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-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);
|
||||
--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;
|
||||
--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;
|
||||
}
|
||||
& .when-light-theme {
|
||||
display: initial !important;
|
||||
}
|
||||
|
||||
&.dark-theme {
|
||||
--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;
|
||||
}
|
||||
& .when-dark-theme {
|
||||
display: none !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;
|
||||
font-weight: 430;
|
||||
|
||||
--color-elevation-2: var(--color-base-100);
|
||||
--color-elevation-1: var(--color-base-125);
|
||||
--color-elevation-0: var(--color-base-150);
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
--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) {
|
||||
@media screen {
|
||||
&.dark-theme {
|
||||
--color-base-1000: #ebeae7;
|
||||
--color-base-950: #eae5e0;
|
||||
--color-base-900: #e8dfd8;
|
||||
|
@ -247,25 +168,134 @@ const { currentTheme } = Astro.locals;
|
|||
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 */
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
*::selection {
|
||||
color: var(--color-elevation-0);
|
||||
background: var(--color-base-600);
|
||||
}
|
||||
|
||||
@media screen {
|
||||
.high-contrast-text {
|
||||
text-shadow: 0 0 0.6em var(--color-elevation-0);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
color: var(--color-base-1000);
|
||||
background-color: var(--color-base-150);
|
||||
|
||||
@media screen {
|
||||
background-color: var(--color-base-150);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
padding: clamp(12px, 3vmin, 24px) clamp(24px, 4vw, 64px);
|
||||
margin: clamp(12px, 3vmin, 24px) clamp(24px, 4vw, 64px);
|
||||
min-height: 100vb;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
h1,
|
||||
|
@ -275,7 +305,8 @@ const { currentTheme } = Astro.locals;
|
|||
h5,
|
||||
h6,
|
||||
p,
|
||||
button {
|
||||
button,
|
||||
html {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -293,6 +324,10 @@ const { currentTheme } = Astro.locals;
|
|||
--font-serif: "Vollkorn Variable", serif;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
button,
|
||||
body {
|
||||
font-family: "Murecho Variable", sans-serif;
|
||||
|
@ -311,12 +346,14 @@ const { currentTheme } = Astro.locals;
|
|||
}
|
||||
}
|
||||
|
||||
.texture-dots {
|
||||
background-size: 10cm;
|
||||
background-attachment: local;
|
||||
background-image: var(--texture-dots);
|
||||
background-blend-mode: var(--texture-dots-blend);
|
||||
background-repeat: repeat;
|
||||
@media screen {
|
||||
.texture-dots {
|
||||
background-size: 10cm;
|
||||
background-attachment: local;
|
||||
background-image: var(--texture-dots);
|
||||
background-blend-mode: var(--texture-dots-blend);
|
||||
background-repeat: repeat;
|
||||
}
|
||||
}
|
||||
|
||||
.pressable-icon {
|
||||
|
@ -332,21 +369,21 @@ const { currentTheme } = Astro.locals;
|
|||
}
|
||||
}
|
||||
|
||||
.keycap {
|
||||
.pressable {
|
||||
--foreground-color: var(--color-base-650);
|
||||
color: var(--foreground-color);
|
||||
border: 0.1rem solid var(--foreground-color);
|
||||
background-color: var(--color-elevation-0);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
transition-duration: 250ms;
|
||||
transition-property: padding-top, box-shadow, background-color, color,
|
||||
border-color;
|
||||
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
border-color, translate;
|
||||
|
||||
&:hover {
|
||||
--foreground-color: var(--color-base-1000);
|
||||
box-shadow: 0 2px 2px var(--color-shadow-2);
|
||||
background-color: var(--color-elevation-1);
|
||||
translate: 0 -2px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
|
@ -354,6 +391,7 @@ const { currentTheme } = Astro.locals;
|
|||
--foreground-color: var(--color-base-1000);
|
||||
background-color: var(--color-elevation-2);
|
||||
box-shadow: 0 6px 12px 2px var(--color-shadow-2);
|
||||
translate: unset;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,4 +406,50 @@ const { currentTheme } = Astro.locals;
|
|||
.when-no-js {
|
||||
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>
|
||||
|
|
|
@ -12,7 +12,7 @@ interface Props {
|
|||
const { withTitle, class: className } = Astro.props;
|
||||
|
||||
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}>
|
||||
<div id="content" slot="tooltip-content">
|
||||
{
|
||||
cache.locales.map(id => (
|
||||
cache.locales.map(({ id }) => (
|
||||
<a
|
||||
class:list={{ current: currentLocale === id }}
|
||||
href={`?action-lang=${id}`}
|
||||
data-astro-prefetch="tap"
|
||||
>
|
||||
{id}
|
||||
{formatLocale(id)}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
|
|
|
@ -5,45 +5,49 @@ import ThemeSelector from "components/AppLayout/components/ThemeSelector.astro";
|
|||
import LanguageSelector from "components/AppLayout/components/LanguageSelector.astro";
|
||||
import CurrencySelector from "components/AppLayout/components/CurrencySelector.astro";
|
||||
import { getI18n } from "translations/translations";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
|
||||
interface Props {
|
||||
breadcrumb: { name: string; slug: string }[];
|
||||
parentPages?: { name: string; slug: string; type: string }[] | undefined;
|
||||
hideHomeButton?: boolean;
|
||||
}
|
||||
|
||||
const { breadcrumb } = Astro.props;
|
||||
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
const { parentPages = [], hideHomeButton = false } = Astro.props;
|
||||
|
||||
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<nav id="topbar">
|
||||
<nav id="topbar" class="when-no-print">
|
||||
{
|
||||
breadcrumb.length > 0 && (
|
||||
<div id="breadcrumb" class="hide-scrollbar">
|
||||
(!hideHomeButton || parentPages.length > 0) && (
|
||||
<div id="breadcrumb" class="hide-scrollbar high-contrast-text">
|
||||
<a href="/">
|
||||
<>
|
||||
<Icon name="accords" width={16} height={16} />
|
||||
<p>{t("home.title")}</p>
|
||||
</>
|
||||
<Icon name="material-symbols:home" width={16} height={16} />
|
||||
<p>{t("home.title")}</p>
|
||||
</a>
|
||||
{breadcrumb.map(({ name, slug }) => (
|
||||
<>
|
||||
<Icon
|
||||
name="material-symbols:arrow-forward-ios"
|
||||
width={12}
|
||||
height={12}
|
||||
/>
|
||||
<a href={slug}>
|
||||
<p>{name}</p>
|
||||
</a>
|
||||
</>
|
||||
))}
|
||||
|
||||
{parentPages.length > 0 && (
|
||||
<Tooltip trigger="click">
|
||||
<div slot="tooltip-content">
|
||||
<p>This content is part of these pages:</p>
|
||||
<p>NieR / Concert</p>
|
||||
<p>NieR:Automata / Concert</p>
|
||||
<p>NieR:Theatrical Orchestra Concert 12020 Bluray</p>
|
||||
</div>
|
||||
<div>
|
||||
<Icon name="material-symbols:keyboard-return" />
|
||||
<p>4 parent pages</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div id="toolbar">
|
||||
<a href={getLocalizedUrl("/search")}>
|
||||
<Button
|
||||
|
@ -89,19 +93,25 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
display: flex;
|
||||
place-items: center;
|
||||
overflow-x: scroll;
|
||||
gap: 8px;
|
||||
margin-left: -0.8em;
|
||||
|
||||
& > svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
& > a {
|
||||
& > a,
|
||||
& > :global(tippy-tooltip > div) {
|
||||
text-decoration: none;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 0.4em;
|
||||
padding: 0.4em 0.6em;
|
||||
padding: 0.7em 0.8em;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
transition: 150ms background-color;
|
||||
|
||||
|
@ -112,10 +122,6 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
&:active {
|
||||
background-color: var(--color-base-300);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ const icons =
|
|||
|
||||
<button
|
||||
id={id}
|
||||
class:list={[{ "with-title": !!title }, className]}
|
||||
class:list={["pressable", "high-contrast-text", { "with-title": !!title }, className]}
|
||||
aria-label={ariaLabel}
|
||||
title={ariaLabel}
|
||||
>
|
||||
|
@ -35,15 +35,13 @@ const icons =
|
|||
|
||||
<style>
|
||||
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;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
height: 2.5em;
|
||||
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
display: flex;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
|
@ -56,7 +54,6 @@ const icons =
|
|||
transition-duration: 250ms;
|
||||
transition-property: padding-top, box-shadow, background-color, color,
|
||||
border-color;
|
||||
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
|
||||
&.with-title > svg {
|
||||
width: 1.2em;
|
||||
|
@ -69,16 +66,12 @@ const icons =
|
|||
}
|
||||
|
||||
&:hover {
|
||||
--foreground-color: var(--color-base-1000);
|
||||
box-shadow: inset 0 0.1em 0.1em 0 var(--color-shadow-2);
|
||||
translate: unset;
|
||||
}
|
||||
|
||||
&: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);
|
||||
|
||||
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 ------------------------------------------- */
|
||||
}
|
||||
|
||||
<maso-target class={className} id={id}>
|
||||
<maso-target>
|
||||
<slot />
|
||||
</maso-target>
|
||||
|
||||
|
||||
{
|
||||
/* ------------------------------------------- JS --------------------------------------------- */
|
||||
}
|
||||
|
|
|
@ -1,41 +1,26 @@
|
|||
---
|
||||
import type { RichTextContent } from "src/shared/payload/payload-sdk";
|
||||
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 {
|
||||
content: {
|
||||
root: {
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
content: RichTextContent;
|
||||
context?: RichTextContext;
|
||||
}
|
||||
|
||||
const { content } = Astro.props;
|
||||
const { content, context = defaultContext } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<div class="rich-text">
|
||||
{content.root.children.map((node) => <RTNode node={node} />)}
|
||||
</div>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
.rich-text {
|
||||
& li[checkbox]::marker {
|
||||
content: "☐";
|
||||
}
|
||||
|
||||
& li[checkbox][checked]::marker {
|
||||
content: "☒";
|
||||
}
|
||||
<ConditionalWrapper condition={context.depth === 1} wrapper={RTProse}>
|
||||
{
|
||||
content.root.children.map((node) => (
|
||||
<RTNode node={node} context={context} />
|
||||
))
|
||||
}
|
||||
</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 RTCustomLink from "./components/RTCustomLink.astro";
|
||||
import RTInternalLink from "./components/RTInternalLink.astro";
|
||||
|
@ -21,25 +21,26 @@ interface Props {
|
|||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
const { node, context } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
node.fields.linkType === "custom" ? (
|
||||
<RTCustomLink href={node.fields.url} newTab={node.fields.newTab}>
|
||||
{node.children.map((node) => (
|
||||
<RTNode node={node} />
|
||||
<RTNode node={node} context={context} />
|
||||
))}
|
||||
</RTCustomLink>
|
||||
) : node.fields.linkType === "internal" ? (
|
||||
<RTInternalLink doc={node.fields.doc}>
|
||||
{node.children.map((node) => (
|
||||
<RTNode node={node} />
|
||||
<RTNode node={node} context={context} />
|
||||
))}
|
||||
</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 RTCheckListItem from "./components/RTCheckListItem.astro";
|
||||
|
||||
|
@ -6,38 +7,42 @@ interface Props {
|
|||
node: {
|
||||
type: string;
|
||||
version: number;
|
||||
format: number;
|
||||
text: string;
|
||||
listType: string;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
}[];
|
||||
[k: string]: unknown;
|
||||
};
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
const { node, context } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
node.listType === "number" ? (
|
||||
<ol>
|
||||
{node.children.map((node) => (
|
||||
<RTBasicListItem node={node} />
|
||||
<RTBasicListItem node={node} context={context} />
|
||||
))}
|
||||
</ol>
|
||||
) : node.listType === "bullet" ? (
|
||||
<ul>
|
||||
{node.children.map((node) => (
|
||||
<RTBasicListItem node={node} />
|
||||
<RTBasicListItem node={node} context={context} />
|
||||
))}
|
||||
</ul>
|
||||
) : node.listType === "check" ? (
|
||||
<ul>
|
||||
{node.children.map((node) => (
|
||||
<RTCheckListItem node={node} />
|
||||
<RTCheckListItem node={node} context={context} />
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
import RTNode from "../../RTNode.astro";
|
||||
|
||||
interface Props {
|
||||
|
@ -10,14 +11,12 @@ interface Props {
|
|||
}[];
|
||||
type: string;
|
||||
version: number;
|
||||
format: number;
|
||||
text: string;
|
||||
listType: string;
|
||||
[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 RTNode from "../../RTNode.astro";
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
|
||||
interface Props {
|
||||
node: {
|
||||
|
@ -11,14 +12,12 @@ interface Props {
|
|||
}[];
|
||||
type: string;
|
||||
version: number;
|
||||
format: number;
|
||||
text: string;
|
||||
listType: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
const { node, context } = Astro.props;
|
||||
---
|
||||
|
||||
<li>
|
||||
|
@ -27,13 +26,13 @@ const { node } = Astro.props;
|
|||
? "material-symbols:check-box"
|
||||
: "material-symbols:check-box-outline-blank"}
|
||||
/>
|
||||
{node.children.map((node) => <RTNode node={node} />)}
|
||||
{node.children.map((node) => <RTNode node={node} context={context} />)}
|
||||
</li>
|
||||
|
||||
<style>
|
||||
li {
|
||||
&::marker {
|
||||
content: ""
|
||||
content: "";
|
||||
}
|
||||
margin-left: -16px;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
---
|
||||
import RTParagraph from "./RTParagraph.astro";
|
||||
import RTList from "./RTList/RTList.astro";
|
||||
import RTError from "./RTError.astro";
|
||||
import RTText from "./RTText/RTText.astro";
|
||||
import RTLink from "./RTLink/RTLink.astro";
|
||||
import RTBlock from "./RTBlock/RTBlock.astro";
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
|
||||
interface Props {
|
||||
node: {
|
||||
|
@ -11,9 +12,10 @@ interface Props {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
const { node, context } = Astro.props;
|
||||
|
||||
let NodeElement;
|
||||
switch (node.type) {
|
||||
|
@ -33,10 +35,16 @@ switch (node.type) {
|
|||
NodeElement = RTLink;
|
||||
break;
|
||||
|
||||
default:
|
||||
NodeElement = RTError;
|
||||
case "block":
|
||||
NodeElement = RTBlock;
|
||||
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";
|
||||
|
||||
interface Props {
|
||||
|
@ -12,23 +13,19 @@ interface Props {
|
|||
}[];
|
||||
[k: string]: unknown;
|
||||
};
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
const { node, context } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<p>{node.children.map((node) => <RTNode node={node} />)}</p>
|
||||
<p>{node.children.map((node) => <RTNode node={node} context={context} />)}</p>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- 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";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
|
||||
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(
|
||||
Astro.locals.currentLocale
|
||||
);
|
||||
|
||||
if (!slug) {
|
||||
return Astro.redirect("/en/404");
|
||||
|
@ -16,7 +18,7 @@ const folder = await payload.getFolder(slug);
|
|||
const meta = getLocalizedMatch(folder.translations, { name: slug });
|
||||
|
||||
// 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
|
||||
---
|
||||
|
||||
|
@ -25,7 +27,6 @@ const meta = getLocalizedMatch(folder.translations, { name: slug });
|
|||
}
|
||||
|
||||
<AppLayout title={meta.name}>
|
||||
|
||||
{
|
||||
meta.description && (
|
||||
<div slot="header-description">
|
||||
|
@ -33,26 +34,49 @@ const meta = getLocalizedMatch(folder.translations, { name: slug });
|
|||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
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.files.map(({ relationTo, value }) => {
|
||||
if (relationTo === "contents") {
|
||||
return (
|
||||
<a
|
||||
class="pressable"
|
||||
href={getLocalizedUrl(`/contents/${value.slug}`)}
|
||||
>
|
||||
{value.slug}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a href={getLocalizedUrl(`/library-item/${value.slug}`)}>
|
||||
{value.slug}
|
||||
</a>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
|
||||
{
|
||||
|
@ -60,8 +84,13 @@ const meta = getLocalizedMatch(folder.translations, { name: slug });
|
|||
}
|
||||
|
||||
<style>
|
||||
#sections {
|
||||
#main {
|
||||
display: grid;
|
||||
gap: 2.5em;
|
||||
gap: 4em;
|
||||
|
||||
#sections {
|
||||
display: grid;
|
||||
gap: 2.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@ interface Props {
|
|||
const { icon = "material-symbols:folder-outline", title, href } = Astro.props;
|
||||
---
|
||||
|
||||
<a href={href} class="keycap">
|
||||
<a href={href} class="pressable">
|
||||
<Icon name={icon} />
|
||||
<div id="right">
|
||||
<h3>{title}</h3>
|
||||
|
|
|
@ -17,9 +17,9 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
|
||||
<AppLayout
|
||||
title="Accord’s Library"
|
||||
illustration="/img/bg-home.webp"
|
||||
illustrationSize="60vw"
|
||||
illustrationPosition="20%"
|
||||
backgroundIllustration="/img/bg-home2.webp"
|
||||
hideFooterLinks
|
||||
hideHomeButton
|
||||
>
|
||||
<div id="title" slot="header-title">
|
||||
<Icon name="accords" />
|
||||
|
@ -29,7 +29,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
</div>
|
||||
</div>
|
||||
<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")}>
|
||||
<Button
|
||||
title={t("home.aboutUsButton")}
|
||||
|
@ -39,7 +39,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
</div>
|
||||
|
||||
<div id="main">
|
||||
<section id="library">
|
||||
<section id="library" class="high-contrast-text">
|
||||
<h2>{t("home.librarySection.title")}</h2>
|
||||
<p set:html={t("home.librarySection.description")} />
|
||||
<a href={getLocalizedUrl("/search")}>
|
||||
|
@ -186,8 +186,17 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
flex-direction: column;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
|
||||
margin-bottom: 128px;
|
||||
|
||||
> p {
|
||||
backdrop-filter: blur(5px);
|
||||
padding: 1em;
|
||||
margin: -1em;
|
||||
|
||||
border-radius: 5em;
|
||||
}
|
||||
|
||||
@media (max-width: 35rem) {
|
||||
align-items: center;
|
||||
|
||||
|
@ -267,7 +276,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
|
||||
& > p {
|
||||
max-width: 35em;
|
||||
line-height: 1.4;
|
||||
line-height: 1.5;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
|
|
@ -7,16 +7,13 @@ const { currentLocale, currentTheme, currentCurrency } = Astro.locals;
|
|||
const { t } = await getI18n(currentLocale);
|
||||
---
|
||||
|
||||
<AppLayout
|
||||
title={t("settings.title")}
|
||||
breadcrumb={[{ name: t("settings.title"), slug: "settings" }]}
|
||||
>
|
||||
<AppLayout title={t("settings.title")}>
|
||||
<div id="main">
|
||||
<div class="section">
|
||||
<h2>{t("settings.language.title")}</h2>
|
||||
<p>{t("settings.language.description")}</p><br />
|
||||
{
|
||||
cache.locales.map((id) => (
|
||||
cache.locales.map(({ id }) => (
|
||||
<a
|
||||
class:list={{ current: currentLocale === id }}
|
||||
href={`?action-lang=${id}`}
|
||||
|
|
|
@ -8,7 +8,7 @@ interface Props {
|
|||
const { img, name, href } = Astro.props;
|
||||
---
|
||||
|
||||
<a href={href} aria-label={name} class="keycap">
|
||||
<a href={href} aria-label={name} class="pressable">
|
||||
{
|
||||
img ? (
|
||||
<>
|
||||
|
@ -33,10 +33,10 @@ const { img, name, href } = Astro.props;
|
|||
border-radius: 12px;
|
||||
|
||||
user-select: none;
|
||||
aspect-ratio: 2;
|
||||
|
||||
& > img {
|
||||
object-fit: contain;
|
||||
aspect-ratio: 2;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ interface Props {
|
|||
const { pretitle, subtitle, title, href } = Astro.props;
|
||||
---
|
||||
|
||||
<a href={href} class="keycap">
|
||||
<a href={href} class="pressable">
|
||||
<p class="pretitle">{pretitle}</p>
|
||||
<h3>{title}</h3>
|
||||
<p>{subtitle}</p>
|
||||
|
|
|
@ -10,7 +10,7 @@ interface Props {
|
|||
const { icon, subtitle, title, href } = Astro.props;
|
||||
---
|
||||
|
||||
<a href={href} class="keycap">
|
||||
<a href={href} class="pressable">
|
||||
<Icon name={icon} />
|
||||
<div id="right">
|
||||
<h3>{title}</h3>
|
||||
|
|
|
@ -1336,6 +1336,16 @@ export type EndpointFolder = {
|
|||
};
|
||||
lightThumbnail?: PayloadImage;
|
||||
darkThumbnail?: PayloadImage;
|
||||
files: (
|
||||
| {
|
||||
relationTo: "library-items";
|
||||
value: LibraryItem;
|
||||
}
|
||||
| {
|
||||
relationTo: "contents";
|
||||
value: Content;
|
||||
}
|
||||
)[];
|
||||
};
|
||||
|
||||
export type EndpointFolderPreview = {
|
||||
|
@ -1350,6 +1360,59 @@ export type EndpointFolderPreview = {
|
|||
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 = {
|
||||
url: string;
|
||||
width: number;
|
||||
|
@ -1358,6 +1421,22 @@ export type PayloadImage = {
|
|||
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 = {
|
||||
getWeapon: async (slug: string): Promise<EndpointWeapon> =>
|
||||
await (await request(payloadApiUrl(Collections.Weapons, `slug/${slug}`))).json(),
|
||||
|
@ -1371,4 +1450,10 @@ export const payload = {
|
|||
await (await request(payloadApiUrl(Collections.Languages, `all`))).json(),
|
||||
getCurrencies: async (): Promise<Currency[]> =>
|
||||
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 = {
|
||||
locales: string[];
|
||||
locales: Language[];
|
||||
currencies: string[];
|
||||
keys: EndpointKey[];
|
||||
recorders: EndpointRecorder[];
|
||||
};
|
||||
|
||||
const fetchNewData = async (): Promise<Cache> => ({
|
||||
locales: (await payload.getLanguages()).map(({ id }) => id),
|
||||
locales: (await payload.getLanguages()),
|
||||
currencies: (await payload.getCurrencies()).map(({ id }) => id),
|
||||
keys: await payload.getKeys(),
|
||||
recorders: await payload.getRecorders(),
|
||||
});
|
||||
|
||||
export let cache = await fetchNewData();
|
||||
|
||||
setInterval(async () => {
|
||||
console.log("Refreshing cached Payload data")
|
||||
console.log("Refreshing cached Payload data");
|
||||
cache = await fetchNewData();
|
||||
}, 1000_000);
|
||||
|
|
|
@ -30,9 +30,7 @@ export const getCookieCurrency = (
|
|||
cookies: AstroCookies
|
||||
): string | undefined => {
|
||||
const cookieValue = cookies.get(CookieKeys.Currency)?.value;
|
||||
return isValidCurrency(cookieValue)
|
||||
? cookieValue
|
||||
: undefined;
|
||||
return isValidCurrency(cookieValue) ? cookieValue : undefined;
|
||||
};
|
||||
|
||||
export const getCookieTheme = (
|
||||
|
@ -53,4 +51,6 @@ export const isValidCurrency = (
|
|||
export const isValidLocale = (
|
||||
locale: string | null | undefined
|
||||
): 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 acceptLanguage from "accept-language";
|
||||
import { KeysTypes } from "src/shared/payload/payload-sdk";
|
||||
|
||||
type WordingKeys = keyof typeof en;
|
||||
const translationFiles: Record<string, Record<WordingKeys, string>> = {
|
||||
|
@ -109,6 +110,39 @@ export const getI18n = async (locale: string) => {
|
|||
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 {
|
||||
t: (key: WordingKeys, values: Record<string, any> = {}): string => {
|
||||
if (translations && key in translations) {
|
||||
|
@ -117,13 +151,24 @@ export const getI18n = async (locale: string) => {
|
|||
return `«${key}»`;
|
||||
},
|
||||
getLocalizedUrl: (url: string): string => `/${locale}${url}`,
|
||||
getLocalizedMatch: <T extends { language: string }>(
|
||||
options: T[],
|
||||
fallback: Omit<T, "language">
|
||||
): Omit<T, "language"> =>
|
||||
options.find(({ language }) => language === locale) ??
|
||||
options.find(({ language }) => language === defaultLocale) ??
|
||||
fallback,
|
||||
getLocalizedMatch,
|
||||
formatCategory: (
|
||||
id: string,
|
||||
format: "short" | "default" = "default"
|
||||
): string => getLocalizedKey(KeysTypes.Categories, id, format),
|
||||
formatContentType: (id: string): string =>
|
||||
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 => {
|
||||
for (const locale of cache.locales) {
|
||||
if (pathname.startsWith(`/${locale}`)) {
|
||||
return locale;
|
||||
if (pathname.startsWith(`/${locale.id}`)) {
|
||||
return locale.id;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
@ -161,7 +206,7 @@ export const getCurrentLocale = (pathname: string): Locale | undefined => {
|
|||
export const getBestAcceptedLanguage = (
|
||||
request: Request
|
||||
): Locale | undefined => {
|
||||
acceptLanguage.languages(cache.locales);
|
||||
acceptLanguage.languages(cache.locales.map(({ id }) => id));
|
||||
|
||||
return (
|
||||
(acceptLanguage.get(
|
||||
|
|
Loading…
Reference in New Issue