From 3fa55dba1b0ca408a10e7456a6d4308dd114c2f6 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Sun, 1 Oct 2023 10:14:20 +0200 Subject: move stylesheets and refactor js bundler --- web/cantarell.woff2 | Bin 0 -> 93888 bytes web/forms.css | 75 ++++++++++++++++++ web/js-player.css | 52 +++++++++++++ web/layout.css | 148 ++++++++++++++++++++++++++++++++++++ web/nodecard.css | 155 ++++++++++++++++++++++++++++++++++++++ web/nodepage.css | 65 ++++++++++++++++ web/player.css | 102 +++++++++++++++++++++++++ web/script/js-player.js | 125 ++++++++++++++++++++++++++++++ web/script/main.ts | 1 + web/script/playerconf-copy-url.js | 32 ++++++++ web/script/transition.js | 75 ++++++++++++++++++ 11 files changed, 830 insertions(+) create mode 100644 web/cantarell.woff2 create mode 100644 web/forms.css create mode 100644 web/js-player.css create mode 100644 web/layout.css create mode 100644 web/nodecard.css create mode 100644 web/nodepage.css create mode 100644 web/player.css create mode 100644 web/script/js-player.js create mode 100644 web/script/main.ts create mode 100644 web/script/playerconf-copy-url.js create mode 100644 web/script/transition.js (limited to 'web') diff --git a/web/cantarell.woff2 b/web/cantarell.woff2 new file mode 100644 index 0000000..76fd894 Binary files /dev/null and b/web/cantarell.woff2 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 + 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 +*/ + +.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 + 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 + 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 + 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 + 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 +*/ + +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) + "
" + display_time(e.currentTime - e.duration); // TODO can we have
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 +*/ +/// + +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 = "

error

" + rt = await r.text() + } catch (e) { + console.error(e) + return + } + const [head, body] = rt.split("")[1].split("") + 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 -- cgit v1.2.3-70-g09d2