Something
This commit is contained in:
parent
1f448f14f0
commit
e57b82d227
|
@ -20,7 +20,15 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: "en",
|
||||||
|
locales: ["en", "es", "fr", "ja", "pt", "zh"],
|
||||||
|
routing: {
|
||||||
|
prefixDefaultLocale: true,
|
||||||
|
redirectToDefaultLocale: false,
|
||||||
|
strategy: "pathname",
|
||||||
|
},
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 12499,
|
port: 12499,
|
||||||
host: true,
|
host: true,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
Bun.serve({
|
||||||
|
port: 12498,
|
||||||
|
fetch: async (req) => {
|
||||||
|
const reqUrl = new URL(req.url);
|
||||||
|
const rewriteUrl = new URL(reqUrl);
|
||||||
|
rewriteUrl.hostname = "localhost";
|
||||||
|
rewriteUrl.port = "12499";
|
||||||
|
rewriteUrl.protocol = "http";
|
||||||
|
const rewrite = new Request(rewriteUrl, req);
|
||||||
|
const response = await fetch(rewrite, { redirect: "manual" });
|
||||||
|
console.log(`[${response.status}] ${rewriteUrl.pathname}`);
|
||||||
|
|
||||||
|
if (response.status === 404 && response.headers.has("Location")) {
|
||||||
|
|
||||||
|
// Prevent redirection from a non locale-specific page to the en locale-specific page
|
||||||
|
if (response.headers.get("location") === "/en" + rewriteUrl.pathname) {
|
||||||
|
rewriteUrl.pathname = "/en/" + rewriteUrl.pathname;
|
||||||
|
const rewrite = new Request(rewriteUrl, req);
|
||||||
|
return await fetch(rewrite, { redirect: "manual" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(await response.blob(), {
|
||||||
|
headers: response.headers,
|
||||||
|
status: 302,
|
||||||
|
statusText: "Found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
});
|
16
package.json
16
package.json
|
@ -12,20 +12,22 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.4.1",
|
"@astrojs/check": "^0.4.1",
|
||||||
"@astrojs/node": "^8.0.0",
|
"@astrojs/node": "^8.1.0",
|
||||||
"astro": "^4.2.1",
|
"astro": "^4.2.5",
|
||||||
"astro-icon": "next",
|
"astro-icon": "^1.0.3",
|
||||||
"ua-parser-js": "^1.0.37"
|
"tippy.js": "^6.3.7",
|
||||||
|
"ua-parser-js": "^1.0.37",
|
||||||
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/material-symbols": "^1.1.70",
|
"@iconify-json/material-symbols": "^1.1.71",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
"astro-meta-tags": "^0.2.1",
|
"astro-meta-tags": "^0.2.1",
|
||||||
"autoprefixer": "^10.4.17",
|
"autoprefixer": "^10.4.17",
|
||||||
"bun-types": "^1.0.24",
|
"bun-types": "^1.0.25",
|
||||||
"postcss-preset-env": "^9.3.0",
|
"postcss-preset-env": "^9.3.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"npm-check-updates": "^16.14.12"
|
"npm-check-updates": "^16.14.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
*,::after,::before{box-sizing:border-box;background-repeat:no-repeat}::after,::before{text-decoration:inherit;vertical-align:inherit}:where(:root){cursor:default;line-height:1.5;overflow-wrap:break-word;-moz-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%}:where(body){margin:0}:where(h1){font-size:2em;margin:0.67em 0}:where(dl,ol,ul):where(dl,ol,ul){margin:0}:where(hr){color:inherit;height:0}:where(nav):where(ol,ul){list-style-type:none;padding:0}:where(nav li)::before{content:"\200B";float:left}:where(pre){font-family:monospace, monospace;font-size:1em;overflow:auto}:where(abbr[title]){text-decoration:underline;text-decoration:underline dotted}:where(b,strong){font-weight:bolder}:where(code,kbd,samp){font-family:monospace, monospace;font-size:1em}:where(small){font-size:80%}:where(audio,canvas,iframe,img,svg,video){vertical-align:middle}:where(iframe){border-style:none}:where(svg:not([fill])){fill:currentColor}:where(table){border-collapse:collapse;border-color:inherit;text-indent:0}:where(button,input,select){margin:0}:where(button,[type="button" i],[type="reset" i],[type="submit" i]){-webkit-appearance:button}:where(fieldset){border:1px solid #a0a0a0}:where(progress){vertical-align:baseline}:where(textarea){margin:0;resize:vertical}:where([type="search" i]){-webkit-appearance:textfield;outline-offset:-2px}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}::-webkit-input-placeholder{color:inherit;opacity:0.54}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}:where(dialog){background-color:white;border:solid;color:black;height:-moz-fit-content;height:fit-content;left:0;margin:auto;padding:1em;position:absolute;right:0;width:-moz-fit-content;width:fit-content}:where(dialog:not([open])){display:none}:where(details > summary:first-of-type){display:list-item}:where([aria-busy="true" i]){cursor:progress}:where([aria-controls]){cursor:pointer}:where([aria-disabled="true" i],[disabled]){cursor:not-allowed}:where([aria-hidden="false" i][hidden]){display:initial}:where([aria-hidden="false" i][hidden]:not(:focus)){clip:rect(0, 0, 0, 0);position:absolute}
|
|
@ -0,0 +1,90 @@
|
||||||
|
.tippy-box[data-animation="fade"][data-state="hidden"] {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
[data-tippy-root] {
|
||||||
|
max-width: calc(100vw - 10px);
|
||||||
|
}
|
||||||
|
.tippy-box {
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--color-elevation-2);
|
||||||
|
color: var(--color-base-1000);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--color-base-650);
|
||||||
|
box-shadow: 0 20px 25px -5px var(--color-shadow-2),
|
||||||
|
0 0 10px -6px var(--color-shadow-2);
|
||||||
|
transition-property: transform, visibility, opacity;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="top"] > .tippy-arrow {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="top"] > .tippy-arrow:before {
|
||||||
|
bottom: -8px;
|
||||||
|
left: 0;
|
||||||
|
border-width: 8px 8px 0;
|
||||||
|
border-top-color: initial;
|
||||||
|
transform-origin: center top;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="bottom"] > .tippy-arrow {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="bottom"] > .tippy-arrow:before {
|
||||||
|
top: -8px;
|
||||||
|
left: 0;
|
||||||
|
border-width: 0 8px 8px;
|
||||||
|
border-bottom-color: initial;
|
||||||
|
transform-origin: center bottom;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="left"] > .tippy-arrow {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="left"] > .tippy-arrow:before {
|
||||||
|
border-width: 8px 0 8px 8px;
|
||||||
|
border-left-color: initial;
|
||||||
|
right: -8px;
|
||||||
|
transform-origin: center left;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="right"] > .tippy-arrow {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="right"] > .tippy-arrow:before {
|
||||||
|
left: -8px;
|
||||||
|
border-width: 8px 8px 8px 0;
|
||||||
|
border-right-color: initial;
|
||||||
|
transform-origin: center right;
|
||||||
|
}
|
||||||
|
.tippy-box[data-inertia][data-state="visible"] {
|
||||||
|
transition-timing-function: cubic-bezier(0.54, 1.5, 0.38, 1.11);
|
||||||
|
}
|
||||||
|
.tippy-arrow {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
color: var(--color-base-650);
|
||||||
|
}
|
||||||
|
.tippy-arrow:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
border-color: transparent;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
.tippy-content {
|
||||||
|
position: relative;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="top"] {
|
||||||
|
transform-origin: bottom;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="bottom"] {
|
||||||
|
transform-origin: top;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="left"] {
|
||||||
|
transform-origin: right;
|
||||||
|
}
|
||||||
|
.tippy-box[data-placement^="right"] {
|
||||||
|
transform-origin: left;
|
||||||
|
}
|
||||||
|
.tippy-box[data-state="hidden"] {
|
||||||
|
transform: scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
|
@ -8,6 +8,8 @@ interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
illustration?: string;
|
illustration?: string;
|
||||||
|
illustrationSize?: string;
|
||||||
|
illustrationPosition?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -15,6 +17,8 @@ const {
|
||||||
description,
|
description,
|
||||||
illustration,
|
illustration,
|
||||||
breadcrumb = [],
|
breadcrumb = [],
|
||||||
|
illustrationSize = "contain",
|
||||||
|
illustrationPosition = "center",
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -45,11 +49,17 @@ const {
|
||||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
|
||||||
<style define:vars={{ illustration: `url(${illustration})` }}>
|
<style
|
||||||
|
define:vars={{
|
||||||
|
illustration: `url(${illustration})`,
|
||||||
|
illustrationSize,
|
||||||
|
illustrationPosition,
|
||||||
|
}}
|
||||||
|
>
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: 1.5em;
|
||||||
|
|
||||||
& > #header-content {
|
& > #header-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -58,12 +68,13 @@ const {
|
||||||
& > #header-left {
|
& > #header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 32px;
|
gap: 2em;
|
||||||
place-items: flex-start;
|
place-items: flex-start;
|
||||||
|
|
||||||
& > h1 {
|
& > h1 {
|
||||||
font-family: var(--font-serif);
|
font-family: var(--font-serif);
|
||||||
font-size: 48px;
|
font-size: 3em;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > p {
|
& > p {
|
||||||
|
@ -73,9 +84,9 @@ const {
|
||||||
|
|
||||||
& > #image-container {
|
& > #image-container {
|
||||||
background-image: var(--illustration);
|
background-image: var(--illustration);
|
||||||
background-size: contain;
|
background-size: var(--illustrationSize);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: right center;
|
background-position: right var(--illustrationPosition);
|
||||||
mask-image: linear-gradient(
|
mask-image: linear-gradient(
|
||||||
to left,
|
to left,
|
||||||
rgba(0, 0, 0, 1) 50%,
|
rgba(0, 0, 0, 1) 50%,
|
||||||
|
@ -90,7 +101,8 @@ const {
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
padding-top: 96px;
|
padding-top: 6em;
|
||||||
padding-bottom: 128px;
|
padding-bottom: 8em;
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -9,46 +9,82 @@ const { withLinks } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div id="nav">
|
{
|
||||||
<p class="font-serif">Accord’s Library</p>
|
withLinks && (
|
||||||
<a href="/"><Icon name="accords" />Home</a>
|
<div id="nav">
|
||||||
<a href="/timeline"
|
<p class="font-serif">Accord’s Library</p>
|
||||||
><Icon name="material-symbols:calendar-month-outline" />Timeline</a
|
<div>
|
||||||
>
|
<a href="/">
|
||||||
<a href="https://gallery.accords-library.com/posts"
|
<Icon name="accords" />
|
||||||
><Icon name="material-symbols:perm-media-outline" />Gallery</a
|
<p>Home</p>
|
||||||
>
|
</a>
|
||||||
<a href="/videos"><Icon name="material-symbols:movie-outline" />Videos</a>
|
<a href="/timeline">
|
||||||
<a href="/archives"
|
<Icon name="material-symbols:calendar-month-outline" />
|
||||||
><Icon name="material-symbols:folder-zip-outline" />Web archives</a
|
<p>Timeline</p>
|
||||||
>
|
</a>
|
||||||
</div>
|
<a href="https://gallery.accords-library.com/posts">
|
||||||
<div id="license">
|
<Icon name="material-symbols:perm-media-outline" />
|
||||||
This website’s content is made available under <a
|
<p>Gallery</p>
|
||||||
href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA</a
|
</a>
|
||||||
> unless otherwise noted.
|
<a href="/videos">
|
||||||
<a
|
<Icon name="material-symbols:movie-outline" />
|
||||||
href="https://creativecommons.org/licenses/by-sa/4.0/"
|
<p>Videos</p>
|
||||||
id="common-creative"
|
</a>
|
||||||
aria-label="CC-BY-SA 4.0 License"
|
<a href="/archives">
|
||||||
>
|
<Icon name="material-symbols:folder-zip-outline" />
|
||||||
<Icon name="creative-commons-brands" />
|
<p>Web archives</p>
|
||||||
<Icon name="creative-commons-by-brands" />
|
</a>
|
||||||
<Icon name="creative-commons-sa-brands" />
|
</div>
|
||||||
</a>
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div id="license" class:list={{ "with-links": withLinks }}>
|
||||||
|
<div id="license-section">
|
||||||
|
This website’s content is made available under <a
|
||||||
|
href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA</a
|
||||||
|
> unless otherwise noted.
|
||||||
|
<a
|
||||||
|
href="https://creativecommons.org/licenses/by-sa/4.0/"
|
||||||
|
id="common-creative"
|
||||||
|
aria-label="CC-BY-SA 4.0 License"
|
||||||
|
class="pressable-icon"
|
||||||
|
>
|
||||||
|
<Icon name="creative-commons-brands" />
|
||||||
|
<Icon name="creative-commons-by-brands" />
|
||||||
|
<Icon name="creative-commons-sa-brands" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
withLinks && (
|
withLinks && (
|
||||||
<div id="socials">
|
<div id="socials">
|
||||||
<a href="/discord">
|
<a
|
||||||
|
href="/discord"
|
||||||
|
class="pressable-icon"
|
||||||
|
aria-label="Join the community"
|
||||||
|
>
|
||||||
<Icon name="discord-brands" />
|
<Icon name="discord-brands" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://twitter.com/AccordsLibrary">
|
<a
|
||||||
|
href="https://twitter.com/AccordsLibrary"
|
||||||
|
class="pressable-icon"
|
||||||
|
aria-label="Get the latest updates"
|
||||||
|
>
|
||||||
<Icon name="x-brands" />
|
<Icon name="x-brands" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/Accords-Library">
|
<a
|
||||||
|
href="https://github.com/Accords-Library"
|
||||||
|
class="pressable-icon"
|
||||||
|
aria-label="Join the technical side"
|
||||||
|
>
|
||||||
<Icon name="github-brands" />
|
<Icon name="github-brands" />
|
||||||
</a>
|
</a>
|
||||||
<a href="/contact">
|
|
||||||
|
<a
|
||||||
|
href="/contact"
|
||||||
|
aria-label="Send us an email"
|
||||||
|
class="pressable-icon"
|
||||||
|
>
|
||||||
<Icon name="material-symbols:mail-outline" />
|
<Icon name="material-symbols:mail-outline" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,57 +102,171 @@ const { withLinks } = Astro.props;
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
footer {
|
footer {
|
||||||
border-top: 1px solid var(--color-base-1000);
|
border-top: 0.1em solid var(--color-base-1000);
|
||||||
padding-top: 32px;
|
padding-top: 2em;
|
||||||
display: flex;
|
display: flex;
|
||||||
place-content: center;
|
place-content: center;
|
||||||
gap: clamp(24px, 12px + 2vw, 64px);
|
gap: clamp(1.5em, 1.25em + 2vw, 4em);
|
||||||
font-size: 14px;
|
font-size: 0.85em;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
max-width: 20em;
|
max-width: 20em;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > #nav {
|
& > #nav {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: row;
|
|
||||||
|
@media (max-width: 35rem) {
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
& > p {
|
& > p {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
font-size: 1.2em;
|
||||||
|
white-space: pre;
|
||||||
|
|
||||||
|
@media (max-width: 35rem) {
|
||||||
|
font-size: clamp(1em, 8vw, 2.5em);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
display: grid;
|
||||||
|
flex-direction: column;
|
||||||
|
place-items: start;
|
||||||
|
margin-top: 0.8em;
|
||||||
|
gap: 0.3em;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
margin-left: -0.6em;
|
||||||
|
|
||||||
|
@media (max-width: 65rem) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
gap: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 35rem) {
|
||||||
|
gap: 0.25em 0.5em;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
margin-top: 0.8em;
|
||||||
|
margin-left: unset;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 22rem) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
margin-top: 0.8em;
|
||||||
|
margin-left: unset;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
gap: 0.4em;
|
||||||
|
padding: 0.4em 0.6em;
|
||||||
|
border-radius: 9999px;
|
||||||
|
|
||||||
|
@media (max-width: 35rem) {
|
||||||
|
padding: 0.6em 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
transition: 150ms background-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-base-250);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--color-base-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 0.75em;
|
||||||
|
width: 0.75em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > #license {
|
& > #license {
|
||||||
border-left: 1px solid var(--color-base-1000);
|
display: grid;
|
||||||
|
grid-template-areas: "license";
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
|
|
||||||
& > #common-creative {
|
@media (max-width: 35rem) {
|
||||||
display: flex;
|
gap: 3em;
|
||||||
justify-content: flex-start;
|
}
|
||||||
gap: 0.2em;
|
|
||||||
margin-top: 8px;
|
|
||||||
|
|
||||||
& > svg {
|
&.with-links {
|
||||||
width: 16px;
|
border-left: 0.1em solid var(--color-base-1000);
|
||||||
height: 16px;
|
grid-template-areas: "license" "socials";
|
||||||
|
|
||||||
|
@media (max-width: 35rem) {
|
||||||
|
grid-template-areas: "socials" "license";
|
||||||
|
border-left: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > #license-section {
|
||||||
|
grid-area: license;
|
||||||
|
|
||||||
|
& > #common-creative {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 0.2em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 35rem) {
|
||||||
|
place-content: center;
|
||||||
|
gap: clamp(4px, 2vw, 8px);
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
width: clamp(1em, 6vw, 24px);
|
||||||
|
height: clamp(1em, 6vw, 24px);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > #socials {
|
& > #socials {
|
||||||
|
grid-area: socials;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24px;
|
gap: 1.5em;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
|
|
||||||
& > a > svg {
|
& > a > svg {
|
||||||
width: 24px;
|
width: 1.5em;
|
||||||
height: 24px;
|
height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 35rem) {
|
||||||
|
place-content: center;
|
||||||
|
gap: clamp(24px, 8vw, 48px);
|
||||||
|
|
||||||
|
& > a > svg {
|
||||||
|
width: clamp(24px, 8vw, 48px);
|
||||||
|
height: clamp(24px, 8vw, 48px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > #copyright {
|
& > #copyright {
|
||||||
border-left: 1px solid var(--color-base-1000);
|
border-left: 0.1em solid var(--color-base-1000);
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
|
|
||||||
|
@media (max-width: 35rem) {
|
||||||
|
border: none;
|
||||||
|
padding-left: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 35rem) {
|
@media (max-width: 35rem) {
|
||||||
|
@ -127,33 +277,6 @@ const { withLinks } = Astro.props;
|
||||||
& > div {
|
& > div {
|
||||||
max-width: unset;
|
max-width: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > #license {
|
|
||||||
& > #common-creative {
|
|
||||||
place-content: center;
|
|
||||||
gap: clamp(4px, 2vw, 8px);
|
|
||||||
|
|
||||||
& > svg {
|
|
||||||
width: clamp(16px, 6vw, 24px);
|
|
||||||
height: clamp(16px, 6vw, 24px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > #socials {
|
|
||||||
place-content: center;
|
|
||||||
gap: clamp(24px, 8vw, 48px);
|
|
||||||
|
|
||||||
& > a > svg {
|
|
||||||
width: clamp(24px, 8vw, 48px);
|
|
||||||
height: clamp(24px, 8vw, 48px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > #copyright {
|
|
||||||
border: none;
|
|
||||||
padding-left: unset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,10 +11,20 @@ const userAgent = Astro.request.headers.get("user-agent") ?? "";
|
||||||
const parser = new UAParser(userAgent);
|
const parser = new UAParser(userAgent);
|
||||||
const isIOS = parser.getOS().name === "iOS";
|
const isIOS = parser.getOS().name === "iOS";
|
||||||
|
|
||||||
|
const prefTheme = Astro.cookies.get("al_pref_theme")?.value;
|
||||||
|
|
||||||
/* -------------------------------------------- HTML -------------------------------------------- */
|
/* -------------------------------------------- HTML -------------------------------------------- */
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html
|
||||||
|
lang="en"
|
||||||
|
style={{ fontSize: "200%" }}
|
||||||
|
class:list={{
|
||||||
|
"manual-theme": prefTheme !== undefined,
|
||||||
|
"light-theme": prefTheme === "light",
|
||||||
|
"dark-theme": prefTheme === "dark",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
@ -23,6 +33,7 @@ const isIOS = parser.getOS().name === "iOS";
|
||||||
<!-- Fonts google -->
|
<!-- Fonts google -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link rel="stylesheet" href="/css/tippy.css" />
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Vollkorn:wght@400;500;600;700;800;900&family=Zen+Maru+Gothic:wght@400;500;700;900&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Vollkorn:wght@400;500;600;700;800;900&family=Zen+Maru+Gothic:wght@400;500;700;900&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
|
@ -49,12 +60,8 @@ const isIOS = parser.getOS().name === "iOS";
|
||||||
}
|
}
|
||||||
|
|
||||||
<style is:global>
|
<style is:global>
|
||||||
:root {
|
html {
|
||||||
--font-serif: "Vollkorn", serif;
|
&.light-theme {
|
||||||
|
|
||||||
/* Get in between colors with https://colorkit.io/ */
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
--color-base-0: #ffffff;
|
--color-base-0: #ffffff;
|
||||||
--color-base-50: #fffaf3;
|
--color-base-50: #fffaf3;
|
||||||
--color-base-100: #fff4e6;
|
--color-base-100: #fff4e6;
|
||||||
|
@ -90,7 +97,7 @@ const isIOS = parser.getOS().name === "iOS";
|
||||||
--texture-dots-blend: multiply;
|
--texture-dots-blend: multiply;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
&.dark-theme {
|
||||||
--color-base-1000: #ebeae7;
|
--color-base-1000: #ebeae7;
|
||||||
--color-base-950: #eae5e0;
|
--color-base-950: #eae5e0;
|
||||||
--color-base-900: #e8dfd8;
|
--color-base-900: #e8dfd8;
|
||||||
|
@ -125,6 +132,85 @@ const isIOS = parser.getOS().name === "iOS";
|
||||||
--texture-dots: url(/img/paper-dots-dark.webp);
|
--texture-dots: url(/img/paper-dots-dark.webp);
|
||||||
--texture-dots-blend: overlay;
|
--texture-dots-blend: overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&: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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-serif: "Vollkorn", serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* THEMING */
|
/* THEMING */
|
||||||
|
@ -142,6 +228,10 @@ const isIOS = parser.getOS().name === "iOS";
|
||||||
padding-left: clamp(24px, 4vw, 64px);
|
padding-left: clamp(24px, 4vw, 64px);
|
||||||
padding-right: clamp(24px, 4vw, 64px);
|
padding-right: clamp(24px, 4vw, 64px);
|
||||||
padding-bottom: clamp(24px, 6vmin, 48px);
|
padding-bottom: clamp(24px, 6vmin, 48px);
|
||||||
|
min-height: 100vb;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
|
@ -179,6 +269,18 @@ const isIOS = parser.getOS().name === "iOS";
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pressable-icon {
|
||||||
|
transition: 150ms color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-base-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: var(--color-base-550);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.keycap {
|
.keycap {
|
||||||
transition-duration: 150ms;
|
transition-duration: 150ms;
|
||||||
transition-property: translate, box-shadow, background-color;
|
transition-property: translate, box-shadow, background-color;
|
||||||
|
|
|
@ -2,12 +2,15 @@
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import Button from "components/Button.astro";
|
import Button from "components/Button.astro";
|
||||||
import ButtonGroup from "components/ButtonGroup.astro";
|
import ButtonGroup from "components/ButtonGroup.astro";
|
||||||
|
import Tooltip from "pages/_components/Tooltip.astro";
|
||||||
|
import astroConfig from "astro.config";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
breadcrumb: { name: string; slug: string }[];
|
breadcrumb: { name: string; slug: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { breadcrumb } = Astro.props;
|
const { breadcrumb } = Astro.props;
|
||||||
|
const currentLocate = Astro.currentLocale ?? "en";
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -20,12 +23,21 @@ const { breadcrumb } = Astro.props;
|
||||||
breadcrumb.length > 0 && (
|
breadcrumb.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<Icon name="accords" /> Home
|
<>
|
||||||
|
<Icon name="accords" width={16} height={16} />
|
||||||
|
<p>Home</p>
|
||||||
|
</>
|
||||||
</a>
|
</a>
|
||||||
{breadcrumb.map(({ name, slug }) => (
|
{breadcrumb.map(({ name, slug }) => (
|
||||||
<>
|
<>
|
||||||
<Icon name="material-symbols:arrow-forward-ios" />
|
<Icon
|
||||||
<a href={slug}>{name}</a>
|
name="material-symbols:arrow-forward-ios"
|
||||||
|
width={12}
|
||||||
|
height={12}
|
||||||
|
/>
|
||||||
|
<a href={slug}>
|
||||||
|
<p>{name}</p>
|
||||||
|
</a>
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -35,20 +47,29 @@ const { breadcrumb } = Astro.props;
|
||||||
<div id="toolbar">
|
<div id="toolbar">
|
||||||
<Button icon="material-symbols:search" ariaLabel="Search on this website" />
|
<Button icon="material-symbols:search" ariaLabel="Search on this website" />
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
<Button
|
<Tooltip class="xs-not">
|
||||||
class="xs-not"
|
<Fragment slot="tooltip-content">
|
||||||
icon="material-symbols:sunny-outline"
|
<a href="?action-theme=dark">Dark</a>
|
||||||
ariaLabel="Switch between dark/light mode"
|
<a href="?action-theme=auto">Auto</a>
|
||||||
/>
|
<a href="?action-theme=light">Light</a>
|
||||||
|
</Fragment>
|
||||||
|
<Button
|
||||||
|
icon="material-symbols:sunny-outline"
|
||||||
|
ariaLabel="Switch between dark/light mode"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
buttons={[
|
buttons={[
|
||||||
{
|
{
|
||||||
icon: "material-symbols:text-decrease",
|
icon: "material-symbols:text-decrease",
|
||||||
ariaLabel: "Decrease text size",
|
ariaLabel: "Decrease text size",
|
||||||
|
href: "?action-fontsize-increase=1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "material-symbols:text-increase",
|
icon: "material-symbols:text-increase",
|
||||||
ariaLabel: "Increase text size",
|
ariaLabel: "Increase text size",
|
||||||
|
href: "?action-fontsize-decrease=1",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@ -70,12 +91,22 @@ const { breadcrumb } = Astro.props;
|
||||||
icon="material-symbols:currency-exchange"
|
icon="material-symbols:currency-exchange"
|
||||||
ariaLabel="Select preferred currency"
|
ariaLabel="Select preferred currency"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Tooltip class="m-not">
|
||||||
class="m-not"
|
<Fragment slot="tooltip-content">
|
||||||
icon="material-symbols:translate"
|
{
|
||||||
title="EN"
|
astroConfig.i18n?.locales.map((locale) => (
|
||||||
ariaLabel="Select preferred language"
|
<a href={`?action-lang=${locale}`}>
|
||||||
/>
|
{locale.toString().toUpperCase()}
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
<Button
|
||||||
|
icon="material-symbols:translate"
|
||||||
|
title={currentLocate.toUpperCase()}
|
||||||
|
ariaLabel="Select preferred language"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
<Button
|
<Button
|
||||||
class="m-not"
|
class="m-not"
|
||||||
icon="material-symbols:currency-exchange"
|
icon="material-symbols:currency-exchange"
|
||||||
|
@ -108,18 +139,20 @@ const { breadcrumb } = Astro.props;
|
||||||
& > a {
|
& > a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 0.2em 0.5em;
|
display: flex;
|
||||||
padding-bottom: 0.3em;
|
place-items: center;
|
||||||
border-radius: 12px;
|
gap: 0.4em;
|
||||||
|
padding: 0.4em 0.6em;
|
||||||
|
border-radius: 9999px;
|
||||||
|
|
||||||
transition: 150ms background-color;
|
transition: 150ms background-color;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-elevation-1);
|
background-color: var(--color-base-250);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: var(--color-elevation-2);
|
background-color: var(--color-base-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
@ -153,39 +186,35 @@ const { breadcrumb } = Astro.props;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style is:global>
|
& > :global(.m-only),
|
||||||
nav#topbar > #toolbar {
|
& > :global(.s-only) {
|
||||||
& > .m-only,
|
|
||||||
& > .s-only {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
& > .m-only {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .m-not {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 28rem) {
|
@media (max-width: 40rem) {
|
||||||
& > .s-only {
|
& > :global(.m-only) {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .s-not {
|
& > :global(.m-not) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 25rem) {
|
@media (max-width: 28rem) {
|
||||||
& > .xs-not {
|
& > :global(.s-only) {
|
||||||
display: none;
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :global(.s-not) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 25rem) {
|
||||||
|
& > :global(.xs-not) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,14 @@
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
id?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
icon?: string | string[];
|
icon?: string | string[];
|
||||||
class?: string;
|
class?: string;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, icon, class: className, ariaLabel } = Astro.props;
|
const { title, icon, class: className, ariaLabel, id } = Astro.props;
|
||||||
|
|
||||||
const icons =
|
const icons =
|
||||||
icon === undefined ? [] : typeof icon === "string" ? [icon] : icon;
|
icon === undefined ? [] : typeof icon === "string" ? [icon] : icon;
|
||||||
|
@ -19,10 +20,11 @@ const icons =
|
||||||
}
|
}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
id={id}
|
||||||
class:list={[{ "with-title": !!title }, className]}
|
class:list={[{ "with-title": !!title }, className]}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
>
|
>
|
||||||
{icons.map((cIcon) => <Icon name={cIcon} height={24} width={24} />)}
|
{icons.map((cIcon) => <Icon name={cIcon} />)}
|
||||||
{title}
|
{title}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -34,17 +36,17 @@ const icons =
|
||||||
button {
|
button {
|
||||||
--foreground-color: var(--color-base-650);
|
--foreground-color: var(--color-base-650);
|
||||||
color: var(--foreground-color);
|
color: var(--foreground-color);
|
||||||
border: 1px solid var(--foreground-color);
|
border: 0.1em solid var(--foreground-color);
|
||||||
background-color: var(--color-elevation-0);
|
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: 38px;
|
height: 2.5em;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
place-content: center;
|
place-content: center;
|
||||||
gap: 10px;
|
gap: 1em;
|
||||||
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
|
@ -55,27 +57,28 @@ const icons =
|
||||||
border-color;
|
border-color;
|
||||||
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
|
||||||
|
&.with-title > svg {
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
--foreground-color: var(--color-base-1000);
|
--foreground-color: var(--color-base-1000);
|
||||||
box-shadow: inset 0 1px 1px 0px var(--color-shadow-2);
|
box-shadow: inset 0 0.1em 0.1em 0 var(--color-shadow-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transition-duration: 75ms;
|
transition-duration: 75ms;
|
||||||
--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: inset 0 1px 1px 1px var(--color-shadow-2);
|
box-shadow: inset 0 0.1em 0.1em 0.1em var(--color-shadow-2);
|
||||||
|
|
||||||
& > div {
|
translate: 0 0.1em;
|
||||||
translate: 0 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.with-title {
|
|
||||||
& > div > svg {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
---
|
---
|
||||||
import Button from "components/Button.astro";
|
import Button from "components/Button.astro";
|
||||||
|
|
||||||
|
type ButtonProps = Parameters<typeof Button>[0] & {
|
||||||
|
href?: string;
|
||||||
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
buttons: Parameters<typeof Button>[0][];
|
buttons: ButtonProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { buttons } = Astro.props;
|
const { buttons } = Astro.props;
|
||||||
|
@ -13,34 +17,49 @@ const { buttons } = Astro.props;
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
{buttons.map((button) => <Button {...button} />)}
|
{
|
||||||
|
buttons.map(({ href, ...otherProps }) =>
|
||||||
|
href ? (
|
||||||
|
<a href={href}>
|
||||||
|
<Button {...otherProps} />
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<Button {...otherProps} />
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
|
||||||
<style is:global>
|
<style>
|
||||||
.button-group {
|
.button-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
|
|
||||||
& > button:first-child {
|
& > :global(*) {
|
||||||
border-top-left-radius: 9999px;
|
&:is(button),
|
||||||
border-bottom-left-radius: 9999px;
|
& > :global(button) {
|
||||||
border-right: unset;
|
border-radius: 0;
|
||||||
padding-right: 0.5em;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
& > button {
|
&:first-child:is(button),
|
||||||
border-radius: 0;
|
&:first-child > :global(button) {
|
||||||
}
|
border-top-left-radius: 9999px;
|
||||||
|
border-bottom-left-radius: 9999px;
|
||||||
|
border-right: unset;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
& > button:last-child {
|
&:last-child:is(button),
|
||||||
border-top-right-radius: 9999px;
|
&:last-child > :global(button) {
|
||||||
border-bottom-right-radius: 9999px;
|
border-top-right-radius: 9999px;
|
||||||
border-left: unset;
|
border-bottom-right-radius: 9999px;
|
||||||
padding-left: 0.5em;
|
border-left: unset;
|
||||||
|
padding-left: 0.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
import type { AstroCookies } from "astro";
|
||||||
|
import { defineMiddleware, sequence } from "astro:middleware";
|
||||||
|
import { z } from "zod";
|
||||||
|
import astroConfig from "astro.config";
|
||||||
|
|
||||||
|
const cookieThemeSchema = z.enum(["dark", "light", "auto"]);
|
||||||
|
|
||||||
|
const getAbsoluteLocaleUrl = (locale: string, url: string) =>
|
||||||
|
`/${locale}${url}`;
|
||||||
|
|
||||||
|
const redirection = (
|
||||||
|
redirectURL: string,
|
||||||
|
headers: Record<string, string> = {}
|
||||||
|
): Response => {
|
||||||
|
return new Response(undefined, {
|
||||||
|
headers: { ...headers, Location: redirectURL },
|
||||||
|
status: 302,
|
||||||
|
statusText: "Found",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const langMiddleware = defineMiddleware(
|
||||||
|
({ cookies, preferredLocale, currentLocale, url }, next) => {
|
||||||
|
const cookiePreferredLocale = getCookiePreferredLocale(cookies);
|
||||||
|
const actionLang = url.searchParams.get("action-lang");
|
||||||
|
|
||||||
|
if (!currentLocale) {
|
||||||
|
currentLocale = cookiePreferredLocale ?? preferredLocale ?? "en";
|
||||||
|
const redirectURL = getAbsoluteLocaleUrl(currentLocale, url.pathname);
|
||||||
|
return redirection(redirectURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionLang && actionLang !== cookiePreferredLocale) {
|
||||||
|
const pathnameWithoutLocale = url.pathname.substring(
|
||||||
|
currentLocale.length + 1
|
||||||
|
);
|
||||||
|
const redirectURL = getAbsoluteLocaleUrl(
|
||||||
|
actionLang,
|
||||||
|
pathnameWithoutLocale
|
||||||
|
);
|
||||||
|
return redirection(redirectURL, {
|
||||||
|
"Set-Cookie": `al_pref_languages=${JSON.stringify([
|
||||||
|
actionLang,
|
||||||
|
])}; Path=/`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookiePreferredLocale) {
|
||||||
|
if (cookiePreferredLocale !== currentLocale) {
|
||||||
|
const pathnameWithoutLocale = url.pathname.substring(
|
||||||
|
currentLocale.length + 1
|
||||||
|
);
|
||||||
|
const redirectURL = getAbsoluteLocaleUrl(
|
||||||
|
cookiePreferredLocale,
|
||||||
|
pathnameWithoutLocale
|
||||||
|
);
|
||||||
|
return redirection(redirectURL);
|
||||||
|
}
|
||||||
|
} else if (preferredLocale) {
|
||||||
|
if (preferredLocale !== currentLocale) {
|
||||||
|
const pathnameWithoutLocale = url.pathname.substring(
|
||||||
|
currentLocale.length + 1
|
||||||
|
);
|
||||||
|
const redirectURL = getAbsoluteLocaleUrl(
|
||||||
|
preferredLocale,
|
||||||
|
pathnameWithoutLocale
|
||||||
|
);
|
||||||
|
return redirection(redirectURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const headersMiddleware = defineMiddleware(
|
||||||
|
async ({ currentLocale, url }, next) => {
|
||||||
|
const actionTheme = url.searchParams.get("action-theme");
|
||||||
|
|
||||||
|
const verifiedActionTheme = cookieThemeSchema.safeParse(actionTheme);
|
||||||
|
|
||||||
|
if (verifiedActionTheme.success) {
|
||||||
|
url.searchParams.delete("action-theme");
|
||||||
|
if (verifiedActionTheme.data === "auto") {
|
||||||
|
return redirection(url.toString(), {
|
||||||
|
"Set-Cookie": `al_pref_theme=; Path=/; Expires=${new Date(0).toUTCString()}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return redirection(url.toString(), {
|
||||||
|
"Set-Cookie": `al_pref_theme=${verifiedActionTheme.data}; Path=/`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await next();
|
||||||
|
if (currentLocale) {
|
||||||
|
response.headers.set("Content-Language", currentLocale);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const onRequest = sequence(headersMiddleware, langMiddleware);
|
||||||
|
|
||||||
|
const getCookiePreferredLocale = (
|
||||||
|
cookies: AstroCookies
|
||||||
|
): string | undefined => {
|
||||||
|
const alPrefLanguages = cookies.get("al_pref_languages");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const json = alPrefLanguages?.json();
|
||||||
|
const result = z.array(z.string()).nonempty().safeParse(json);
|
||||||
|
if (result.success) {
|
||||||
|
for (const value of result.data) {
|
||||||
|
if (astroConfig.i18n?.locales.includes(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
<h1>Oh nyo...</h1>
|
|
@ -20,7 +20,7 @@ const { icon, title, href } = Astro.props;
|
||||||
a {
|
a {
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
gap: 16px;
|
gap: 1em;
|
||||||
color: var(--color-base-1000);
|
color: var(--color-base-1000);
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
|
@ -10,8 +10,6 @@ import FolderCard from "./_components/FolderCard.astro";
|
||||||
<AppLayout
|
<AppLayout
|
||||||
breadcrumb={[
|
breadcrumb={[
|
||||||
{ name: "Drakengard", slug: "drakengard" },
|
{ name: "Drakengard", slug: "drakengard" },
|
||||||
{ name: "Testing a long name", slug: "long" },
|
|
||||||
{ name: "More stuff", slug: "more" },
|
|
||||||
]}
|
]}
|
||||||
title="Drakengard"
|
title="Drakengard"
|
||||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
|
@ -2,15 +2,20 @@
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||||
import Button from "components/Button.astro";
|
import Button from "components/Button.astro";
|
||||||
import LinkCard from "./_components/LinkCard.astro";
|
import LinkCard from "../_components/LinkCard.astro";
|
||||||
import CategoryCard from "./_components/CategoryCard.astro";
|
import CategoryCard from "../_components/CategoryCard.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||||
}
|
}
|
||||||
|
|
||||||
<AppLayout title="Accord’s Library" illustration="/img/bg-home.webp">
|
<AppLayout
|
||||||
|
title="Accord’s Library"
|
||||||
|
illustration="/img/bg-home.webp"
|
||||||
|
illustrationSize="100vh"
|
||||||
|
illustrationPosition="20%"
|
||||||
|
>
|
||||||
<div id="title" slot="header-title">
|
<div id="title" slot="header-title">
|
||||||
<Icon name="accords" />
|
<Icon name="accords" />
|
||||||
<div>
|
<div>
|
||||||
|
@ -23,13 +28,12 @@ import CategoryCard from "./_components/CategoryCard.astro";
|
||||||
We aim at archiving and translating all of <strong>Yoko Taro</strong>’s
|
We aim at archiving and translating all of <strong>Yoko Taro</strong>’s
|
||||||
works.<br />Yoko Taro is a Japanese video game director and scenario
|
works.<br />Yoko Taro is a Japanese video game director and scenario
|
||||||
writer. He is best-known for his involvement with the <strong>NieR</strong
|
writer. He is best-known for his involvement with the <strong>NieR</strong
|
||||||
> and <strong>Drakengard</strong> series. To complement his games, Yoko
|
> and <strong>Drakengard</strong> series. To complement his games, Yoko Taro
|
||||||
Taro likes to publish side materials in the form of books, anime, manga,
|
likes to publish side materials in the form of books, anime, manga, audio books,
|
||||||
audio books, novellas, even theater plays.<br />These media can be very
|
novellas, even theater plays.<br />These media can be very difficult to
|
||||||
difficult to find. His work goes all the way back to 2003. Most of it was
|
find. His work goes all the way back to 2003. Most of it was released
|
||||||
released solely in Japanese, and sometimes in short supply. So this is
|
solely in Japanese, and sometimes in short supply. So this is what we do
|
||||||
what we do here: <strong>discover, archive, translate, and analyze</strong
|
here: <strong>discover, archive, translate, and analyze</strong>.
|
||||||
>.
|
|
||||||
</p>
|
</p>
|
||||||
<Button title="Read more about us" icon="material-symbols:left-click" />
|
<Button title="Read more about us" icon="material-symbols:left-click" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -195,8 +199,8 @@ import CategoryCard from "./_components/CategoryCard.astro";
|
||||||
with our
|
with our
|
||||||
<strong>community</strong>? Are you interested in <strong
|
<strong>community</strong>? Are you interested in <strong
|
||||||
>contributing</strong
|
>contributing</strong
|
||||||
> to this project? Whatever it is, you should find what you are
|
> to this project? Whatever it is, you should find what you are looking
|
||||||
looking for at the following links.
|
for at the following links.
|
||||||
</p>
|
</p>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<LinkCard
|
<LinkCard
|
||||||
|
@ -239,6 +243,7 @@ import CategoryCard from "./_components/CategoryCard.astro";
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
margin-bottom: 128px;
|
||||||
|
|
||||||
& > p {
|
& > p {
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
@ -257,7 +262,7 @@ import CategoryCard from "./_components/CategoryCard.astro";
|
||||||
#title {
|
#title {
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
gap: 16px;
|
gap: 1em;
|
||||||
|
|
||||||
& > svg {
|
& > svg {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
|
@ -314,6 +319,7 @@ import CategoryCard from "./_components/CategoryCard.astro";
|
||||||
#main {
|
#main {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 64px;
|
gap: 64px;
|
||||||
|
margin-top: -96px;
|
||||||
|
|
||||||
& > section {
|
& > section {
|
||||||
& > h2 {
|
& > h2 {
|
||||||
|
@ -342,9 +348,9 @@ import CategoryCard from "./_components/CategoryCard.astro";
|
||||||
|
|
||||||
& > .grid {
|
& > .grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
|
||||||
column-gap: clamp(6px, 2vmin, 16px);
|
column-gap: clamp(6px, 2vmin, 1em);
|
||||||
row-gap: clamp(6px + 6px, 2vmin + 6px, 16px);
|
row-gap: clamp(6px + 6px, 2vmin + 6px, 1em);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,6 +18,26 @@ const { img, name, href } = Astro.props;
|
||||||
"dark-image": `url(${img?.dark})`,
|
"dark-image": `url(${img?.dark})`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
:global(html):not(.manual-theme) {
|
||||||
|
a > div {
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
background-image: var(--light-image);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
background-image: var(--dark-image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html).light-theme a > div {
|
||||||
|
background-image: var(--light-image);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html).dark-theme a > div {
|
||||||
|
background-image: var(--dark-image);
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
@ -32,14 +52,6 @@ const { img, name, href } = Astro.props;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
background-image: var(--light-image);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
background-image: var(--dark-image);
|
|
||||||
}
|
|
||||||
|
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
|
@ -22,16 +22,16 @@ const { icon, subtitle, title, href } = Astro.props;
|
||||||
a {
|
a {
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
gap: 16px;
|
gap: 1em;
|
||||||
color: var(--color-base-1000);
|
color: var(--color-base-1000);
|
||||||
padding: 24px;
|
padding: 1.5em;
|
||||||
padding-top: 12px;
|
padding-top: 0.75em;
|
||||||
border-radius: 12px;
|
border-radius: 0.75em;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
& > svg {
|
& > svg {
|
||||||
width: clamp(24px, 6vw + 14px, 48px);
|
width: clamp(1.5em, 6vw + 0.8em, 3em);
|
||||||
height: clamp(24px, 6vw + 14px, 48px);
|
height: clamp(1.5em, 6vw + 0.8em, 3em);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > #right {
|
& > #right {
|
||||||
|
@ -41,7 +41,7 @@ const { icon, subtitle, title, href } = Astro.props;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
|
||||||
& > h3 {
|
& > h3 {
|
||||||
font-size: 24px;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > p {
|
& > p {
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { class: className } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<tippy-tooltip class={className}>
|
||||||
|
<template><slot name="tooltip-content" /></template>
|
||||||
|
<slot />
|
||||||
|
</tippy-tooltip>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import tippy from "tippy.js";
|
||||||
|
|
||||||
|
class TippyTooltip extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
tippy(this, {
|
||||||
|
allowHTML: true,
|
||||||
|
content: (ref) =>
|
||||||
|
ref.querySelector(":scope > template")?.innerHTML ?? "",
|
||||||
|
interactive: true,
|
||||||
|
trigger: "click",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("tippy-tooltip", TippyTooltip);
|
||||||
|
</script>
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { expect, test } from "bun:test";
|
||||||
|
|
||||||
|
const cases: [string, string, string[], string][] = [
|
||||||
|
["", "", [], "/en/"],
|
||||||
|
["", "", ["fr"], "/fr/"],
|
||||||
|
["", "", ["en"], "/en/"],
|
||||||
|
["", "", ["en", "fr"], "/en/"],
|
||||||
|
["", "en", [], "/en/"],
|
||||||
|
["", "fr", [], "/fr/"],
|
||||||
|
["", "fr", ["en"], "/en/"],
|
||||||
|
["", "fr", ["en", "fr"], "/en/"],
|
||||||
|
["", "fr,en", ["en", "fr"], "/en/"],
|
||||||
|
];
|
||||||
|
|
||||||
|
test.each(cases)(
|
||||||
|
"Fetching url with prefix %p, with Accept-Language header %p, with cookie al_pref_languages %p, should redirect the user to %p",
|
||||||
|
async (urlPrefix, acceptLanguage, cookie, expectedRedirection) => {
|
||||||
|
const response = await fetch(`http://localhost:12498${urlPrefix}`, {
|
||||||
|
redirect: "manual",
|
||||||
|
headers: {
|
||||||
|
...(acceptLanguage ? { "Accept-Language": acceptLanguage } : {}),
|
||||||
|
...(cookie.length > 0
|
||||||
|
? { Cookie: `al_pref_languages=${JSON.stringify(cookie)}` }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(302);
|
||||||
|
expect(response.headers.get("Location")).toBe(expectedRedirection);
|
||||||
|
}
|
||||||
|
);
|
Loading…
Reference in New Issue