summaryrefslogtreecommitdiff
path: root/views/layouts
diff refs
from: back
to: back
| flip
diff options
context:
space:
mode:
Diffstat (limited to 'views/layouts')
-rw-r--r--views/layouts/general-mail.html124
-rw-r--r--views/layouts/general-page.html948
2 files changed, 773 insertions, 299 deletions
diff --git a/views/layouts/general-mail.html b/views/layouts/general-mail.html
new file mode 100644
index 0000000..452982a
--- /dev/null
+++ b/views/layouts/general-mail.html
@@ -0,0 +1,124 @@
+<!doctype html>
+<html lang="en">
+ <meta charset="UTF-8" />
+ <head>
+ <style>
+ @font-face {
+ font-family: "Gentium Plus";
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src:
+ local("Gentium Plus"),
+ url(https://fonts.gstatic.com/s/gentiumplus/v2/Iurd6Ytw-oSPaZ00r2bNe8VZj5FNym499r7e.woff2)
+ format("woff2");
+ unicode-range:
+ U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+ }
+ @font-face {
+ font-family: "Gentium Plus";
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src:
+ local("Gentium Plus"),
+ url(https://fonts.gstatic.com/s/gentiumplus/v2/Iurd6Ytw-oSPaZ00r2bNe8VZi5FNym499g.woff2)
+ format("woff2");
+ unicode-range:
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
+ U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
+ U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ }
+ html {
+ font-family: "Gentium Plus", sans-serif;
+ --bg-paper: url(data:image/webp;base64,UklGRi4BAABXRUJQVlA4ICIBAABQCQCdASqAAIAAPm02lUmkIqIkIImIgA2JaW3rMFmGDAOKLAoJtP591ByFR74Tun2DP+Lygpfsz5FH7+cKgxi2Islfd03i5AJdXUfU0SDc8SXYAAD++hlzeYAJWQEF8ZdkJdlcA+5btZg0FMGaso8SHoBph0qAOQysElJ5C5SJFNoKIV1J550UyL7Y9Gh0/c/uzLnBmr8lvz7k1WeA81H4S5ViU/wBqa7aM1m8D9rDtrk8cSTqx+xWF6MS8UfZGSsks6CT/igtgv6syFN9uNA3LjnjiCJKYOvFcMkX+jB2578GWVo3nyLaM1QNPYCxn3TiUbnf7U0MdD2NsBpg1+Jd1wu6yW/U+goN+IQS6Nt4RMWltVcZHlxrneKJi0SobO0AAA==);
+ }
+ body {
+ background-color: #e3e6ff;
+ }
+ a {
+ text-decoration: underline;
+ text-shadow: none !important;
+ }
+ .header {
+ width: 100%;
+ padding: 8px 8px 8px 32px;
+ font-size: 48px;
+ color: white;
+ text-shadow:
+ -2px 0 #343b58,
+ 0 2px #343b58,
+ 2px 0 #343b58,
+ 0 -2px #343b58;
+ background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='80'><path fill='#e5e8ff' d='M0 0h40v80H0z'/><g fill='none' stroke-linecap='square' stroke-width='4.5'><path stroke='#8d4453' d='m-10 5 20 10L30 5l20 10'/><path stroke='#375e0d' d='m-10 25 20 10 20-10 20 10'/><path stroke='#006a85' d='m-10 45 20 10 20-10 20 10'/><path stroke='#5a3e8e' d='m-10 65 20 10 20-10 20 10'/></g></svg>");
+ background-color: #e3e6ff;
+ background-size: contain;
+ background-repeat: repeat-x;
+ }
+ .content {
+ margin: 0 32px 0 32px;
+ padding: 16px 16px 32px 16px;
+ font-size: 24px;
+ text-shadow:
+ -1px 0 #343b58,
+ 0 1px #343b58,
+ 1px 0 #343b58,
+ 0 -1px #343b58;
+ color: white;
+ background-color: #c3caff;
+ background-image: var(--bg-paper);
+ background-blend-mode: multiply;
+ background-repeat: repeat;
+ background-size: 128px;
+ }
+ .footer {
+ margin: 0;
+ padding: 1px 8px 1px 8px;
+ font-size: 16px;
+ color: white;
+ }
+ .darkened {
+ background-color: #9aa5f4;
+ background-image: var(--bg-paper);
+ background-blend-mode: multiply;
+ background-repeat: repeat;
+ background-size: 128px;
+ }
+ .outlier {
+ padding-inline: 4px;
+ background-color: #6c6e75;
+ background-image: var(--bg-paper);
+ background-blend-mode: multiply;
+ background-repeat: repeat;
+ background-size: 128px;
+ }
+ table,
+ th,
+ td {
+ border: 1px solid black;
+ }
+ td {
+ padding: 5px;
+ }
+ img {
+ width: 200px;
+ }
+ </style>
+ </head>
+
+ <body>
+ <div id="header" class="header">saya.uz</div>
+
+ <div
+ id="content"
+ class="content"
+ style="background-color: #e3e6ff !important"
+ >
+ {{ block "body" . }}{{ end }}
+ </div>
+
+ <div id="footer" class="footer darkened">
+ {{ block "footer" . }}{{ end }}
+ </div>
+ </body>
+</html>
diff --git a/views/layouts/general-page.html b/views/layouts/general-page.html
index d874ecf..0bd3454 100644
--- a/views/layouts/general-page.html
+++ b/views/layouts/general-page.html
@@ -1,375 +1,725 @@
-<!DOCTYPE html>
+<!doctype html>
<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,200;1,300;1,400;1,500;1,600;1,700;1,800&family=Patua+One&display=swap" rel="stylesheet">
- <link rel="stylesheet" href="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.css">
- <link rel="stylesheet" href="https://f003.backblazeb2.com/file/sayana-static/libs/glightbox/3.3.0/css/glightbox.min.css">
- <link href="/output.css" rel="stylesheet">
-</head>
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ {{- range .Meta }}
+ <meta
+ {{with
+ .Name}}name="{{.}}"
+ {{end}}{{with
+ .Property}}property="{{.}}"
+ {{end}}{{with
+ .Content}}content="{{.}}"
+ {{end}}
+ />
+ {{- if eq .Property "og:description" }}
+ <meta name="description" content="{{.Content}}" />
+ {{- end }} {{- if eq .Property "og:url" }}
+ <link rel="canonical" href="{{.Content}}" />
+ {{- end }} {{- end }}
+ <link
+ rel="stylesheet"
+ href="{{ .StaticStorageBaseUrl }}/fonts/fonts.css"
+ />
+ <link
+ rel="stylesheet"
+ href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/fontawesome.min.css"
+ />
+ <link
+ rel="stylesheet"
+ href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/brands.min.css"
+ />
+ <link
+ rel="stylesheet"
+ href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/solid.min.css"
+ />
+ <link
+ rel="stylesheet"
+ href="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.css"
+ />
+ <link
+ rel="stylesheet"
+ href="{{ .StaticStorageBaseUrl }}/libs/glightbox/3.3.0/css/glightbox.min.css"
+ />
+ <link rel="stylesheet" href="/output.css" />
+ <link
+ rel="icon"
+ href="/favicon-light.svg"
+ type="image/svg+xml"
+ media="(prefers-color-scheme: light)"
+ />
+ <link
+ rel="icon"
+ href="/favicon-dark.svg"
+ type="image/svg+xml"
+ media="(prefers-color-scheme: dark)"
+ />
+ {{- with .LinkedData }}
+ <script type="application/ld+json">
+ {{ . }}
+ </script>
+ {{- end }}
+ </head>
-<body data-theme="ram" class="font-spectral text-main-dark overflow-hidden bg-interlocked-hexagons h-[100dvh] flex flex-row ar-lt-0.8:flex-col">
+ <body
+ data-theme="ram"
+ class="font-m-plus text-main-hard overflow-hidden bg-interlocked-hexagons h-dvh flex flex-row"
+ >
+ <script>
+ let map = null;
+ </script>
- <script>
- let map = null;
- </script>
+ <script>
+ function changeUrl(path, toAppend = false) {
+ const urlParams = new URLSearchParams(window.location.search);
+ newPathname = location.pathname.endsWith("/")
+ ? location.pathname.slice(0, -1)
+ : location.pathname;
+ while (path.slice(-3) == "/.." && toAppend) {
+ newPathname = newPathname.replace(/\/[^\/]+$/, "");
+ path = path.slice(0, -3);
+ }
+ const newPath = toAppend ? `${newPathname}${path}` : `${path}`;
- <script>
- function changeUrl(path, toAppend = false) {
- const urlParams = new URLSearchParams(window.location.search);
- const newPathname = location.pathname.endsWith('/') ? location.pathname.slice(0, -1) : location.pathname;
- const newPath = toAppend ? `${newPathname}${path}` : `${path}`;
-
- history.pushState({}, '', `${newPath}${urlParams.entries.length == 0 ? '' : '?' + urlParams.toString()}`);
- if (location.pathname == "" || location.pathname == "/") {
- return true;
+ history.pushState(
+ {},
+ "",
+ `${newPath}${urlParams.entries.length == 0 ? "" : "?" + urlParams.toString()}`,
+ );
+ window.dispatchEvent(new Event("popstate"));
+ return false;
}
- window.dispatchEvent(new Event('popstate'));
- return false;
- };
- function convertRemToPixels(rem) {
- return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
- }
- </script>
+ function convertRemToPixels(rem) {
+ return (
+ rem *
+ parseFloat(
+ getComputedStyle(document.documentElement).fontSize,
+ )
+ );
+ }
+ </script>
- <script src="https://f003.backblazeb2.com/file/sayana-static/libs/glightbox/3.3.0/js/glightbox.min.js"></script>
+ <script src="{{ .StaticStorageBaseUrl }}/libs/glightbox/3.3.0/js/glightbox.min.js"></script>
- <script>
- function sfc32(a, b, c, d) {
- return function() {
- a |= 0; b |= 0; c |= 0; d |= 0;
- let t = (a + b | 0) + d | 0;
- d = d + 1 | 0;
- a = b ^ b >>> 9;
- b = c + (c << 3) | 0;
- c = (c << 21 | c >>> 11);
- c = c + t | 0;
- return (t >>> 0) / 4294967296;
+ <script>
+ function sfc32(a, b, c, d) {
+ return function () {
+ a |= 0;
+ b |= 0;
+ c |= 0;
+ d |= 0;
+ let t = (((a + b) | 0) + d) | 0;
+ d = (d + 1) | 0;
+ a = b ^ (b >>> 9);
+ b = (c + (c << 3)) | 0;
+ c = (c << 21) | (c >>> 11);
+ c = (c + t) | 0;
+ return (t >>> 0) / 4294967296;
+ };
}
- }
- function cyrb128(str) {
- let h1 = 1779033703, h2 = 3144134277,
- h3 = 1013904242, h4 = 2773480762;
- for (let i = 0, k; i < str.length; i++) {
- k = str.charCodeAt(i);
- h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
- h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
- h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
- h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
+ function cyrb128(str) {
+ let h1 = 1779033703,
+ h2 = 3144134277,
+ h3 = 1013904242,
+ h4 = 2773480762;
+ for (let i = 0, k; i < str.length; i++) {
+ k = str.charCodeAt(i);
+ h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
+ h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
+ h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
+ h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
+ }
+ h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
+ h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
+ h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
+ h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
+ ((h1 ^= h2 ^ h3 ^ h4), (h2 ^= h1), (h3 ^= h1), (h4 ^= h1));
+ return [h1 >>> 0, h2 >>> 0, h3 >>> 0, h4 >>> 0];
}
- h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
- h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
- h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
- h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
- h1 ^= (h2 ^ h3 ^ h4), h2 ^= h1, h3 ^= h1, h4 ^= h1;
- return [h1>>>0, h2>>>0, h3>>>0, h4>>>0];
- }
- var seed = cyrb128("{{ .Title }}");
- var rand = sfc32(seed[0], seed[1], seed[2], seed[3]);
+ var seed = cyrb128("{{ .Title }}");
+ var rand = sfc32(seed[0], seed[1], seed[2], seed[3]);
- function calculateVmin(percent) {
- const viewportWidth = window.innerWidth;
- const viewportHeight = window.innerHeight;
+ function calculateVmin(percent) {
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
- const smallerDimension = Math.min(viewportWidth, viewportHeight);
+ const smallerDimension = Math.min(
+ viewportWidth,
+ viewportHeight,
+ );
- const vminValue = (smallerDimension / 100) * percent;
+ const vminValue = (smallerDimension / 100) * percent;
- return vminValue;
- }
+ return vminValue;
+ }
- function switchThemeDecor(theme) {
- document.querySelectorAll('.theme-decor').forEach((e) => {
- e.remove();
- });
+ function switchThemeDecor(theme) {
+ document.querySelectorAll(".theme-decor").forEach((e) => {
+ e.remove();
+ });
- if (theme == "" || typeof theme == "undefined") {
- theme = document.body.getAttribute('data-theme');
- }
+ if (theme == "" || typeof theme == "undefined") {
+ theme = document.body.getAttribute("data-theme");
+ }
- vmin = calculateVmin(1);
- var clWidth = document.documentElement.clientWidth;
- var clHeight = document.documentElement.clientHeight;
+ vmin = calculateVmin(1);
+ var clWidth = document.documentElement.clientWidth;
+ var clHeight = document.documentElement.clientHeight;
- switch (theme) {
- case "olive": break;
- case "lettuce": break;
- case "night":
- for (i = 0; i < 25; i++) {
- let star = document.createElement("div");
- star.append("*");
- star.classList.add("night-star", "theme-decor", "z-0", "absolute");
- const x = Math.floor(rand() * clWidth);
- const y = Math.floor(rand() * clHeight * 0.8);
- const rot = rand() * 90;
- const animDuration = (rand() * 10) + 3;
- const sz = Math.floor(rand() * 8) + 12;
- star.style.transform = `translateX(${x}px) translateY(${y}px) rotate(${rot}deg)`;
- star.style.animationDuration = `${animDuration}s`;
- star.style.fontSize = `${sz}px`;
- document.body.append(star);
- }
- break;
- case "ram":
- const frameCenters = new Array(4);
- for (i = 1; i <= 4; i++) {
- let frame = document.createElement("img");
- frame.src = `https://f003.backblazeb2.com/file/sayana-static/themes/ram/ram-frame${i}.webp`;
- frame.classList.add("ram-frame", "theme-decor", "z-0", "absolute");
- let x, y;
- outerLoop:
- while (true) {
- x = Math.floor(rand() * clWidth - 20*vmin);
- y = Math.floor(rand() * clHeight - 20*vmin);
- for (j = 0; j < i-1; j++) {
- const xd = frameCenters[j].x - x;
- const yd = frameCenters[j].y - y;
- if (Math.sqrt(xd*xd + yd*yd) < 30*vmin) {
- continue outerLoop;
+ switch (theme) {
+ case "olive":
+ break;
+ case "lettuce":
+ break;
+ case "night":
+ for (i = 0; i < 25; i++) {
+ let star = document.createElement("div");
+ star.append("*");
+ star.classList.add(
+ "night-star",
+ "theme-decor",
+ "z-0",
+ "absolute",
+ );
+ const x = Math.floor(rand() * clWidth);
+ const y = Math.floor(rand() * clHeight * 0.8);
+ const rot = rand() * 90;
+ const animDuration = rand() * 10 + 3;
+ const sz = Math.floor(rand() * 8) + 12;
+ star.style.transform = `translateX(${x}px) translateY(${y}px) rotate(${rot}deg)`;
+ star.style.animationDuration = `${animDuration}s`;
+ star.style.fontSize = `${sz}px`;
+ document.body.append(star);
+ }
+ break;
+ case "ram":
+ const frameCenters = new Array(4);
+ for (i = 1; i <= 4; i++) {
+ let frame = document.createElement("img");
+ frame.src = `{{ .StaticStorageBaseUrl }}/themes/ram/ram-frame${i}.webp`;
+ frame.classList.add(
+ "ram-frame",
+ "theme-decor",
+ "z-0",
+ "absolute",
+ );
+ let x, y;
+ outerLoop: while (true) {
+ x = Math.floor(rand() * clWidth - 20 * vmin);
+ y = Math.floor(rand() * clHeight - 20 * vmin);
+ for (j = 0; j < i - 1; j++) {
+ const xd = frameCenters[j].x - x;
+ const yd = frameCenters[j].y - y;
+ if (
+ Math.sqrt(xd * xd + yd * yd) <
+ 30 * vmin
+ ) {
+ continue outerLoop;
+ }
}
+ break;
}
- break;
+ frameCenters[i - 1] = { x: x, y: y };
+ const rot = rand() * 90 - 45;
+ frame.style.transform = `translateX(${x}px) translateY(${y}px) rotate(${rot}deg)`;
+ document.body.append(frame);
}
- frameCenters[i-1] = {x: x, y: y};
- const rot = rand() * 90 - 45;
- frame.style.transform = `translateX(${x}px) translateY(${y}px) rotate(${rot}deg)`;
- document.body.append(frame);
- }
- break;
+ break;
+ case "lilac":
+ break;
+ }
}
- }
- function switchTheme(theme) {
- if (theme == "" || typeof theme == "undefined") {
- const currentTheme = document.body.getAttribute('data-theme');
- switch (currentTheme) {
- case "olive": theme = "lettuce"; break;
- case "lettuce": theme = "night"; break;
- case "night": theme = "ram"; break;
- case "ram": theme = "olive"; break;
+ function switchTheme(theme) {
+ if (theme == "" || typeof theme == "undefined") {
+ const currentTheme =
+ document.body.getAttribute("data-theme");
+ switch (currentTheme) {
+ case "olive":
+ theme = "lettuce";
+ break;
+ case "lettuce":
+ theme = "night";
+ break;
+ case "night":
+ theme = "ram";
+ break;
+ case "ram":
+ theme = "lilac";
+ break;
+ case "lilac":
+ theme = "olive";
+ break;
+ }
}
+ localStorage.setItem("theme", theme);
+ document.body.setAttribute("data-theme", theme);
+ switchThemeDecor(theme);
}
- localStorage.setItem('theme', theme);
- document.body.setAttribute('data-theme', theme);
- switchThemeDecor(theme);
- }
- const savedTheme = localStorage.getItem('theme') || 'ram';
- switchTheme(savedTheme);
- </script>
+ const savedTheme = localStorage.getItem("theme") || "lilac";
+ switchTheme(savedTheme);
+ </script>
- <div id="general-page-top-embeds" class="flex"
- hx-get="/api/v1/general-page/top-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
- </div>
- <div id="general-page-top-embeds-trigger" hx-get="/api/v1/general-page/top-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="popstate from:window" hx-target="#general-page-top-embeds" hx-swap="innerHTML"></div>
+ <div
+ id="general-page-top-embeds"
+ class="flex"
+ hx-get="/api/v1/general-page/top-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}"
+ hx-trigger="load"
+ hx-target="this"
+ hx-swap="innerHTML"
+ ></div>
+ <div
+ id="general-page-top-embeds-trigger"
+ hx-get="/api/v1/general-page/top-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}"
+ hx-trigger="popstate from:window"
+ hx-target="#general-page-top-embeds"
+ hx-swap="innerHTML"
+ ></div>
- <script>
- const clientTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
- </script>
+ <script>
+ const clientTimeZone =
+ Intl.DateTimeFormat().resolvedOptions().timeZone;
+ </script>
- <div id="sidebar" class="bg-sidebar flex sticky flex-col ar-gt-0.8:items-center shadow-2xl z-20 w-[5rem] ar-lt-0.8:w-[100dvw] h-[100dvh] ar-lt-0.8:h-[8dvh]">
- <div class="logo-custom block ar-lt-0.8:hidden [@media(max-height:32rem)]:hidden text-sidebar font-patua font-extrabold text-center relative z-20">
- saya.today
- </div>
- <div class="text-[3rem] ar-lt-0.8:text-[5dvh] text-stroke-(--sidebar-stroke-color) text-stroke-[0.1dvw] flex flex-col ar-lt-0.8:flex-row gap-0 relative z-20">
- <a href="/{{ .Lang }}" class="w-[5rem] ar-lt-0.8:w-[8dvh] h-[5rem] ar-lt-0.8:h-[8dvh]">
- <i onclick="return changeUrl('/{{ .Lang }}');" class="fas fa-home flex w-full h-full content-center text-center text-sidebar hover:bg-background-dark transition-colors duration-300 rounded-lg"></i>
- </a>
- <a href="/{{ .Lang }}/blog" class="w-[5rem] ar-lt-0.8:w-[8dvh] h-[5rem] ar-lt-0.8:h-[8dvh]">
- <i onclick="return changeUrl('/{{ .Lang }}/blog');" class="fas fa-search flex w-full h-full content-center text-center text-sidebar hover:bg-background-dark transition-colors duration-300 rounded-lg"></i>
- </a>
- <a href="/{{ .Lang }}/map" class="w-[5rem] ar-lt-0.8:w-[8dvh] h-[5rem] ar-lt-0.8:h-[8dvh]">
- <i class="fas fa-map flex w-full h-full content-center text-center text-sidebar hover:bg-background-dark transition-colors duration-300 rounded-lg"></i>
- </a>
- <i onclick="toggleThemeWheel();" id="theme-button" class="fas fa-swatchbook flex w-[5rem] ar-lt-0.8:w-[8dvh] h-[5rem] ar-lt-0.8:h-[8dvh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 rounded-lg"></i>
- <a href="./" class="hidden max-xs:block [@media(max-height:32rem)]:block w-[5rem] ar-lt-0.8:w-[8dvh] h-[5rem] ar-lt-0.8:h-[8dvh]">
- <i onclick="return changeUrl('/../', true);" class="fas fa-circle-left flex w-full h-full content-center text-center text-sidebar hover:bg-background-dark transition-colors duration-300 rounded-lg"></i>
- </a>
- </div>
- <div class="theme-wheel" id="theme-wheel">
- <div class="theme-icon" data-theme="olive"></div>
- <div class="theme-icon" data-theme="lettuce"></div>
- <div class="theme-icon" data-theme="night"></div>
- <div class="theme-icon" data-theme="ram"></div>
- <div class="theme-pin"></div>
- </div>
- </div>
+ <div class="grow bg-split-left"></div>
- <div class="bg-background-dark @container/main flex z-10 ar-lt-0.8:w-[100dvw] w-[90dvw] xs:w-[85dvw] sm:w-[80dvw] md:w-[75dvw] lg:w-[70dvw] xl:w-[65dvw] 2xl:w-[60dvw] grow-0 h-[100dvh] ar-lt-0.8:max-h-[92dvh]">
- <div class="bg-background-dark border-r-2 border-dashed border-main-dark"></div>
+ <div class="flex flex-col sm:flex-row">
+ <div
+ id="sidebar"
+ class="bg-sidebar flex flex-row sticky w-dvw h-fit xs:w-[90dvw] sm:left-0 sm:flex-col sm:fixed sm:w-fit z-20 shadow-[0_0_1rem_black]"
+ >
+ <nav
+ aria-label="Main navigation"
+ class="text-[3rem] flex flex-row sm:flex-col gap-0 relative z-20"
+ >
+ <a
+ onclick="return changeUrl('/..', true);"
+ href="../"
+ id="sidebar-back-button"
+ class="themed-button themed-button-sidebar w-fit h-fit hidden"
+ draggable="false"
+ >
+ <i
+ class="fas fa-circle-left flex faw-18 h-18 content-center text-center text-sidebar"
+ ></i>
+ </a>
+ <a
+ onclick="return changeUrl('/{{ .Lang }}');"
+ href="/{{ .Lang }}"
+ id="sidebar-home-button"
+ class="themed-button themed-button-sidebar w-fit h-fit hidden"
+ draggable="false"
+ >
+ <i
+ class="fas fa-home flex faw-18 h-18 content-center text-center text-sidebar"
+ ></i>
+ </a>
+ <a
+ onclick="return changeUrl('/{{ .Lang }}/blog');"
+ href="/{{ .Lang }}/blog"
+ id="sidebar-blog-button"
+ class="themed-button themed-button-sidebar w-fit h-fit hidden"
+ draggable="false"
+ >
+ <i
+ class="fas fa-search flex faw-18 h-18 content-center text-center text-sidebar"
+ ></i>
+ </a>
+ <a
+ href="/{{ .Lang }}/map"
+ id="sidebar-map-button"
+ class="themed-button themed-button-sidebar w-fit h-fit hidden"
+ draggable="false"
+ >
+ <i
+ class="fas fa-map flex faw-18 h-18 content-center text-center text-sidebar"
+ ></i>
+ </a>
+ <div
+ id="toolbar-theme-button"
+ onclick="toggleThemeWheel()"
+ class="themed-button themed-button-sidebar w-fit h-fit"
+ draggable="false"
+ >
+ <i
+ id="toolbar-theme-button-content"
+ class="fas fa-swatchbook flex faw-18 h-18 content-center text-center text-sidebar"
+ ></i>
+ </div>
+ <a
+ onclick="return changeUrl('/{{ .Lang }}/user');"
+ href="/{{ .Lang }}/user"
+ id="sidebar-user-button"
+ class="themed-button themed-button-sidebar w-fit h-fit hidden"
+ draggable="false"
+ >
+ <i
+ class="fas fa-user flex faw-18 h-18 content-center text-center text-sidebar"
+ ></i>
+ </a>
+ </nav>
+ <div class="theme-wheel" id="theme-wheel">
+ <div class="theme-icon" data-theme="olive"></div>
+ <div class="theme-icon" data-theme="lettuce"></div>
+ <div class="theme-icon" data-theme="night"></div>
+ <div class="theme-icon" data-theme="ram"></div>
+ <div class="theme-icon" data-theme="lilac"></div>
+ <div class="theme-pin"></div>
+ </div>
+ <script>
+ sidebarHomeButton = document.getElementById(
+ "sidebar-home-button",
+ );
+ sidebarMapButton =
+ document.getElementById("sidebar-map-button");
+ sidebarBlogButton = document.getElementById(
+ "sidebar-blog-button",
+ );
+ sidebarBackButton = document.getElementById(
+ "sidebar-back-button",
+ );
+ sidebarUserButton = document.getElementById(
+ "sidebar-user-button",
+ );
- <div class="max-h-100% flex flex-col grow bg-background-dark relative pt-4 ml-8 pr-4">
- <div id="general-page-header" class="max-xs:hidden [@media(max-height:32rem)]:hidden flex flex-col basis-8"
- hx-get="/api/v1/general-page/header{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
+ function updateSidebar() {
+ if (
+ location.pathname == "" ||
+ location.pathname == "/"
+ ) {
+ sidebarHomeButton.classList.add("hidden");
+ sidebarMapButton.classList.add("hidden");
+ sidebarBlogButton.classList.add("hidden");
+ sidebarBackButton.classList.add("hidden");
+ sidebarUserButton.classList.add("hidden");
+ } else {
+ sidebarHomeButton.classList.remove("hidden");
+ sidebarMapButton.classList.remove("hidden");
+ sidebarBlogButton.classList.remove("hidden");
+ sidebarBackButton.classList.remove("hidden");
+ sidebarUserButton.classList.remove("hidden");
+ lang = location.pathname.split("/")[1];
+ sidebarHomeButton.setAttribute("href", `/${lang}`);
+ sidebarHomeButton.setAttribute(
+ "onclick",
+ `return changeUrl('/${lang}', false);`,
+ );
+ sidebarMapButton.setAttribute(
+ "href",
+ `/${lang}/map`,
+ );
+ sidebarBlogButton.setAttribute(
+ "href",
+ `/${lang}/blog`,
+ );
+ sidebarBlogButton.setAttribute(
+ "onclick",
+ `return changeUrl('/${lang}/blog', false);`,
+ );
+ sidebarUserButton.setAttribute(
+ "href",
+ `/${lang}/user`,
+ );
+ sidebarUserButton.setAttribute(
+ "onclick",
+ `return changeUrl('/${lang}/user', false);`,
+ );
+ }
+ }
+ window.addEventListener("popstate", updateSidebar);
+ window.addEventListener("load", updateSidebar, {
+ once: true,
+ });
+ </script>
</div>
- <div id="general-page-header-trigger" hx-get="/api/v1/general-page/header{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="popstate from:window" hx-target="#general-page-header" hx-swap="innerHTML"></div>
- <hr class="max-xs:hidden [@media(max-height:32rem)]:hidden border-t-4 border-dotted border-main-dark mb-2">
+ <div
+ class="bg-paper bg-background-dark @container/main flex z-10 w-dvw xs:w-[90dvw] sm:w-[85dvw] md:w-[80dvw] lg:w-[75dvw] xl:w-[70dvw] 2xl:w-[60dvw] 3xl:w-[50dvw] grow-0 h-[calc(100dvh-5rem)] sm:h-dvh shadow-[0_0_1rem_black]"
+ >
+ <div
+ class="bg-paper bg-background-dark border-r-2 border-dashed border-main-hard"
+ ></div>
- <div id="general-page-body" class="flex flex-col grow overflow-y-auto"
- hx-get="/api/v1/general-page/body{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
- </div>
- <div id="general-page-body-trigger" hx-get="/api/v1/general-page/body{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="popstate from:window" hx-target="#general-page-body" hx-swap="innerHTML"></div>
+ <div
+ class="max-h-full max-w-full flex flex-col grow bg-paper bg-background-dark relative pt-4 ml-8 pr-4"
+ >
+ <header
+ id="general-page-header"
+ class="max-xs:hidden [@media(max-height:32rem)]:hidden flex flex-col basis-8"
+ hx-get="/api/v1/general-page/header{{ if .QueryString }}?{{ .QueryString }}{{ end }}"
+ hx-trigger="load"
+ hx-target="this"
+ hx-swap="innerHTML"
+ ></header>
+ <div
+ id="general-page-header-trigger"
+ hx-get="/api/v1/general-page/header{{ if .QueryString }}?{{ .QueryString }}{{ end }}"
+ hx-trigger="popstate from:window"
+ hx-target="#general-page-header"
+ hx-swap="innerHTML"
+ ></div>
- <div id="general-page-footer" class="flex flex-row basis-4 my-1"
- hx-get="/api/v1/general-page/footer{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
- </div>
- <div id="general-page-footer-trigger" hx-get="/api/v1/general-page/footer{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="popstate from:window" hx-target="#general-page-footer" hx-swap="innerHTML"></div>
+ <hr
+ class="max-xs:hidden [@media(max-height:32rem)]:hidden border-t-4 border-dotted border-main-hard mb-2"
+ />
- <hr class="border-t-4 border-dotted border-main-dark mb-2">
- </div>
+ <main
+ id="general-page-body"
+ class="relative flex flex-col grow overflow-y-auto"
+ hx-get="/api/v1/general-page/body{{ if .QueryString }}?{{ .QueryString }}{{ end }}"
+ hx-trigger="load"
+ hx-target="this"
+ hx-swap="innerHTML"
+ ></main>
+ <div
+ id="general-page-body-trigger"
+ hx-get="/api/v1/general-page/body{{ if .QueryString }}?{{ .QueryString }}{{ end }}"
+ hx-trigger="popstate from:window"
+ hx-target="#general-page-body"
+ hx-swap="innerHTML"
+ ></div>
- <div class="pr-[0.5dvw] bg-background-dark shadow-2xl border-l-2 border-dotted border-main-dark"></div>
- </div>
+ <footer
+ id="general-page-footer"
+ class="flex flex-row basis-4 my-1"
+ hx-get="/api/v1/general-page/footer{{ if .QueryString }}?{{ .QueryString }}{{ end }}"
+ hx-trigger="load"
+ hx-target="this"
+ hx-swap="innerHTML"
+ ></footer>
+ <div
+ id="general-page-footer-trigger"
+ hx-get="/api/v1/general-page/footer{{ if .QueryString }}?{{ .QueryString }}{{ end }}"
+ hx-trigger="popstate from:window"
+ hx-target="#general-page-footer"
+ hx-swap="innerHTML"
+ ></div>
- <script src="https://f003.backblazeb2.com/file/sayana-static/libs/htmx/2.0.6/htmx.min.js"></script>
- <script src="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.js"></script>
- <script src="https://f003.backblazeb2.com/file/sayana-static/libs/imagesloaded/5.0.0/imagesloaded.pkgd.min.js"></script>
- <script src="https://f003.backblazeb2.com/file/sayana-static/libs/masonry-layout/4.2.2/masonry.pkgd.min.js"></script>
+ <hr
+ class="border-t-4 border-dotted border-main-hard mb-2"
+ />
+ </div>
- <div id="general-page-bottom-embeds" class="flex"
- hx-get="/api/v1/general-page/bottom-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
- </div>
- <div id="general-page-bottom-embeds-trigger" hx-get="/api/v1/general-page/bottom-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="popstate from:window" hx-target="#general-page-bottom-embeds" hx-swap="innerHTML"></div>
+ <div
+ class="pr-[0.5dvw] bg-paper bg-background-dark shadow-2xl border-l-2 border-dotted border-main-hard"
+ ></div>
+ </div>
+ </div>
- <script>
- const themeWheel = document.getElementById('theme-wheel');
- const themeButton = document.getElementById('theme-button');
- const themeIcons = document.querySelectorAll('.theme-icon');
- const themeWheelRadius = convertRemToPixels(6);
+ <div class="grow bg-split-right"></div>
- themeIcons.forEach(icon => {
- if (icon.getAttribute('data-theme') == savedTheme) {
- icon.classList.add('active');
- }
- });
+ <script src="{{ .StaticStorageBaseUrl }}/libs/htmx/2.0.6/htmx.min.js"></script>
+ <script src="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.js"></script>
+ <script src="{{ .StaticStorageBaseUrl }}/libs/imagesloaded/5.0.0/imagesloaded.pkgd.min.js"></script>
+ <script src="{{ .StaticStorageBaseUrl }}/libs/packery/3.0.0/dist/packery.pkgd.min.js"></script>
- function calculateVmax(percent) {
- const viewportWidth = window.innerWidth;
- const viewportHeight = window.innerHeight;
+ <script>
+ // Enable fitWidth-like attribute for Packery. Source: https://codepen.io/desandro/pen/kVmLYz
+ var __resetLayout = Packery.prototype._resetLayout;
+ Packery.prototype._resetLayout = function () {
+ __resetLayout.call(this);
+ var parentSize = getSize(this.element.parentNode);
+ var colW = this.columnWidth + this.gutter;
+ this.fitWidth =
+ Math.floor((parentSize.innerWidth + this.gutter) / colW) *
+ colW;
+ this.packer.width = this.fitWidth;
+ this.packer.height = Number.POSITIVE_INFINITY;
+ this.packer.reset();
+ };
- const largerDimension = Math.max(viewportWidth, viewportHeight);
+ Packery.prototype._getContainerSize = function () {
+ var emptyWidth = 0;
+ for (var i = 0, len = this.packer.spaces.length; i < len; i++) {
+ var space = this.packer.spaces[i];
+ if (
+ space.y === 0 &&
+ space.height === Number.POSITIVE_INFINITY
+ ) {
+ emptyWidth += space.width;
+ }
+ }
- const vmaxValue = (largerDimension / 100) * percent;
+ return {
+ width: this.fitWidth - this.gutter - emptyWidth,
+ height: this.maxY - this.gutter,
+ };
+ };
- return vmaxValue;
- }
+ Packery.prototype.needsResizeLayout = function () {
+ return true;
+ };
+ </script>
- function positionThemeIcons() {
- const sliceAngle = 360 / themeIcons.length;
+ <div
+ id="general-page-bottom-embeds"
+ class="flex"
+ hx-get="/api/v1/general-page/bottom-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}"
+ hx-trigger="load"
+ hx-target="this"
+ hx-swap="innerHTML"
+ ></div>
+ <div
+ id="general-page-bottom-embeds-trigger"
+ hx-get="/api/v1/general-page/bottom-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}"
+ hx-trigger="popstate from:window"
+ hx-target="#general-page-bottom-embeds"
+ hx-swap="innerHTML"
+ ></div>
- themeIcons.forEach((icon, index) => {
- const startAngle = index * sliceAngle;
- const endAngle = (index + 1) * sliceAngle;
+ <script>
+ const themeWheel = document.getElementById("theme-wheel");
+ const themeButton = document.getElementById("toolbar-theme-button");
+ const themeIcons = document.querySelectorAll(".theme-icon");
+ const themeWheelRadius = convertRemToPixels(6);
- const clipPath = createSliceClipPath(startAngle, endAngle, themeWheelRadius);
- icon.style.clipPath = clipPath;
+ themeIcons.forEach((icon) => {
+ if (icon.getAttribute("data-theme") == savedTheme) {
+ icon.classList.add("active");
+ }
});
- }
- function createSliceClipPath(startAngle, endAngle, radius) {
- const centerX = radius;
- const centerY = radius;
+ function calculateVmax(percent) {
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
- let points = `${centerX}px ${centerY}px`;
+ const largerDimension = Math.max(viewportWidth, viewportHeight);
- const segments = Math.max(2, Math.ceil(Math.abs(endAngle - startAngle) / 5));
- const angleStep = (endAngle - startAngle) / segments;
+ const vmaxValue = (largerDimension / 100) * percent;
- for (let i = 0; i <= segments; i++) {
- const angle = ((startAngle + angleStep * i - 90) * Math.PI) / 180;
- const x = centerX + Math.cos(angle) * radius;
- const y = centerY + Math.sin(angle) * radius;
- points += `, ${x}px ${y}px`;
+ return vmaxValue;
}
- points += `, ${centerX}px ${centerY}px`;
-
- return `polygon(${points})`;
- }
+ function positionThemeIcons() {
+ const sliceAngle = 360 / themeIcons.length;
- function positionThemeWheel() {
- const buttonRect = themeButton.getBoundingClientRect();
- const buttonCenterX = buttonRect.left + buttonRect.width / 2;
- const buttonCenterY = buttonRect.top + buttonRect.height / 2;
+ themeIcons.forEach((icon, index) => {
+ const startAngle = index * sliceAngle;
+ const endAngle = (index + 1) * sliceAngle;
- const margin = 0;
+ const clipPath = createSliceClipPath(
+ startAngle,
+ endAngle,
+ themeWheelRadius,
+ );
+ icon.style.clipPath = clipPath;
+ });
+ }
- let wheelX = buttonCenterX - themeWheelRadius;
- let wheelY = buttonCenterY - themeWheelRadius;
+ function createSliceClipPath(startAngle, endAngle, radius) {
+ const centerX = radius;
+ const centerY = radius;
- const viewportWidth = window.innerWidth;
- const viewportHeight = window.innerHeight;
+ let points = `${centerX}px ${centerY}px`;
- if (wheelX < margin) {
- wheelX = margin;
- } else if (wheelX + (themeWheelRadius * 2) > viewportWidth - margin) {
- wheelX = viewportWidth - (themeWheelRadius * 2) - margin;
- }
+ const segments = Math.max(
+ 2,
+ Math.ceil(Math.abs(endAngle - startAngle) / 5),
+ );
+ const angleStep = (endAngle - startAngle) / segments;
+
+ for (let i = 0; i <= segments; i++) {
+ const angle =
+ ((startAngle + angleStep * i - 90) * Math.PI) / 180;
+ const x = centerX + Math.cos(angle) * radius;
+ const y = centerY + Math.sin(angle) * radius;
+ points += `, ${x}px ${y}px`;
+ }
- if (wheelY < margin) {
- wheelY = margin;
- } else if (wheelY + (themeWheelRadius * 2) > viewportHeight - margin) {
- wheelY = viewportHeight - (themeWheelRadius * 2) - margin;
+ points += `, ${centerX}px ${centerY}px`;
+
+ return `polygon(${points})`;
}
- themeWheel.style.left = wheelX + 'px';
- themeWheel.style.top = wheelY + 'px';
- }
+ function positionThemeWheel() {
+ const buttonRect = themeButton.getBoundingClientRect();
+ const buttonCenterX = buttonRect.left + buttonRect.width / 2;
+ const buttonCenterY = buttonRect.top + buttonRect.height / 2;
+
+ const margin = 0;
- function hideThemeWheel() {
- themeWheel.classList.remove('active');
- }
+ let wheelX = buttonCenterX - themeWheelRadius;
+ let wheelY = buttonCenterY - themeWheelRadius;
- function showThemeWheel() {
- positionThemeWheel();
- positionThemeIcons();
- themeWheel.classList.add('active');
- }
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
- function toggleThemeWheel() {
- if (themeWheel.classList.contains('active')) {
- hideThemeWheel();
- } else {
- showThemeWheel();
+ if (wheelX < margin) {
+ wheelX = margin;
+ } else if (
+ wheelX + themeWheelRadius * 2 >
+ viewportWidth - margin
+ ) {
+ wheelX = viewportWidth - themeWheelRadius * 2 - margin;
+ }
+
+ if (wheelY < margin) {
+ wheelY = margin;
+ } else if (
+ wheelY + themeWheelRadius * 2 >
+ viewportHeight - margin
+ ) {
+ wheelY = viewportHeight - themeWheelRadius * 2 - margin;
+ }
+
+ themeWheel.style.left = wheelX + "px";
+ themeWheel.style.top = wheelY + "px";
}
- }
- themeIcons.forEach(icon => {
- icon.addEventListener('click', (e) => {
- e.stopPropagation();
- const theme = icon.getAttribute('data-theme');
- switchTheme(theme);
- themeIcons.forEach(icon => {
- icon.classList.remove('active');
- })
- icon.classList.add('active');
- });
- });
+ function hideThemeWheel() {
+ themeWheel.classList.remove("active");
+ }
- themeWheel.addEventListener('mouseleave', hideThemeWheel);
+ function showThemeWheel() {
+ positionThemeWheel();
+ positionThemeIcons();
+ themeWheel.classList.add("active");
+ }
- document.addEventListener('click', (e) => {
- if (!themeWheel.contains(e.target) && e.target !== themeButton) {
- hideThemeWheel();
+ function toggleThemeWheel() {
+ if (themeWheel.classList.contains("active")) {
+ hideThemeWheel();
+ } else {
+ showThemeWheel();
+ }
}
- });
- </script>
- <script>
- let decorResizeTimeout;
- window.addEventListener('resize', function() {
- clearTimeout(decorResizeTimeout);
- decorResizeTimeout = setTimeout(switchThemeDecor, 300);
- });
- </script>
+ themeIcons.forEach((icon) => {
+ icon.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const theme = icon.getAttribute("data-theme");
+ switchTheme(theme);
+ themeIcons.forEach((icon) => {
+ icon.classList.remove("active");
+ });
+ icon.classList.add("active");
+ });
+ });
+
+ themeWheel.addEventListener("mouseleave", hideThemeWheel);
+
+ document.addEventListener("click", (e) => {
+ if (
+ !themeWheel.contains(e.target) &&
+ e.target !== themeButton &&
+ !e.target.classList.contains("toolbar-theme-button-content")
+ ) {
+ hideThemeWheel();
+ }
+ });
+ </script>
-</body>
+ <script>
+ let decorResizeTimeout;
+ window.addEventListener("resize", function () {
+ clearTimeout(decorResizeTimeout);
+ decorResizeTimeout = setTimeout(switchThemeDecor, 300);
+ });
+ </script>
+ </body>
</html>