Improve everything

This commit is contained in:
DrMint 2023-09-21 21:32:41 +02:00
parent 421dc1e614
commit cd69584b66
29 changed files with 1330 additions and 620 deletions

View File

@ -1,4 +1,5 @@
{
"editor.rulers": [100],
"editor.tabSize": 2
"editor.tabSize": 2,
"typescript.preferences.importModuleSpecifier": "non-relative"
}

2
TODO.md Normal file
View File

@ -0,0 +1,2 @@
- [ ] Check why I'm getting NS_BINDING_ABORTED on fonts when fetched in the network browser dev tool tab
- [ ] Add a background when opening menus in mobile mode

BIN
bun.lockb

Binary file not shown.

View File

@ -1,28 +0,0 @@
/*
original light mode
color: #fff1e0;
color: #ffedd8;
color: #f0d1b3;
color: #c0845e;
color: #9c6644;
color: #1b1811; */
/*
original dark mode
color: #191914;
color: #26221e;
color: #2c2803;
color: #392d22;
color: #c0845e;
color: #ebeae7; */
/* new version using https://leonardocolor.io/theme.html */
:where(button) {
background-color: inherit;
color: inherit;
border: initial;
padding: initial;
margin: initial;
cursor: pointer;
}

88
public/css/tippy.css Normal file
View File

@ -0,0 +1,88 @@
.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-1);
color: var(--color-base-1000);
border-radius: 0.5rem;
box-shadow: 0 20px 25px -5px var(--color-shadow),
0 0 10px -6px var(--color-shadow);
transition-property: transform, visibility, opacity;
}
.tippy-box[data-placement^="top"] > .tippy-arrow {
bottom: 0;
}
.tippy-box[data-placement^="top"] > .tippy-arrow:before {
bottom: -7px;
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: -7px;
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: -7px;
transform-origin: center left;
}
.tippy-box[data-placement^="right"] > .tippy-arrow {
left: 0;
}
.tippy-box[data-placement^="right"] > .tippy-arrow:before {
left: -7px;
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: 16px;
height: 16px;
color: var(--color-elevation-1);
}
.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;
}

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow: /

View File

@ -1,20 +0,0 @@
---
import { Icon } from "astro-icon/components";
import { getLocalizedUrl } from "utils/urls";
interface Props {
href: string;
isActive?: boolean;
text?: string;
icon?: string;
}
const { locale } = Astro.params;
const { href, text, icon, isActive } = Astro.props;
---
<a href={getLocalizedUrl(href, locale)} class:list={{ active: isActive }}>
{text && text}
{icon && <Icon name={icon} />}
</a>
<style></style>

View File

@ -1,262 +0,0 @@
---
import { Icon } from "astro-icon/components";
import { getLocalizedUrl } from "utils/urls";
import { CookieNames } from "utils/cookies";
import NavOption from "./NavOption.astro";
const { locale = "en" } = Astro.params;
const isReduced =
Astro.cookies.get(CookieNames.MENU_PANEL_REDUCED)?.boolean() ?? false;
const themeColors =
Astro.cookies.get(CookieNames.THEME_COLOR)?.value ?? "theme-color-light";
---
<div id="component" class:list={{ reduced: isReduced }}>
<a
id="accords-logo"
data-turbo-confirm="Do you want to leave this page?"
href={getLocalizedUrl("/", locale)}
>
<Icon name="accords" />
</a>
<p id="title">Accords Library</p>
<button id="reduce-toggle">
<Icon name="material-symbols:chevron-left-rounded" width={24} height={24} />
</button>
<button
class={themeColors === "theme-color-dark" ? "dark" : "light"}
id="theme-toggle"
>
<Icon class="when-light" name="material-symbols:light-mode" />
<Icon class="when-dark" name="material-symbols:dark-mode" />
</button>
<NavOption
href="/library"
icon="material-symbols:auto-stories"
title="Library"
subtitle="Browse all physical and digital media"
/>
<NavOption
href="/contents"
icon="material-symbols:workspaces"
title="Content"
subtitle="Explore all content and filter by type or category"
/>
<NavOption
href="/wiki"
icon="material-symbols:travel-explore"
title="Wiki"
subtitle="An encyclopedia for everything related to DrakeNieR"
/>
<NavOption
href="/chronicles"
icon="material-symbols:schedule"
title="Chronicles"
subtitle="Experience all events and content in chronological order"
/>
<NavOption href="/news" icon="material-symbols:newspaper" title="News" />
<NavOption
href="https://gallery.accords-library.com/posts/"
icon="material-symbols:perm-media"
title="Gallery"
/>
<NavOption href="/archives" icon="material-symbols:save" title="Archives" />
<NavOption href="/about-us" icon="material-symbols:info" title="About us" />
<p>
This websites content is made available under <a
href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA</a
> unless otherwise noted.
</p>
<div id="common-creative">
<Icon name="creative-commons-brands" />
<Icon name="creative-commons-by-brands" />
<Icon name="creative-commons-sa-brands" />
</div>
<p>
Accords Library is not affiliated with or endorsed by SQUARE ENIX CO. LTD.
All game assets and promotional materials belongs to © SQUARE ENIX CO. LTD.
</p>
<div id="social-links">
<Icon name="github-brands" />
<Icon name="x-brands" />
<Icon name="discord-brands" />
</div>
</div>
<script>
import { CookieNames } from "utils/cookies";
import { observableWithPersistence } from "utils/micro-observables";
import { Elementos } from "utils/Elementos";
import { onLoad } from "utils/turbo";
onLoad(() => {
const component = new Elementos("#component");
const reduceToggleButton = new Elementos("#reduce-toggle");
const isReduced = observableWithPersistence(
CookieNames.MENU_PANEL_REDUCED,
false
);
reduceToggleButton.onClick(() => {
isReduced.update((oldValue) => !oldValue);
Turbo.cache.clear();
});
component.setClass("reduced", isReduced);
// theme-toggle
const body = new Elementos("body");
const themeToggleButton = new Elementos("#theme-toggle");
const themeColor = observableWithPersistence(
CookieNames.THEME_COLOR,
"theme-color-light"
);
const isDarkMode = themeColor.select(
(value) => value === "theme-color-dark"
);
themeToggleButton.onClick(() => {
themeColor.update((oldValue) =>
oldValue === "theme-color-light"
? "theme-color-dark"
: "theme-color-light"
);
Turbo.cache.clear();
});
themeToggleButton.setClass(
{ ifFalse: "light", ifTrue: "dark" },
isDarkMode
);
body.setClass(
{ ifFalse: "theme-color-light", ifTrue: "theme-color-dark" },
isDarkMode
);
});
</script>
<style>
#component {
--reduced-transition-duration: 5s;
display: grid;
padding: 2rem;
gap: 1rem;
container-type: inline-size;
@media (width >= 60rem) {
transition:
var(--reduced-transition-duration) width,
var(--reduced-transition-duration) padding;
position: relative;
width: 20rem;
flex-shrink: 0;
border-right: var(--border-style);
& > #title {
font-family: var(--font-headers);
font-size: 1.875rem;
margin: 0;
}
& > p {
transition:
var(--reduced-transition-duration) font-size,
var(--reduced-transition-duration) opacity;
}
& > #common-creative,
& > #social-links {
transition:
var(--reduced-transition-duration) transform,
var(--reduced-transition-duration) opacity;
}
&.reduced {
width: 6rem;
padding: 2rem 1rem;
& > p,
& > #title {
font-size: 0;
opacity: 0;
}
& > #common-creative,
& > #social-links {
transform: scale(0);
opacity: 0;
}
}
}
& > p {
text-align: center;
}
& > #accords-logo {
place-self: center;
width: max(50%, 3rem);
aspect-ratio: 1/1;
color: var(--color-base-1000);
transition: 0.1s color;
&:hover {
color: var(--color-base-600);
}
& > svg {
width: 100%;
height: 100%;
}
}
& > #theme-toggle.dark > .when-light,
& > #theme-toggle.light > .when-dark {
display: none;
}
& > #common-creative {
display: grid;
grid-auto-flow: column;
place-content: center;
gap: 0.25rem;
& > svg {
width: 24px;
height: 24px;
}
}
& > #social-links {
display: grid;
grid-auto-flow: column;
place-items: center;
& > svg {
width: 40px;
height: 40px;
}
}
}
</style>

View File

@ -1,75 +0,0 @@
---
import { Icon } from "astro-icon/components";
interface Props {
title: string;
showSubPanel: boolean;
}
const { title, showSubPanel } = Astro.props;
---
<div id="component">
<button
id="toggle-menu-panel"
_="on click
toggle .opened on #menu-panel
remove .opened from #sub-panel
remove .on from #toggle-sub-panel
toggle .on on me"
>
<Icon
class="when-off"
name="material-symbols:menu-rounded"
width={24}
height={24}
/>
<Icon
class="when-on"
name="material-symbols:close-rounded"
width={24}
height={24}
/>
</button>
<p>{title}</p>
<div>
{
showSubPanel && (
<button
id="toggle-sub-panel"
_="on click
toggle .opened on #sub-panel
remove .opened from #menu-panel
remove .on from #toggle-menu-panel
toggle .on on me"
>
<Icon
class="when-off"
name="material-symbols:tune-rounded"
width={24}
height={24}
/>
<Icon
class="when-on"
name="material-symbols:close-rounded"
width={24}
height={24}
/>
</button>
)
}
</div>
</div>
<style>
#component {
border-top: var(--border-style);
display: grid;
grid-template-columns: 5rem 1fr 5rem;
place-items: center;
}
.on > .when-off,
:not(.on) > .when-on {
display: none;
}
</style>

2
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
/// <reference types="astro/client" />
/// <reference path="../.astro/types.d.ts" />

View File

@ -1,159 +0,0 @@
---
import { CookieNames } from "utils/cookies";
interface Props {
title: string;
}
const { title } = Astro.props;
const themeColors =
Astro.cookies.get(CookieNames.THEME_COLOR)?.value ?? "theme-color-light";
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
<script is:inline src="/js/turbo.es2017-umd.js" async defer></script>
<link href="/css/sanitize.min.css" rel="stylesheet" />
<link href="/css/global.css" rel="stylesheet" />
</head>
<body class={themeColors} hx-ext="head-support" hx-boost="true">
<slot />
</body>
</html>
<script>
import tippy from "tippy.js";
import "tippy.js/dist/tippy.css";
tippy("[data-tippy-content]", { allowHTML: true });
</script>
<style is:global>
.theme-color-light {
--color-base-0: #ffffff;
--color-base-50: #fffaf3;
--color-base-100: #fff4e6;
--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-shadow: var(--color-base-500);
--texture-dots: url(/images/paper-dots.webp);
--texture-dots-blend: multiply;
}
.theme-color-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-150: #27231e;
--color-base-100: #1c1b16;
--color-base-50: #11110d;
--color-base-0: #000000;
--color-shadow: var(--color-base-0);
--texture-dots: url(/images/paper-dots-dark.webp);
--texture-dots-blend: overlay;
}
.texture-dots {
background-size: 10cm;
background-attachment: local;
background-image: var(--texture-dots);
background-blend-mode: var(--texture-dots-blend);
background-repeat: repeat;
}
body {
background-color: var(--color-base-150);
color: var(--color-base-1000);
}
.turbo-progress-bar {
height: 5px;
background-color: #b07751;
}
@font-face {
font-family: "Vollkorn";
src: url("/fonts/Vollkorn-Bold.woff2") format("woff2");
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: "Noto Sans";
src: url("/fonts/NotoSans-Medium.woff2") format("woff2");
font-weight: medium;
font-style: normal;
}
@font-face {
font-family: "Noto Sans";
src: url("/fonts/NotoSans-Bold.woff2") format("woff2");
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: "Angelic Agrippa";
src: url("/fonts/AngelicAgrippa-Regular.woff2") format("woff2");
font-weight: normal;
font-style: normal;
}
:root {
--font-body: "Noto Sans", sans-serif;
--font-headers: "Vollkorn", serif;
--font-angelic: "Angelic Agrippa", serif;
}
p {
font-family: var(--font-body);
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: var(--font-headers);
}
</style>

View File

@ -1,7 +1,11 @@
---
import AppLayout from "layouts/AppLayout.astro";
import AppLayout from "pages/_components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="Archive">
<div slot="subPanel">
Besides physical medias, we also archive digital contents such as websites,

View File

@ -0,0 +1,15 @@
---
import AppLayout from "pages/_components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="Library">
<main slot="mainPanel"><h1>Contents</h1></main>
<div slot="subPanel">
All the contents (textual, audio, and video) from the Library or other
online sources.
</div>
</AppLayout>

View File

@ -1,8 +1,12 @@
---
import { Icon } from "astro-icon/components";
import AppLayout from "layouts/AppLayout.astro";
import AppLayout from "pages/_components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="Accords Library">
<main slot="mainPanel">
<div id="header">
@ -46,6 +50,10 @@ import AppLayout from "layouts/AppLayout.astro";
</main>
</AppLayout>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
main {
padding-left: 2.5rem;

View File

@ -1,7 +1,11 @@
---
import AppLayout from "layouts/AppLayout.astro";
import AppLayout from "pages/_components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="Library">
<main slot="mainPanel"><h1>Library</h1></main>
<div slot="subPanel">

View File

@ -1,7 +1,10 @@
---
import Html from "layouts/Html.astro";
import Navbar from "components/Navbar.astro";
import MenuPanel from "components/MenuPanel.astro";
import Html from "pages/_components/Html.astro";
import MenuPanel from "./components/MenuPanel.astro";
import Navbar from "./components/Navbar.astro";
import MenuPanelReduced from "./components/MenuPanelReduced.astro";
import { parseCookie } from "utils/astro";
import { CookieNames } from "utils/cookies";
interface Props {
title: string;
@ -11,15 +14,32 @@ const { title } = Astro.props;
const turnSubPanelIntoMainPanel =
Astro.slots.has("subPanel") && !Astro.slots.has("mainPanel");
const isMenuPanelReduced = parseCookie<boolean>(
Astro.cookies.get(CookieNames.MENU_PANEL_REDUCED),
false
);
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<Html title={title}>
<div
id="panels"
class:list={{ "turn-sub-into-main": turnSubPanelIntoMainPanel }}
>
<div id="menu-panel" class="texture-dots">
<MenuPanel />
<div
id="menu-panel"
class:list={["texture-dots", { reduced: isMenuPanelReduced }]}
>
<div class="when-reduced">
<MenuPanelReduced />
</div>
<div class="when-not-reduced">
<MenuPanel />
</div>
</div>
<div id="sub-panel" class="texture-dots">
@ -39,6 +59,10 @@ const turnSubPanelIntoMainPanel =
</div>
</Html>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
body {
overflow: hidden;
@ -51,39 +75,29 @@ const turnSubPanelIntoMainPanel =
grid-template-rows: 1fr 5rem;
}
& > #navbar {
display: grid;
@media (width >= 60rem) {
display: none;
}
}
& > #panels {
display: flex;
position: relative;
@media (width >= 60rem) {
position: absolute;
inset: 0;
}
@media (width < 60rem) {
&.turn-sub-into-main > #sub-panel {
inset: 0;
z-index: 0;
z-index: initial;
transition: initial;
border: initial;
width: 100%;
}
}
@media (width >= 60rem) {
position: absolute;
inset: 0;
display: flex;
}
& > #sub-panel {
@media (width < 60rem) {
right: -100%;
top: 0;
bottom: 0;
transition: 0.2s right;
z-index: 1;
border-left: var(--border-style);
&.opened {
@ -92,48 +106,99 @@ const turnSubPanelIntoMainPanel =
}
@media (width >= 60rem) {
width: 20rem;
flex-shrink: 0;
border-right: var(--border-style);
}
}
& > #menu-panel {
border-right: var(--border-style);
background-position: top right;
flex-shrink: 0;
overflow-y: auto;
@media (width < 60rem) {
left: -100%;
top: 0;
bottom: 0;
transition: 0.2s left;
z-index: 1;
border-right: var(--border-style);
&.opened {
left: 0;
}
& > .when-reduced {
display: none;
}
}
@media (width >=60rem) {
transition: 0.2s width;
&.reduced {
width: auto;
padding: 1rem;
}
&.reduced > .when-not-reduced,
&:not(.reduced) > .when-reduced {
display: none;
}
}
& > div {
display: grid;
align-content: start;
justify-items: center;
gap: 1rem;
}
}
& > #main-panel {
width: 100%;
@media (width < 60rem) {
position: absolute;
inset: 0;
}
}
& > #menu-panel,
& > #sub-panel {
width: 20rem;
flex-shrink: 0;
overflow-x: hidden;
@media (width < 60rem) {
z-index: 1;
position: absolute;
top: 0;
bottom: 0;
width: min(30rem, 90%);
@media (width < 20rem) {
width: 100%;
}
}
}
& > div {
display: grid;
background-color: var(--color-base-150);
transition: 0.1s background-color;
justify-content: center;
overflow-y: auto;
padding: 2rem;
&:empty {
display: none;
}
}
}
@media (width < 60rem) {
position: absolute;
width: min(30rem, 90%);
}
& > #navbar {
display: grid;
place-items: center;
border-top: var(--border-style);
grid-template-columns: 5rem 1fr 5rem;
@media (width >= 60rem) {
display: none;
}
}
}

View File

@ -0,0 +1,162 @@
---
import { Icon } from "astro-icon/components";
import { getLocalizedUrl } from "utils/urls";
import NavOption from "pages/_components/NavOption.astro";
import HorizontalLine from "pages/_components/HorizontalLine.astro";
import ReduceToggleButton from "./ReduceToggleButton.astro";
import ThemeToggleButton from "./ThemeToggleButton.astro";
const { locale = "en" } = Astro.params;
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<a
id="accords-logo"
data-turbo-confirm="Do you want to leave this page?"
href={getLocalizedUrl("/", locale)}
>
<Icon name="accords" />
</a>
<p id="title">Accords Library</p>
<div id="buttons">
<ReduceToggleButton icon="material-symbols:chevron-left-rounded" />
<ThemeToggleButton />
</div>
<HorizontalLine />
<NavOption
href="/library"
icon="material-symbols:auto-stories"
title="Library"
subtitle="Browse all physical and digital media"
/>
<NavOption
href="/contents"
icon="material-symbols:workspaces"
title="Content"
subtitle="Explore all content and filter by type or category"
/>
<NavOption
href="/wiki"
icon="material-symbols:travel-explore"
title="Wiki"
subtitle="An encyclopedia for everything related to DrakeNieR"
/>
<NavOption
href="/chronicles"
icon="material-symbols:schedule"
title="Chronicles"
subtitle="Experience all events and content in chronological order"
/>
<HorizontalLine />
<NavOption href="/news" icon="material-symbols:newspaper" title="News" />
<NavOption
href="https://gallery.accords-library.com/posts/"
icon="material-symbols:perm-media"
title="Gallery"
/>
<NavOption href="/archives" icon="material-symbols:save" title="Archives" />
<NavOption href="/about-us" icon="material-symbols:info" title="About us" />
<HorizontalLine />
<p>
This websites content is made available under <a
href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA</a
> unless otherwise noted.
</p>
<div id="common-creative">
<Icon name="creative-commons-brands" />
<Icon name="creative-commons-by-brands" />
<Icon name="creative-commons-sa-brands" />
</div>
<p>
Accords Library is not affiliated with or endorsed by SQUARE ENIX CO. LTD.
All game assets and promotional materials belongs to © SQUARE ENIX CO. LTD.
</p>
<div id="social-links">
<Icon name="github-brands" />
<Icon name="x-brands" />
<Icon name="discord-brands" />
</div>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
#title {
font-family: var(--font-headers);
font-size: 1.875rem;
}
p {
text-align: center;
}
#accords-logo {
width: max(50%, 3rem);
aspect-ratio: 1/1;
color: var(--color-base-1000);
&:hover {
color: var(--color-base-600);
}
& > svg {
width: 100%;
height: 100%;
}
}
#buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
#common-creative {
display: grid;
grid-auto-flow: column;
place-content: center;
gap: 0.25rem;
& > svg {
width: 24px;
height: 24px;
}
}
p {
text-align: center;
}
#social-links {
display: grid;
grid-auto-flow: column;
place-items: center;
gap: 2rem;
& > svg {
width: 40px;
height: 40px;
}
}
</style>

View File

@ -0,0 +1,159 @@
---
import { Icon } from "astro-icon/components";
import { getLocalizedUrl } from "utils/urls";
import { CookieNames } from "utils/cookies";
import NavOptionIcon from "pages/_components/NavOptionIcon.astro";
import { parseCookie } from "utils/astro";
import HorizontalLine from "pages/_components/HorizontalLine.astro";
import ReduceToggleButton from "./ReduceToggleButton.astro";
const { locale = "en" } = Astro.params;
const themeColors = parseCookie<string>(
Astro.cookies.get(CookieNames.THEME_COLOR),
"theme-color-light"
);
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<a
id="accords-logo"
data-turbo-confirm="Do you want to leave this page?"
href={getLocalizedUrl("/", locale)}
>
<Icon name="accords" />
</a>
<div id="buttons">
<ReduceToggleButton icon="material-symbols:chevron-right-rounded" />
<button
class={themeColors === "theme-color-dark" ? "dark" : "light"}
id="theme-toggle"
>
<Icon
class="when-light"
name="material-symbols:light-mode"
width={24}
height={24}
/>
<Icon
class="when-dark"
name="material-symbols:dark-mode"
width={24}
height={24}
/>
</button>
</div>
<HorizontalLine />
<NavOptionIcon
href="/library"
icon="material-symbols:auto-stories"
title="Library"
subtitle="Browse all physical and digital media"
/>
<NavOptionIcon
href="/contents"
icon="material-symbols:workspaces"
title="Content"
subtitle="Explore all content and filter by type or category"
/>
<NavOptionIcon
href="/wiki"
icon="material-symbols:travel-explore"
title="Wiki"
subtitle="An encyclopedia for everything related to DrakeNieR"
/>
<NavOptionIcon
href="/chronicles"
icon="material-symbols:schedule"
title="Chronicles"
subtitle="Experience all events and content in chronological order"
/>
<HorizontalLine />
<NavOptionIcon href="/news" icon="material-symbols:newspaper" title="News" />
<NavOptionIcon
href="https://gallery.accords-library.com/posts/"
icon="material-symbols:perm-media"
title="Gallery"
/>
<NavOptionIcon href="/archives" icon="material-symbols:save" title="Archives" />
<NavOptionIcon href="/about-us" icon="material-symbols:info" title="About us" />
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
#accords-logo {
width: 3rem;
aspect-ratio: 1/1;
color: var(--color-base-1000);
&:hover {
color: var(--color-base-600);
}
& > svg {
width: 100%;
height: 100%;
}
}
#buttons {
display: flex;
flex-direction: column;
gap: 0.5rem;
flex-wrap: wrap;
& > #theme-toggle.dark > .when-light,
& > #theme-toggle.light > .when-dark {
display: none;
}
}
</style>
{
/* -------------------------------------------- JS -------------------------------------------- */
}
<script>
import tippy, { createSingleton } from "tippy.js";
import { onLoad } from "utils/turbo";
onLoad(() => {
createSingleton(
tippy(".menu-panel-content > [data-tippy-template]", {
content: (reference) => {
const templateQuery = reference.getAttribute("data-tippy-template")!;
const template = reference.querySelector(templateQuery)!;
const container = document.createElement("div");
container.append(template.cloneNode(true));
container.style.fontSize = "90%";
return container;
},
}),
{
allowHTML: true,
placement: "right",
delay: 250,
moveTransition: "transform 0.2s ease",
maxWidth: "18rem",
inertia: true,
}
);
});
</script>

View File

@ -0,0 +1,93 @@
---
import { Icon } from "astro-icon/components";
interface Props {
title: string;
showSubPanel: boolean;
}
const { title, showSubPanel } = Astro.props;
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<button id="toggle-menu-panel">
<Icon
class="when-off"
name="material-symbols:menu-rounded"
width={24}
height={24}
/>
<Icon
class="when-on"
name="material-symbols:close-rounded"
width={24}
height={24}
/>
</button>
<p>{title}</p>
<div>
{
showSubPanel && (
<button id="toggle-sub-panel">
<Icon
class="when-off"
name="material-symbols:tune-rounded"
width={24}
height={24}
/>
<Icon
class="when-on"
name="material-symbols:close-rounded"
width={24}
height={24}
/>
</button>
)
}
</div>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
.on > .when-off,
:not(.on) > .when-on {
display: none;
}
</style>
{
/* -------------------------------------------- JS -------------------------------------------- */
}
<script>
import { Elementos } from "utils/Elementos";
import { observable } from "utils/micro-observables";
import { onLoad } from "utils/turbo";
onLoad(() => {
const isMenuPanelOpened = observable(false);
const menuPanel = new Elementos("#menu-panel");
const menuPanelToggle = new Elementos("#toggle-menu-panel");
menuPanelToggle.onClick(() => {
isMenuPanelOpened.update((current) => !current);
isSubPanelOpened.set(false);
});
menuPanel.setClass("opened", isMenuPanelOpened);
menuPanelToggle.setClass("on", isMenuPanelOpened);
const isSubPanelOpened = observable(false);
const subPanel = new Elementos("#sub-panel");
const subPanelToggle = new Elementos("#toggle-sub-panel");
subPanelToggle.onClick(() => {
isSubPanelOpened.update((current) => !current);
isMenuPanelOpened.set(false);
});
subPanel.setClass("opened", isSubPanelOpened);
subPanelToggle.setClass("on", isSubPanelOpened);
});
</script>

View File

@ -0,0 +1,45 @@
---
import { Icon } from "astro-icon/components";
interface Props {
icon: string;
}
const { icon } = Astro.props;
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<button id="reduce-toggle">
<Icon name={icon} width={24} height={24} />
</button>
{
/* -------------------------------------------- JS -------------------------------------------- */
}
<script>
import { Elementos } from "utils/Elementos";
import { CookieNames } from "utils/cookies";
import { observableWithPersistence } from "utils/micro-observables";
import { onLoad } from "utils/turbo";
onLoad(() => {
const toggleReducedButton = new Elementos("#reduce-toggle");
const menuPanel = new Elementos("#menu-panel");
const isReduced = observableWithPersistence(
CookieNames.MENU_PANEL_REDUCED,
false
);
toggleReducedButton.onClick(() => {
isReduced.update((oldValue) => !oldValue);
Turbo.cache.clear();
});
menuPanel.setClass("reduced", isReduced);
});
</script>

View File

@ -0,0 +1,87 @@
---
import { Icon } from "astro-icon/components";
import { parseCookie } from "utils/astro";
import { CookieNames } from "utils/cookies";
const themeColors = parseCookie<string>(
Astro.cookies.get(CookieNames.THEME_COLOR),
"theme-color-light"
);
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<button
class={themeColors === "theme-color-dark" ? "dark" : "light"}
id="theme-toggle"
>
<Icon
class="when-light"
name="material-symbols:light-mode"
width={24}
height={24}
/>
<Icon
class="when-dark"
name="material-symbols:dark-mode"
width={24}
height={24}
/>
</button>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
#theme-toggle.dark > .when-light,
#theme-toggle.light > .when-dark {
display: none;
}
</style>
{
/* -------------------------------------------- JS -------------------------------------------- */
}
<script>
import { Elementos } from "utils/Elementos";
import { CookieNames } from "utils/cookies";
import { observableWithPersistence } from "utils/micro-observables";
import { onLoad } from "utils/turbo";
onLoad(() => {
const body = new Elementos("body");
const themeToggleButton = new Elementos("#theme-toggle");
const themeColor = observableWithPersistence(
CookieNames.THEME_COLOR,
"theme-color-light"
);
const isDarkMode = themeColor.select(
(value) => value === "theme-color-dark"
);
themeToggleButton.onClick(() => {
themeColor.update((oldValue) =>
oldValue === "theme-color-light"
? "theme-color-dark"
: "theme-color-light"
);
Turbo.cache.clear();
});
themeToggleButton.setClass(
{ ifFalse: "light", ifTrue: "dark" },
isDarkMode
);
body.setClass(
{ ifFalse: "theme-color-light", ifTrue: "theme-color-dark" },
isDarkMode
);
});
</script>

View File

@ -0,0 +1,17 @@
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<hr />
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
hr {
width: 100%;
border: none;
border-top: 3px dotted var(--color-base-1000);
}
</style>

View File

@ -0,0 +1,388 @@
---
import { parseCookie } from "utils/astro";
import { CookieNames } from "utils/cookies";
interface Props {
title: string;
}
const { title } = Astro.props;
const themeColors = parseCookie(
Astro.cookies.get(CookieNames.THEME_COLOR),
"theme-color-light"
);
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
<script is:inline src="/js/turbo.es2017-umd.js" async defer></script>
<link href="/css/sanitize.min.css" rel="stylesheet" />
<link href="/css/tippy.css" rel="stylesheet" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
</head>
<body class={themeColors}>
<slot />
</body>
</html>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style is:global>
/* RESET */
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
main,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
body {
line-height: 1;
}
menu,
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
:where(button) {
background-color: inherit;
color: inherit;
border: initial;
padding: initial;
margin: initial;
cursor: pointer;
}
/* THEMING */
.theme-color-light {
--color-base-0: #ffffff;
--color-base-50: #fffaf3;
--color-base-100: #fff4e6;
--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-1: var(--color-base-100);
--color-elevation-0: var(--color-base-150);
--color-shadow: var(--color-base-500);
--texture-dots: url(/images/paper-dots.webp);
--texture-dots-blend: multiply;
}
.theme-color-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-150: #27231e;
--color-base-100: #1c1b16;
--color-base-50: #11110d;
--color-base-0: #000000;
--color-elevation-1: var(--color-base-200);
--color-elevation-0: var(--color-base-150);
--color-shadow: var(--color-base-50);
--texture-dots: url(/images/paper-dots-dark.webp);
--texture-dots-blend: overlay;
}
/* FONTS */
@font-face {
font-family: "Vollkorn";
src: url("/fonts/Vollkorn-Bold.woff2") format("woff2");
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Noto Sans";
src: url("/fonts/NotoSans-Medium.woff2") format("woff2");
font-weight: medium;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Noto Sans";
src: url("/fonts/NotoSans-Bold.woff2") format("woff2");
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Angelic Agrippa";
src: url("/fonts/AngelicAgrippa-Regular.woff2") format("woff2");
font-weight: normal;
font-style: normal;
font-display: swap;
}
p {
font-family: var(--font-body);
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: var(--font-headers);
}
.font-headers {
font-family: var(--font-headers);
}
.font-body {
font-family: var(--font-body);
}
.text-xs {
font-size: 0.75rem /* 12px */;
line-height: 1rem /* 16px */;
}
.text-sm {
font-size: 0.875rem /* 14px */;
line-height: 1.25rem /* 20px */;
}
.text-base {
font-size: 1rem /* 16px */;
line-height: 1.5rem /* 24px */;
}
.text-lg {
font-size: 1.125rem /* 18px */;
line-height: 1.75rem /* 28px */;
}
.text-xl {
font-size: 1.25rem /* 20px */;
line-height: 1.75rem /* 28px */;
}
.text-2xl {
font-size: 1.5rem /* 24px */;
line-height: 2rem /* 32px */;
}
.text-3xl {
font-size: 1.875rem /* 30px */;
line-height: 2.25rem /* 36px */;
}
/* GLOBAL */
* {
scrollbar-color: var(--color-base-600) transparent;
}
*::-webkit-scrollbar {
background-color: transparent;
width: 4px;
}
*::-webkit-scrollbar-thumb {
background: var(--color-base-600);
border-radius: 9999px;
}
.texture-dots {
background-size: 10cm;
background-attachment: local;
background-image: var(--texture-dots);
background-blend-mode: var(--texture-dots-blend);
background-repeat: repeat;
}
body {
background-color: var(--color-base-150);
color: var(--color-base-1000);
font-size: 95%;
}
button {
color: var(--color-base-600);
border: 1px solid var(--color-base-600);
border-radius: 9999px;
padding: 0 1rem;
height: 2.5rem;
transition-property: color, background-color, box-shadow, border-color;
transition-duration: 0.1s;
&:hover {
background-color: var(--color-base-600);
color: var(--color-base-150);
box-shadow:
0 10px 15px -3px var(--color-shadow),
0 0 6px -4px var(--color-shadow);
}
&:active {
background-color: var(--color-base-1000);
color: var(--color-base-0);
border-color: var(--color-base-1000);
box-shadow:
0 10px 15px -3px var(--color-base-1000),
0 0 6px -4px var(--color-base-1000);
}
}
.turbo-progress-bar {
height: 5px;
background-color: #b07751;
}
:root {
--font-body: "Noto Sans", sans-serif;
--font-headers: "Vollkorn", serif;
--font-angelic: "Angelic Agrippa", serif;
}
</style>

View File

@ -18,35 +18,30 @@ const currentPath = new URL(Astro.request.url).pathname.slice(
const isActive = currentPath.startsWith(href);
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<a
id="component"
href={getLocalizedUrl(href, locale)}
class:list={{ active: isActive }}
>
{icon && <Icon name={icon} />}
<div>
<p id="title">
<div id="text-content">
<p class="font-headers">
{title}
</p>
<p>
{subtitle}
</p>
{subtitle && <p>{subtitle}</p>}
</div>
</a>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
#component {
@container (max-width: 15rem) {
width: 3.5rem;
& > div {
& > #title,
& > p {
display: none;
}
}
}
padding: 1rem;
color: inherit;
text-decoration: inherit;
@ -76,19 +71,26 @@ const isActive = currentPath.startsWith(href);
flex-shrink: 0;
}
& > div {
display: grid;
flex-grow: 1;
@container (max-width: 15rem) {
width: 3.5rem;
& > #title {
line-height: 1.1;
font-size: 1.5rem;
font-family: var(--font-headers);
}
& > p {
margin: 0;
& > #text-content {
display: none;
}
}
}
#text-content {
display: grid;
flex-grow: 1;
gap: 0.25rem;
& > .font-headers {
font-size: 150%;
}
& > :not(.font-headers) {
line-height: 1.5;
}
}
</style>

View File

@ -0,0 +1,93 @@
---
import { Icon } from "astro-icon/components";
import { getLocalizedUrl } from "utils/urls";
interface Props {
href: string;
icon?: string;
title: string | null | undefined;
subtitle?: string | null | undefined;
}
const { locale } = Astro.params;
const { href, icon, title, subtitle } = Astro.props;
const currentPath = new URL(Astro.request.url).pathname.slice(
`/${locale}`.length
);
const isActive = currentPath.startsWith(href);
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<a
id="component"
href={getLocalizedUrl(href, locale)}
class:list={{ active: isActive }}
data-tippy-template="#text-content"
>
{icon && <Icon name={icon} />}
<div id="text-content">
<p class="font-headers">
{title}
</p>
{subtitle && <p>{subtitle}</p>}
</div>
</a>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
#component {
padding: 1rem;
color: inherit;
text-decoration: inherit;
display: flex;
width: 100%;
gap: 1.25rem;
border-radius: 1rem;
transition:
0.15s background-color,
0.15s box-shadow;
&:hover,
&.active {
box-shadow: inset 0 1px 4px -2px var(--color-shadow);
background-color: var(--color-base-250);
&:active {
box-shadow: inset 0 2px 4px 0 var(--color-shadow);
}
}
& > svg {
width: 24px;
height: 24px;
flex-shrink: 0;
}
& > #text-content {
display: none;
}
}
#text-content {
display: grid;
flex-grow: 1;
gap: 0.25rem;
& > .font-headers {
font-size: 150%;
}
& > :not(.font-headers) {
line-height: 1.5;
}
}
</style>

View File

@ -1,13 +1,13 @@
import type { Observable } from "./micro-observables";
export class Elementos {
readonly element: HTMLElement;
readonly element: NodeListOf<Element>;
constructor(readonly selector: string) {
this.element = document.querySelector(selector)!;
this.element = document.querySelectorAll(selector)!;
}
onClick(listener: () => void) {
this.element.addEventListener("click", listener);
this.element.forEach((e) => e.addEventListener("click", listener));
}
setClass(
@ -15,12 +15,14 @@ export class Elementos {
observable: Observable<boolean>
) {
observable.subscribe((val) => {
if (typeof className === "string") {
this.element.classList.toggle(className, val);
} else {
this.element.classList.toggle(className.ifFalse, val === false);
this.element.classList.toggle(className.ifTrue, val === true);
}
this.element.forEach((e) => {
if (typeof className === "string") {
e.classList.toggle(className, val);
} else {
e.classList.toggle(className.ifFalse, val === false);
e.classList.toggle(className.ifTrue, val === true);
}
});
});
}
}

8
src/utils/astro.ts Normal file
View File

@ -0,0 +1,8 @@
import { type AstroCookies } from "astro";
type AstroCookie = ReturnType<AstroCookies["get"]>;
export const parseCookie = <T>(cookies: AstroCookie, defaultValue: T): T => {
const value: T | null = JSON.parse(cookies?.value ?? "null");
return value ?? defaultValue;
};

5
src/utils/events.ts Normal file
View File

@ -0,0 +1,5 @@
export const customEvents = {
mainPanel: {
reduce: { toggle: "mainPanel.reduce.toggle" },
},
};

View File

@ -5,8 +5,10 @@ export const observableWithPersistence = <T>(
cookieKey: string,
defaultValue: T
): WritableObservable<T> => {
const valueFromCookie = Cookies.get(cookieKey) as T | undefined;
const valueFromCookie: T | null = JSON.parse(
Cookies.get(cookieKey) ?? "null"
);
const obs = observable(valueFromCookie ?? defaultValue);
obs.subscribe((val) => Cookies.set(cookieKey, val as string));
obs.subscribe((val) => Cookies.set(cookieKey, JSON.stringify(val)));
return obs;
};