aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-10-01 10:14:20 +0200
committermetamuffin <metamuffin@disroot.org>2023-10-01 10:14:20 +0200
commit3fa55dba1b0ca408a10e7456a6d4308dd114c2f6 (patch)
treef1f378662406a5f091816ca97c3f1ccfb5210eef /web
parentd857684dd6358fb5ff979ca09ac78b5649b0f411 (diff)
downloadjellything-3fa55dba1b0ca408a10e7456a6d4308dd114c2f6.tar
jellything-3fa55dba1b0ca408a10e7456a6d4308dd114c2f6.tar.bz2
jellything-3fa55dba1b0ca408a10e7456a6d4308dd114c2f6.tar.zst
move stylesheets and refactor js bundler
Diffstat (limited to 'web')
-rw-r--r--web/cantarell.woff2bin0 -> 93888 bytes
-rw-r--r--web/forms.css75
-rw-r--r--web/js-player.css52
-rw-r--r--web/layout.css148
-rw-r--r--web/nodecard.css155
-rw-r--r--web/nodepage.css65
-rw-r--r--web/player.css102
-rw-r--r--web/script/js-player.js125
-rw-r--r--web/script/main.ts1
-rw-r--r--web/script/playerconf-copy-url.js32
-rw-r--r--web/script/transition.js75
11 files changed, 830 insertions, 0 deletions
diff --git a/web/cantarell.woff2 b/web/cantarell.woff2
new file mode 100644
index 0000000..76fd894
--- /dev/null
+++ b/web/cantarell.woff2
Binary files differ
diff --git a/web/forms.css b/web/forms.css
new file mode 100644
index 0000000..259d7ef
--- /dev/null
+++ b/web/forms.css
@@ -0,0 +1,75 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2023 metamuffin <metamuffin.org>
+ Copyright (C) 2023 tpart
+*/
+input {
+ outline: none;
+ box-sizing: border-box;
+ height: 2.5em;
+}
+input[type="text"],
+input[type="password"] {
+ border-radius: 7px;
+ padding: 0.3em;
+ margin-top: 0.3em;
+ border: 2px solid var(--accent-light);
+}
+input[type="text"]:focus,
+input[type="password"]:focus {
+ background-color: var(--background-light);
+}
+input[type="text"]:disabled,
+input[type="password"]:disabled {
+ border: 2px solid grey;
+}
+
+input[type="submit"],
+form button {
+ padding: 0.5em;
+ margin: 0.5em;
+ justify-self: center;
+ border: 0px solid transparent;
+ background-color: var(--accent-dark);
+ border-radius: 8px;
+ cursor: pointer;
+}
+input[type="submit"]:disabled {
+ background-color: grey;
+}
+input[type="submit"]:hover {
+ filter: brightness(150%);
+}
+
+form.account {
+ padding: 3em;
+ border-radius: 1em;
+ background-color: var(--background-light);
+
+ min-width: 25em;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+form.account input {
+ width: 100%;
+ font-size: large;
+}
+form.account input,
+form.account label {
+ display: block;
+}
+form.account input[type="submit"] {
+ margin: 0;
+ margin-top: 1em;
+ margin-bottom: 1.5em;
+ font-weight: bold;
+}
+form.account h1 {
+ margin-top: 0px;
+}
+form.account p {
+ color: var(--font-dark);
+}
diff --git a/web/js-player.css b/web/js-player.css
new file mode 100644
index 0000000..79dffa0
--- /dev/null
+++ b/web/js-player.css
@@ -0,0 +1,52 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2023 metamuffin <metamuffin.org>
+*/
+
+.js-player {
+ --csize: 50px;
+}
+
+.js-player .controls {
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ width: 100%;
+ height: var(--csize);
+ background-color: #1d1d1d99;
+ transition: opacity 0.2s;
+
+ display: flex;
+ flex-direction: row;
+}
+
+.js-player .controls button {
+ padding: 0px;
+ width: var(--csize);
+ height: 100%;
+ border: none;
+ background-color: #ffffff10;
+}
+
+.js-player .controls p.status {
+ display: inline-block;
+ width: 6em;
+ text-align: right;
+ margin: 0px;
+ font-family: monospace;
+ margin: 0.4em;
+}
+
+.pri {
+ flex-grow: 1;
+ padding: 0px;
+ display: inline-block;
+ margin: 0px;
+ width: calc(100% - var(--csize) * 2 - 2px);
+ height: var(--csize);
+}
+.pri-current {
+ height: var(--csize);
+ background-color: #ffffff20;
+}
diff --git a/web/layout.css b/web/layout.css
new file mode 100644
index 0000000..92a8929
--- /dev/null
+++ b/web/layout.css
@@ -0,0 +1,148 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2023 metamuffin <metamuffin.org>
+ Copyright (C) 2023 tpart
+*/
+@font-face {
+ font-family: "Cantarell";
+ src: url(/assets/cantarell.woff2) format("woff2");
+}
+
+:root {
+ --card-size: 17em;
+ --bar-height: 5em;
+ --port-poster-aspect: 1.41;
+ --land-poster-aspect: (1.41 / 2);
+ --land-thumb-aspect: (9 / 16);
+ --accent-light: rgb(255, 163, 87);
+ --accent-dark: rgb(199, 90, 0);
+ --backdrop-height: 24em;
+ --background-dark: #070707;
+ --background-light: #1c1c1c;
+ --background-very-light: #323232;
+ --main-side-margin: 2em;
+ --font: rgb(218, 218, 218);
+ --font-dark: rgb(148, 148, 148);
+ --font-highlight: white;
+}
+
+::selection {
+ background-color: var(--accent-dark);
+}
+
+* {
+ color: var(--font);
+ scrollbar-width: thin;
+ scrollbar-color: var(--background-light) #0000;
+}
+:root {
+ font-family: "Cantarell", sans-serif;
+ font-weight: 500;
+}
+
+body {
+ background-color: var(--background-dark);
+ width: 100vw;
+ margin: 0px;
+ padding: 0px;
+}
+
+h1 {
+ font-weight: bold;
+ color: var(--font-highlight);
+}
+
+nav {
+ user-select: none;
+ z-index: 90;
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ padding: 1em;
+ width: calc(100vw - 2em);
+ height: 2em;
+ backdrop-filter: blur(6px);
+ background-color: #1c1c1c9a;
+
+ display: flex;
+ align-items: center;
+}
+
+code {
+ font-family: monospace !important;
+}
+.log .time,
+.log .module {
+ color: grey;
+}
+
+nav a {
+ border: 0px solid transparent;
+ border-radius: 5px;
+ padding: 0.5em;
+ text-decoration: none;
+}
+nav a:hover {
+ background-color: #ffffff10;
+}
+nav a {
+ color: #dfe5f3;
+ text-decoration: none;
+ background-image: linear-gradient(transparent, transparent),
+ linear-gradient(var(--accent-light), var(--accent-light));
+ background-size: 100% 2px, 0 2px;
+ background-position: 100% 100%, 0 100%;
+ background-repeat: no-repeat;
+ transition: background-size 0.15s linear;
+}
+nav a:hover {
+ background-size: 0 2px, 100% 2px;
+}
+
+nav h1 {
+ margin: 0px;
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ font-size: 1.5em;
+ display: inline;
+}
+nav .account {
+ margin-left: auto;
+}
+nav .account .username {
+ color: var(--accent-light);
+ font-weight: bold;
+ margin-right: 1em;
+}
+
+#main {
+ display: block;
+ margin-top: var(--bar-height);
+ margin-left: var(--main-side-margin);
+ margin-right: var(--main-side-margin);
+ margin-bottom: 1em;
+}
+
+section.message {
+ background-color: var(--background-light);
+ border-radius: 8px;
+}
+.error {
+ padding: 1em;
+ color: rgb(255, 117, 117);
+ font-family: monospace;
+}
+.success {
+ padding: 1em;
+ color: rgb(117, 255, 117);
+}
+
+footer {
+ padding: 0.1em;
+ text-align: center;
+}
+footer p {
+ color: #828282;
+ font-size: 0.8em;
+}
diff --git a/web/nodecard.css b/web/nodecard.css
new file mode 100644
index 0000000..bfc0b74
--- /dev/null
+++ b/web/nodecard.css
@@ -0,0 +1,155 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2023 metamuffin <metamuffin.org>
+ Copyright (C) 2023 tpart
+*/
+.children {
+ padding: 1em;
+ padding-left: 3em;
+ padding-right: 3em;
+ list-style: none;
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+ box-sizing: border-box;
+}
+.children li {
+ display: block;
+}
+
+.dirup {
+ width: 100%;
+ font-size: large;
+ display: block;
+ text-align: center;
+ background-color: var(--background-light);
+ border-radius: 0.2em;
+ padding: 0.6em;
+ margin: 0.2em;
+ transition: filter 0.22s;
+}
+.dirup:hover {
+ filter: brightness(120%);
+}
+
+.node.card {
+ padding: 1em;
+ height: var(--card-size);
+}
+.node.card.poster.poster-port {
+ width: calc(var(--card-size) / var(--port-poster-aspect));
+}
+.node.card.poster.poster-land {
+ width: calc(var(--card-size) / var(--land-poster-aspect));
+}
+.node.card.poster.thumb-land {
+ width: calc(var(--card-size) / var(--land-thumb-aspect));
+}
+.node.card.poster.poster-square {
+ width: calc(var(--card-size));
+}
+
+.node.card .title {
+ margin-top: 0.1em;
+ text-align: center;
+ text-overflow: ellipsis;
+}
+.node.card .poster {
+ display: grid;
+}
+.node.card .poster a {
+ grid-area: 1 / 1;
+}
+
+.node.card.poster.poster-port .poster img {
+ width: calc(var(--card-size) / var(--port-poster-aspect));
+ height: var(--card-size);
+}
+.node.card.poster.poster-land .poster img {
+ width: calc(var(--card-size) / var(--land-poster-aspect));
+ height: var(--card-size);
+}
+.node.card.poster.thumb-land .poster img {
+ width: calc(var(--card-size) / var(--land-thumb-aspect));
+ height: var(--card-size);
+}
+.node.card.poster.poster-square .poster img {
+ width: calc(var(--card-size));
+ height: var(--card-size);
+}
+.node.card .poster a img {
+ object-fit: cover;
+ object-position: center;
+}
+
+.node.card.poster .poster .cardhover.open {
+ transition: opacity 0.3s, backdrop-filter 0.3s;
+ opacity: 0;
+ display: flex;
+ position: relative;
+ bottom: 0px;
+ height: 5em;
+ margin-top: -5em;
+}
+.node.card.poster .poster:hover .cardhover.open {
+ opacity: 1;
+ background-color: #0004;
+ backdrop-filter: blur(3px);
+}
+.node.card.poster .poster .cardhover.open a {
+ text-decoration: none;
+ width: 100%;
+ height: 1.7em;
+ font-size: large;
+ display: block;
+ text-align: center;
+ background-color: #0004;
+ border-radius: 0.2em;
+ padding: 0.6em;
+ margin: 0.6em;
+ transition: background-color 0.2s;
+}
+.node.card.poster .poster .cardhover.open a:hover {
+ background-color: #0008;
+}
+
+.node.card.poster .poster .cardhover.item {
+ position: relative;
+ pointer-events: none;
+ grid-area: 1 / 1;
+ transition: opacity 0.3s, backdrop-filter 0.3s;
+ opacity: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+.node.card.poster .poster:hover .cardhover.item {
+ opacity: 1;
+ background-color: #0004;
+ backdrop-filter: blur(3px);
+}
+
+.node.card.poster .poster .cardhover.item a.play {
+ text-decoration: none;
+ font-stretch: 200%;
+ width: 1em;
+ height: 1em;
+ line-height: 1;
+ margin: auto;
+ padding: 0.4em 0.3em 0.4em 0.5em;
+ border-radius: 50%;
+ font-size: 1.8em;
+ pointer-events: all;
+ background-color: #0005;
+ transition: background-color 0.2s, font-size 0.2s;
+}
+.node.card.poster .poster .cardhover.item a.play:hover {
+ background-color: #0008;
+ font-size: 2.4em;
+}
+.node.card.poster .poster .cardhover.item .props {
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+}
diff --git a/web/nodepage.css b/web/nodepage.css
new file mode 100644
index 0000000..e40706e
--- /dev/null
+++ b/web/nodepage.css
@@ -0,0 +1,65 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2023 metamuffin <metamuffin.org>
+ Copyright (C) 2023 tpart
+*/
+.backdrop {
+ width: calc(100% + 2 * var(--main-side-margin));
+ height: calc(var(--backdrop-height) + 5em);
+ margin-left: calc(-1 * var(--main-side-margin));
+ margin-right: calc(-1 * var(--main-side-margin));
+ margin-top: calc(-1 * var(--bar-height));
+ margin-bottom: -5em;
+ -webkit-mask-image: linear-gradient(
+ rgba(0, 0, 0, 1),
+ transparent calc(var(--backdrop-height) + 5em)
+ );
+ mask-image: linear-gradient(
+ rgba(0, 0, 0, 1),
+ transparent calc(var(--backdrop-height) + 5em)
+ );
+ -webkit-mask-mode: alpha;
+ mask-mode: alpha;
+ pointer-events: none;
+ object-fit: cover;
+ object-position: center;
+}
+.page.node {
+ position: relative;
+ width: 100%;
+}
+.page.node .bigposter {
+ width: max(8em, 25%);
+ float: left;
+ margin: 3em;
+ margin-top: -1em;
+}
+.page.node .bigposter img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ object-position: center;
+}
+
+.page.node .title h1 {
+ display: inline;
+ margin-right: 1em;
+}
+.page.node .title .play {
+ display: inline;
+ font-stretch: 200%;
+}
+.page.node .title .play::before {
+ content: "▶";
+}
+
+.props p {
+ margin: 0.4em;
+ font-size: small;
+ font-weight: bolder;
+ display: inline-block;
+ padding: 0.2em;
+ background: #5a5a5a85;
+ border-radius: 4px;
+}
diff --git a/web/player.css b/web/player.css
new file mode 100644
index 0000000..eb1a0a0
--- /dev/null
+++ b/web/player.css
@@ -0,0 +1,102 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2023 metamuffin <metamuffin.org>
+ Copyright (C) 2023 tpart
+*/
+
+input {
+ color: white;
+ background-color: black;
+}
+option {
+ font-family: "Cantarell", sans-serif;
+}
+
+fieldset {
+ background-color: var(--background-light);
+ border-radius: 8px;
+}
+
+form.playerconf {
+ display: grid;
+ grid-template-areas:
+ "h h h"
+ "v a s"
+ "b b b";
+ gap: 1em;
+ grid-template-columns: auto auto auto;
+ grid-template-rows: 3em auto 5em;
+}
+
+legend {
+ font-size: 1.5em;
+}
+input[type="radio"] {
+ appearance: none;
+ display: inline-block;
+ width: 1.2em;
+ height: 1.2em;
+ border-radius: 8px;
+ padding: 2px;
+ background-clip: content-box;
+ border: 2px solid var(--font);
+ background-color: transparent;
+ transition: background-color 0.3s;
+}
+input[type="radio"]:checked {
+ background-color: var(--accent-light);
+}
+
+fieldset label {
+ transition: color 0.2s;
+}
+fieldset label:hover {
+ color: var(--accent-light);
+}
+
+.playerconf {
+ margin: 2em;
+}
+.playerconf h2 {
+ grid-area: h;
+ text-align: center;
+}
+.playerconf h3 {
+ grid-area: h;
+ text-align: center;
+}
+.playerconf .video {
+ grid-area: v;
+}
+.playerconf .audio {
+ grid-area: a;
+}
+.playerconf .subtitles {
+ grid-area: s;
+}
+.playerconf input[type="submit"] {
+ grid-area: b;
+ width: 30%;
+ height: 3em;
+ font-size: 1.5em;
+}
+
+.player nav {
+ opacity: 0;
+ transition: opacity 0.2s;
+}
+.player nav:hover {
+ opacity: 1;
+}
+.player #main {
+ margin-top: 0px;
+ margin-left: 0px;
+ margin-right: 0px;
+ margin-bottom: 0px;
+}
+.player video {
+ width: 100vw;
+ height: 100vh;
+ background-color: black;
+}
diff --git a/web/script/js-player.js b/web/script/js-player.js
new file mode 100644
index 0000000..7c3bb43
--- /dev/null
+++ b/web/script/js-player.js
@@ -0,0 +1,125 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2023 metamuffin <metamuffin.org>
+*/
+
+globalThis.addEventListener("load", () => {
+ for (const e of document.getElementsByTagName("video"))
+ patch_video(e)
+})
+
+function patch_video(e) {
+ // move the video to a div
+ const d = document.createElement("div")
+ const p = e.parentElement
+ p.removeChild(e)
+ d.appendChild(e)
+ p.prepend(d)
+
+ e.removeAttribute("controls")
+ d.classList.add("js-player")
+
+ const controls = document.createElement("div")
+ controls.classList.add("controls")
+
+ const pause_button = document.createElement("button")
+ pause_button.textContent = "|>"
+
+ const fullscreen_button = document.createElement("button")
+ fullscreen_button.textContent = "X"
+
+ const status_display = document.createElement("p")
+ status_display.classList.add("status")
+
+ const pri = document.createElement("div")
+ pri.classList.add("pri")
+ const pri_current = document.createElement("div")
+ pri_current.style.width = "0px"
+ pri_current.classList.add("pri-current")
+ pri.append(pri_current)
+
+ controls.append(pause_button, status_display, pri, fullscreen_button)
+ d.append(controls)
+
+
+ e.onloadedmetadata = () => {
+
+ }
+ e.ondurationchange = () => {
+
+ }
+ e.ontimeupdate = () => {
+ status_display.innerHTML = display_time(e.currentTime) + "<br/>" + display_time(e.currentTime - e.duration); // TODO can we have <br> with textContent?!
+ pri_current.style.width = (e.currentTime / e.duration * 100) + "%"
+ }
+ e.onplay = () => {
+ pause_button.textContent = "..."
+ }
+ e.onwaiting = () => {
+ pause_button.textContent = "..."
+ }
+ e.onplaying = () => {
+ pause_button.textContent = "||"
+ }
+ e.onpause = () => {
+ pause_button.textContent = "|>"
+ }
+ const toggle_playing = () => e.paused ? e.play() : e.pause()
+
+ mouse_idle(e, 1000, idle => {
+ controls.style.opacity = idle ? 0 : 1
+ e.style.cursor = idle ? "none" : "default"
+ })
+
+ e.addEventListener("click", toggle_playing)
+ pause_button.addEventListener("click", toggle_playing)
+ fullscreen_button.addEventListener("click", () => {
+ if (document.fullscreenElement) document.exitFullscreen()
+ else document.documentElement.requestFullscreen()
+ })
+ const seek_ev = ev => {
+ const r = pri.getBoundingClientRect()
+ const p = (ev.clientX - r.left) / (r.right - r.left)
+ e.currentTime = p * e.duration
+ }
+ pri.addEventListener("mousedown", ev => {
+ seek_ev(ev)
+ })
+ document.body.addEventListener("keydown", k => {
+ if (k.code == "Period") e.seekToNextFrame()
+ else if (k.code == "Space") toggle_playing()
+ else if (k.code == "ArrowLeft") e.currentTime -= 5
+ else if (k.code == "ArrowRight") e.currentTime += 5
+ else if (k.code == "ArrowUp") e.currentTime -= 60
+ else if (k.code == "ArrowDown") e.currentTime += 60
+ else return;
+ k.preventDefault()
+ })
+}
+
+function mouse_idle(e, timeout, cb) {
+ let ct;
+ let idle = false
+ e.onmouseleave = () => { clearTimeout(ct) }
+ e.onmousemove = () => {
+ clearTimeout(ct)
+ if (idle) {
+ idle = false
+ cb(idle)
+ }
+ ct = setTimeout(() => {
+ idle = true
+ cb(idle)
+ }, timeout)
+ }
+}
+
+function display_time(t) {
+ if (t < 0) return "-" + display_time(-t)
+ let h = 0, m = 0, s = 0;
+ while (t > 3600) t -= 3600, h++;
+ while (t > 60) t -= 60, m++;
+ while (t > 1) t -= 1, s++;
+ return (h ? h + "h" : "") + (m ? m + "m" : "") + (s ? s + "s" : "")
+}
diff --git a/web/script/main.ts b/web/script/main.ts
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/web/script/main.ts
@@ -0,0 +1 @@
+
diff --git a/web/script/playerconf-copy-url.js b/web/script/playerconf-copy-url.js
new file mode 100644
index 0000000..49f27fd
--- /dev/null
+++ b/web/script/playerconf-copy-url.js
@@ -0,0 +1,32 @@
+
+globalThis.addEventListener("load", () => {
+ for (const e of document.getElementsByClassName("playerconf"))
+ patch_playerconf(e)
+})
+
+function patch_playerconf(form) {
+ const submit = form.lastChild
+
+ const copyurl = document.createElement("button")
+ const d = document.createElement("div")
+ form.removeChild(submit)
+ d.appendChild(submit)
+ d.appendChild(copyurl)
+ form.append(d)
+
+ copyurl.textContent = "Copy Stream URL"
+ d.style.gridArea = "b"
+ d.style.width = "100%"
+ copyurl.style.width = "5em"
+
+ copyurl.addEventListener("click", ev => {
+ const session = document.cookie.split(";").map(e => e.trim().split("=")).find(e => e[0] == "session")[1]
+ ev.preventDefault()
+ const fd = new FormData(form)
+ const sp = ["v", "a", "s"].map(k => fd.get(k)).filter(k => k != "").flat()
+ const url = `${window.location.protocol}//${window.location.host}/n/${window.location.pathname.split("/")[2]}/stream?tracks=${sp}&session=${session}`
+ navigator.clipboard.writeText(url)
+ copyurl.textContent = "Copied"
+ setTimeout(() => copyurl.textContent = "Copy Stream URL", 1000)
+ })
+}
diff --git a/web/script/transition.js b/web/script/transition.js
new file mode 100644
index 0000000..7d39176
--- /dev/null
+++ b/web/script/transition.js
@@ -0,0 +1,75 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2023 metamuffin <metamuffin.org>
+*/
+/// <reference lib="dom" />
+
+const duration = 0.2
+globalThis.addEventListener("load", () => {
+ patch_page()
+})
+
+globalThis.addEventListener("popstate", (_e) => {
+ transition_to(window.location.href, true)
+ // transition_to(_e.state.href, true)
+})
+
+function patch_page() {
+ document.querySelectorAll("a").forEach(el => {
+ el.addEventListener("click", async ev => {
+ ev.preventDefault()
+ await transition_to(el.href)
+ })
+ })
+}
+
+async function transition_to(href, back) {
+ const trigger_load = prepare_load(href, back)
+ await fade(false)
+ trigger_load()
+}
+
+function prepare_load(href, back) {
+ const r_promise = fetch(href)
+ return async () => {
+ let rt = ""
+ try {
+ const r = await r_promise
+ if (!r.ok) return document.body.innerHTML = "<h1>error</h1>"
+ rt = await r.text()
+ } catch (e) {
+ console.error(e)
+ return
+ }
+ const [head, body] = rt.split("<head>")[1].split("</head>")
+ document.head.innerHTML = head
+ document.body.outerHTML = body
+ fade(true)
+ // if (!back) window.history.pushState({href}, "", href)
+ if (!back) window.history.pushState({}, "", href)
+ patch_page()
+ }
+}
+
+function fade(dir) {
+ const overlay = document.createElement("div")
+ overlay.style.position = "absolute"
+ overlay.style.left = "0px"
+ overlay.style.top = "0px"
+ overlay.style.width = "100vw"
+ overlay.style.height = "100vh"
+ overlay.style.backgroundColor = dir ? "black" : "transparent"
+ overlay.style.transition = `background-color ${duration}s`
+ overlay.style.zIndex = 99999;
+ setTimeout(() => {
+ overlay.style.backgroundColor = dir ? "transparent" : "black"
+ }, 0)
+ document.body.appendChild(overlay)
+ return new Promise(res => {
+ setTimeout(() => {
+ if (dir) document.body.removeChild(overlay)
+ res()
+ }, duration * 1000)
+ })
+} \ No newline at end of file