aboutsummaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/client-scripts/Cargo.toml2
-rw-r--r--ui/client-scripts/build.rs1
m---------ui/client-scripts/src/jshelper0
-rw-r--r--ui/client-style/Cargo.toml7
-rw-r--r--ui/client-style/build.rs20
-rw-r--r--ui/client-style/src/forms.css153
-rw-r--r--ui/client-style/src/js-player.css255
-rw-r--r--ui/client-style/src/js-transition.css68
-rw-r--r--ui/client-style/src/layout.css174
-rw-r--r--ui/client-style/src/lib.rs10
-rw-r--r--ui/client-style/src/navbar.css104
-rw-r--r--ui/client-style/src/nodecard.css147
-rw-r--r--ui/client-style/src/nodepage.css108
-rw-r--r--ui/client-style/src/player.css100
-rw-r--r--ui/client-style/src/props.css60
-rw-r--r--ui/client-style/src/themes.css94
16 files changed, 1300 insertions, 3 deletions
diff --git a/ui/client-scripts/Cargo.toml b/ui/client-scripts/Cargo.toml
index 1332388..c6a8d74 100644
--- a/ui/client-scripts/Cargo.toml
+++ b/ui/client-scripts/Cargo.toml
@@ -3,7 +3,5 @@ name = "jellyui-client-scripts"
version = "0.1.0"
edition = "2024"
-[dependencies]
-
[build-dependencies]
glob = "0.3.3"
diff --git a/ui/client-scripts/build.rs b/ui/client-scripts/build.rs
index 256fa21..2f3826d 100644
--- a/ui/client-scripts/build.rs
+++ b/ui/client-scripts/build.rs
@@ -15,7 +15,6 @@ fn main() {
println!("cargo:rerun-if-changed={}", file.to_str().unwrap());
}
let outpath = std::env::var("OUT_DIR").unwrap();
- // this is great :)))
let mut proc = Command::new("esbuild")
.arg("src/main.ts")
.arg("--bundle")
diff --git a/ui/client-scripts/src/jshelper b/ui/client-scripts/src/jshelper
new file mode 160000
+Subproject 26b8e17daac3ef21d97c25e55c0812bc9e59286
diff --git a/ui/client-style/Cargo.toml b/ui/client-style/Cargo.toml
new file mode 100644
index 0000000..a5db904
--- /dev/null
+++ b/ui/client-style/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "jellyui-client-style"
+version = "0.1.0"
+edition = "2024"
+
+[build-dependencies]
+glob = "0.3.3"
diff --git a/ui/client-style/build.rs b/ui/client-style/build.rs
new file mode 100644
index 0000000..8cf1404
--- /dev/null
+++ b/ui/client-style/build.rs
@@ -0,0 +1,20 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+*/
+
+use std::{
+ fs::{File, read_to_string},
+ path::PathBuf,
+};
+fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ let mut out = String::new();
+ for file in glob::glob("src/**/*.ts").unwrap().map(Result::unwrap) {
+ println!("cargo:rerun-if-changed={}", file.to_str().unwrap());
+ out += &read_to_string(file).unwrap();
+ }
+ let outpath: PathBuf = std::env::var("OUT_DIR").unwrap().try_into().unwrap();
+ File::create(outpath.join("bundle.css")).unwrap();
+}
diff --git a/ui/client-style/src/forms.css b/ui/client-style/src/forms.css
new file mode 100644
index 0000000..dd885da
--- /dev/null
+++ b/ui/client-style/src/forms.css
@@ -0,0 +1,153 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+ Copyright (C) 2023 tpart
+*/
+input {
+ background-color: var(--background-dark);
+ 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 var(--background-disable);
+}
+option {
+ font-family: "Cantarell", sans-serif;
+}
+
+fieldset {
+ background-color: var(--background-light);
+ border-radius: 8px;
+}
+
+input[type="submit"],
+.play,
+button {
+ color: var(--font-highlight);
+ 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,
+.play,
+button:disabled {
+ background-color: var(--background-disable);
+}
+input[type="submit"]:hover,
+.play,
+button: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);
+}
+
+legend {
+ font-size: 1.5em;
+}
+input[type="radio"] {
+ appearance: none;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ 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);
+}
+
+input[type="checkbox"] {
+ appearance: none;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ border-radius: 3px;
+ padding: 2px;
+ background-clip: content-box;
+ border: 2px solid var(--font);
+ background-color: transparent;
+ transition: background-color 0.3s;
+}
+input[type="checkbox"]:checked {
+ background-color: var(--accent-light);
+}
+
+fieldset label {
+ transition: color 0.2s;
+}
+fieldset label:hover {
+ color: var(--accent-light);
+}
+
+fieldset .categories {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+}
+fieldset .categories .category {
+ margin-right: 2em;
+ margin-left: 2em;
+ min-width: max-content;
+ flex-basis: auto;
+ display: inline-flex;
+ flex-direction: column;
+}
+fieldset .categories .category h3 {
+ margin: 0px;
+}
+
+input.danger {
+ background-color: var(--c-danger);
+}
diff --git a/ui/client-style/src/js-player.css b/ui/client-style/src/js-player.css
new file mode 100644
index 0000000..5c10ff9
--- /dev/null
+++ b/ui/client-style/src/js-player.css
@@ -0,0 +1,255 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+*/
+
+.jsp {
+ --csize: 50px;
+}
+
+.jsp .jsp-controls,
+.jsp-popup {
+ background-color: #1d1d1d99;
+}
+
+.jsp .jsp-controls {
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ width: 100%;
+ height: var(--csize);
+ transition: opacity 0.2s;
+ display: flex;
+ flex-direction: row;
+}
+
+.jsp .jsp-controls button,
+.jsp .jsp-track-list button {
+ padding: 0px;
+ width: var(--csize);
+ height: 100%;
+ border: none;
+ margin: 0;
+ background-color: #ffffff10;
+ border-radius: 0px;
+}
+
+.jsp-controls p.jsp-status {
+ display: inline-block;
+ width: 6em;
+ text-align: right;
+ margin: 0px;
+ font-family: monospace;
+ padding: 0.4em;
+}
+.jsp-track-select {
+ display: inherit;
+}
+.jsp-track-state {
+ border: 0px solid transparent;
+ font-size: x-large;
+ background-color: transparent;
+ cursor: pointer;
+}
+
+.jsp-pri {
+ position: relative;
+ flex-grow: 1;
+ padding: 0px;
+ display: inline-block;
+ margin: 0px;
+ width: calc(100% - var(--csize) * 2 - 2px);
+ height: var(--csize);
+}
+.jsp-pri-current {
+ z-index: 101;
+ position: absolute;
+ height: var(--csize);
+ background-color: #ffffff20;
+}
+.jsp-pri-buffer {
+ z-index: 100;
+ position: absolute;
+}
+.jsp-pri-buffer-buffered {
+ background-color: #08fa0018;
+}
+.jsp-pri-buffer-loading {
+ background-color: #ffd00018;
+}
+
+.jsp-overlay {
+ position: absolute;
+ bottom: var(--csize);
+ left: 0px;
+}
+.jsp-overlay .jsp-buffering {
+ margin: 0.2em;
+ font-size: larger;
+ color: grey;
+}
+.jsp-stats {
+ position: absolute;
+ background-color: rgba(0, 0, 0, 0.404);
+ top: 0px;
+ left: 0px;
+ padding: 1em;
+}
+.jsp-stats pre {
+ margin: 0.1em;
+}
+.jsp .icon {
+ font-size: 1.5em;
+}
+
+.jsp .jsh-log {
+ position: absolute;
+ bottom: var(--csize);
+ left: 0px;
+}
+.jsp .jsh-log-line {
+ padding: 0.15em;
+ margin: 0px;
+ font-size: large;
+}
+.jsp .jsh-log-line-appear {
+ animation-name: appear;
+ animation-timing-function: linear;
+ animation-duration: 0.5s;
+ animation-fill-mode: forwards;
+}
+.jsp .jsh-log-line-disappear {
+ animation-name: disappear;
+ animation-timing-function: linear;
+ animation-duration: 0.2s;
+ animation-fill-mode: forwards;
+}
+
+@keyframes appear {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+@keyframes disappear {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
+
+.jsp-popup {
+ position: absolute;
+ bottom: var(--csize);
+ right: 0px;
+ animation-name: popup-in;
+ animation-delay: 180ms;
+ animation-duration: 100ms;
+ animation-fill-mode: both;
+ animation-timing-function: ease-out;
+}
+.jsp-popup-out {
+ animation-name: popup-out;
+ animation-delay: 0ms;
+ animation-duration: 100ms;
+ animation-fill-mode: both;
+ animation-timing-function: ease-in;
+}
+@keyframes popup-in {
+ from {
+ bottom: calc(var(--csize) - 20px);
+ opacity: 0;
+ }
+ to {
+ bottom: var(--csize);
+ opacity: 1;
+ }
+}
+@keyframes popup-out {
+ from {
+ bottom: var(--csize);
+ opacity: 1;
+ }
+ to {
+ bottom: calc(var(--csize) - 20px);
+ opacity: 0;
+ }
+}
+
+.jsp-settings-popup {
+ padding: 1em;
+ min-width: 14em;
+}
+.jsp-track-select-popup {
+ min-width: 14em;
+ padding: 1em;
+}
+.jsp-settings-popup h2,
+.jsp-settings-popup h3 {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+
+.jsp-controlgroup {
+ padding: 0.5em;
+ margin-top: 1em;
+ margin-bottom: 1em;
+ background-color: #00000038;
+}
+
+ul.jsp-track-list {
+ list-style: none;
+ padding: 0px;
+}
+ul.jsp-track-list li.active {
+ background-color: #047a0073;
+}
+
+.jsp-volumecontrol input {
+ appearance: none;
+ width: 100%;
+ height: 24px;
+ background-color: black;
+ opacity: 0.5;
+ outline: none;
+}
+.jsp-volumecontrol input:hover {
+ opacity: 1;
+}
+.jsp-volumecontrol input::-webkit-slider-thumb,
+.jsp-volumecontrol input::-moz-range-thumb {
+ width: 24px;
+ height: 24px;
+ border-radius: 0px;
+ background-color: #06ad00;
+ cursor: ew-resize;
+ border: 0px solid transparent;
+}
+
+.jsp-volume {
+ display: inline-block;
+ margin-left: 2em;
+ font-family: monospace;
+ font-size: large;
+ width: 20em;
+}
+
+.jsp-chapter {
+ position: absolute;
+ height: var(--csize);
+ padding-left: 2px;
+ border-left: 2px solid rgba(255, 161, 55, 0.548);
+}
+.jsp-chapter p {
+ font-size: small;
+ text-overflow: ellipsis;
+ overflow: visible;
+ overflow: hidden;
+ white-space: nowrap;
+ width: 100%;
+}
diff --git a/ui/client-style/src/js-transition.css b/ui/client-style/src/js-transition.css
new file mode 100644
index 0000000..64e9bb2
--- /dev/null
+++ b/ui/client-style/src/js-transition.css
@@ -0,0 +1,68 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+*/
+@keyframes jst-fadein {
+ from {
+ background-color: transparent;
+ }
+ to {
+ background-color: var(--c-fade);
+ }
+}
+@keyframes jst-fadeout {
+ from {
+ background-color: var(--c-fade);
+ }
+ to {
+ background-color: transparent;
+ }
+}
+@keyframes jst-spin {
+ from {
+ transform: rotate(0turn);
+ }
+ to {
+ transform: rotate(-1turn);
+ }
+}
+
+.jst-fade {
+ position: fixed;
+ left: 0px;
+ top: 0px;
+ width: 100vw;
+ height: 100vh;
+ z-index: 10;
+}
+.jst-message {
+ position: fixed;
+ top: 50vh;
+ left: 50vw;
+ transform: translate(-50%, -50%);
+ font-size: large;
+ z-index: 11;
+}
+.jst-message.error {
+ color: var(--c-error);
+}
+.jst-message.success {
+ color: var(--c-success);
+}
+.jst-spinner {
+ position: fixed;
+ top: 50vh;
+ left: 50vw;
+ translate: -50% -50%;
+ color: var(--c-warn);
+ font-size: large;
+ z-index: 11;
+ animation-name: jst-spin;
+ animation-duration: 1s;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+}
+nav {
+ z-index: 11;
+}
diff --git a/ui/client-style/src/layout.css b/ui/client-style/src/layout.css
new file mode 100644
index 0000000..5c5272c
--- /dev/null
+++ b/ui/client-style/src/layout.css
@@ -0,0 +1,174 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+ Copyright (C) 2023 tpart
+*/
+@font-face {
+ font-family: "Cantarell";
+ src: url(/assets/cantarell.woff2) format("woff2");
+}
+
+@font-face {
+ font-family: "Material Icons";
+ font-style: normal;
+ font-weight: 400;
+ font-display: block;
+ src: url("/assets/fonts/material-icons.woff2") format("woff2");
+}
+
+:root {
+ --card-size: min(50vw, 17em);
+ --bar-height: 5em;
+ --port-poster-aspect: calc(1 / 1.41);
+ --land-poster-aspect: calc(2 / 1.41);
+ --land-thumb-aspect: calc(16 / 9);
+ --backdrop-height: 24em;
+ --main-side-margin: 2em;
+}
+
+::selection {
+ background-color: var(--accent-dark);
+}
+
+* {
+ 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, h2, h3, h4 {
+ color: var(--font-highlight);
+}
+h1 {
+ font-weight: bold;
+}
+p, span, a, td, th, label, input, legend, pre, summary, li {
+ color: var(--font);
+}
+pre {
+ margin: 0px;
+}
+
+code {
+ font-family: monospace !important;
+}
+.log .time,
+.log .module {
+ color: grey;
+}
+
+#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 {
+ color: var(--c-error);
+ font-family: monospace;
+}
+.warn {
+ color: var(--c-warn);
+}
+.success {
+ color: var(--c-success);
+}
+.message p {
+ padding: 1em;
+}
+
+footer {
+ padding: 0.1em;
+ text-align: center;
+}
+footer p, footer p a {
+ color: var(--font-dark);
+ font-size: 0.8rem;
+}
+
+summary h3 {
+ display: inline;
+}
+
+*::before,
+.icon {
+ font-family: "Material Icons";
+ line-height: 1;
+ vertical-align: text-bottom;
+ display: inline-block;
+ text-rendering: optimizeLegibility;
+ font-feature-settings: "liga";
+}
+*::before {
+ margin-right: 3px;
+}
+
+.children {
+ padding: 1em;
+ padding-left: 3em;
+ padding-right: 3em;
+ list-style: none;
+ width: 100%;
+ box-sizing: border-box;
+ display: flex;
+}
+.children:not(.hlist) {
+ flex-wrap: wrap;
+}
+.children li {
+ display: block;
+}
+.hlist {
+ overflow-x: auto;
+ max-width: 100%;
+ flex-wrap: nowrap;
+}
+.hlist li {
+ display: inline-block;
+}
+
+@media (max-width: 750px) {
+ .hlist ul {
+ padding-left: 0;
+ }
+}
+@media (max-width: 600px), (max-height: 800px) {
+ #main h2 {
+ margin: 0;
+ }
+}
+
+.search h1 {
+ text-align: center;
+}
+.search form {
+ text-align: center;
+}
+.search form input[type="text"] {
+ width: max(10em, 40%);
+}
+
+table.striped tr:nth-child(2n) {
+ background-color: #fff2;
+}
+table.striped td {
+ border: none;
+ padding: 5px;
+}
diff --git a/ui/client-style/src/lib.rs b/ui/client-style/src/lib.rs
new file mode 100644
index 0000000..06ddce4
--- /dev/null
+++ b/ui/client-style/src/lib.rs
@@ -0,0 +1,10 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+*/
+use std::borrow::Cow;
+
+pub fn css_bundle() -> Cow<'static, str> {
+ include_str!(concat!(env!("OUT_DIR"), "/bundle.css")).into()
+}
diff --git a/ui/client-style/src/navbar.css b/ui/client-style/src/navbar.css
new file mode 100644
index 0000000..fcb3cdd
--- /dev/null
+++ b/ui/client-style/src/navbar.css
@@ -0,0 +1,104 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+*/
+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: var(--c-nav);
+
+ display: flex;
+ align-items: center;
+}
+
+nav a .logo {
+ height: 1.5em;
+}
+
+nav a {
+ display: inline-block;
+ border: 0px solid transparent;
+ border-radius: 5px;
+ padding: 0.5em;
+ text-decoration: none;
+ color: var(--font);
+ 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;
+ cursor: pointer;
+}
+
+nav a:hover {
+ background-color: var(--c-nav-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;
+}
+
+nav .admin::before {
+ content: "admin_panel_settings";
+}
+
+nav .settings::before {
+ content: "settings";
+}
+
+nav .logout::before {
+ content: "logout";
+}
+
+nav .login::before {
+ content: "login";
+}
+
+nav .back::before {
+ content: "arrow_back";
+}
+
+.hybrid_button p {
+ display: inline;
+}
+
+@media (max-width: 1000px) {
+ .hybrid_button p {
+ display: none;
+ }
+ *::before {
+ margin-right: 0px;
+ }
+}
+@media (max-width: 750px) {
+ nav .account span {
+ display: none;
+ }
+}
+@media (max-width: 500px) {
+ nav .library {
+ display: none;
+ }
+}
diff --git a/ui/client-style/src/nodecard.css b/ui/client-style/src/nodecard.css
new file mode 100644
index 0000000..2c0b97f
--- /dev/null
+++ b/ui/client-style/src/nodecard.css
@@ -0,0 +1,147 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+ Copyright (C) 2023 tpart
+*/
+
+.poster * img, .bigposter img {
+ background-color: var(--image-loading-placeholder);
+}
+.poster a, .bigposter img {
+ border-radius: 1em;
+}
+
+.card {
+ padding: 1em;
+}
+
+.card .poster,
+.card .poster img {
+ height: var(--card-size);
+}
+.aspect-port {
+ width: calc(var(--card-size) * var(--port-poster-aspect));
+}
+.aspect-land {
+ width: calc(var(--card-size) * var(--land-poster-aspect));
+}
+.aspect-thumb {
+ width: calc(var(--card-size) * var(--land-thumb-aspect));
+}
+.aspect-square {
+ width: calc(var(--card-size));
+}
+
+.card .poster a img {
+ object-fit: cover;
+ object-position: center;
+ width: 100%;
+}
+.card .title, .card .subtitle {
+ text-align: center;
+ margin-top: 0.5em;
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+.card .subtitle {
+ margin-top: 0.25em;
+}
+.card .subtitle * {
+ color: var(--font-dark);
+}
+.card .title a, .card .subtitle a {
+ text-decoration-line: none;
+}
+.card .title a:hover {
+ text-decoration-line: underline;
+}
+.card .title .subtitle {
+ color: grey;
+}
+.card .poster {
+ display: grid;
+}
+.card .poster a {
+ grid-area: 1 / 1;
+}
+
+.card .poster .cardhover {
+ position: relative;
+ pointer-events: none;
+ grid-area: 1 / 1;
+ transition: opacity 0.3s;
+ background-color: var(--overlay-poster);
+ opacity: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+.card .poster:hover .cardhover {
+ opacity: 1;
+}
+
+.card .poster a {
+ overflow: hidden;
+}
+
+.card .poster a img {
+ transition: transform 0.3s;
+}
+
+.card .poster:hover a img {
+ transform: scale(1.1);
+}
+
+.card .poster .cardhover a.play {
+ text-decoration: none;
+ width: 1em;
+ height: 1em;
+ line-height: 1;
+ margin: auto;
+ padding: 0.2em;
+ border-radius: 50%;
+ font-size: 2.2em;
+ pointer-events: all;
+ background-color: var(--overlay);
+ transition: background-color 0.3s, font-size 0.3s;
+}
+.card .poster .cardhover a.play:hover {
+ background-color: var(--overlay-hover);
+ font-size: 3em;
+}
+.card .poster .cardhover .props {
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+}
+
+.widecard {
+ display: grid;
+ grid-template-columns: 1fr 10000fr;
+ width: 100%;
+}
+.widecard .poster {
+ grid-column: 1;
+}
+.widecard .details {
+ grid-column: 2;
+ margin: 1em;
+}
+.widecard .details .title {
+ font-size: large;
+}
+.widecard .details .props {
+ margin-bottom: 0.5em;
+}
+
+@media (max-width: 750px) {
+ nav .library {
+ display: none;
+ }
+ .children {
+ justify-content: center;
+ }
+}
diff --git a/ui/client-style/src/nodepage.css b/ui/client-style/src/nodepage.css
new file mode 100644
index 0000000..1a5b8ee
--- /dev/null
+++ b/ui/client-style/src/nodepage.css
@@ -0,0 +1,108 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+ Copyright (C) 2023 tpart
+*/
+.backdrop {
+ width: calc(100% + 2 * var(--main-side-margin));
+ height: min(50vh, 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 min(50vh, calc(var(--backdrop-height) + 5em))
+ );
+ mask-image: linear-gradient(
+ rgba(0, 0, 0, 1),
+ transparent min(50vh, 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;
+ height: 100%;
+}
+.page.node .bigposter img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ object-position: center;
+ display: block;
+}
+
+.bigposter.aspect-thumb {
+ aspect-ratio: var(--land-thumb-aspect)
+}
+.bigposter.aspect-land {
+ aspect-ratio: var(--land-poster-aspect)
+}
+.bigposter.aspect-port {
+ aspect-ratio: var(--port-poster-aspect)
+}
+.bigposter.aspect-square {
+ aspect-ratio: 1;
+}
+
+.page.node .title h1 {
+ margin-right: 1em;
+}
+.page.node .title .play {
+ display: inline-block;
+ font-size: small;
+ background-color: #52b83340;
+}
+.page.node .title .play::before {
+ content: "play_arrow";
+ vertical-align: middle;
+}
+.page.node .title .mark_watched,
+.page.node .title .mark_unwatched {
+ display: inline-block;
+}
+.page.node .title .mark_watched input,
+.page.node .title .mark_unwatched input {
+ background-color: #80808040;
+}
+
+/* TODO find a non-horrible way to put the icon there */
+/*.page.node .title .mark_watched input::before {
+ content: "done_all";
+}
+.page.node .title .mark_unwatched input::before {
+ content: "undo";
+}*/
+
+.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%);
+}
+
+@media (max-width: 500px) {
+ .page.node .bigposter {
+ margin: 1.5em;
+ }
+}
diff --git a/ui/client-style/src/player.css b/ui/client-style/src/player.css
new file mode 100644
index 0000000..4998ca2
--- /dev/null
+++ b/ui/client-style/src/player.css
@@ -0,0 +1,100 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+ Copyright (C) 2023 tpart
+*/
+
+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;
+}
+
+.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 .subtitle {
+ 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;
+ display: block;
+}
+
+video::cue {
+ background-color: transparent;
+ /* TODO this is inefficient */
+ /* print(", ".join([f"{x/19}em {y/19}em black" for x in range(-5,6) for y in range(-5,6) if x*x+y*y < 5*5])) */
+ text-shadow: 0em 0.1em black,
+ 0.02079116908177593em 0.09781476007338058em black,
+ 0.040673664307580015em 0.0913545457642601em black,
+ 0.058778525229247314em 0.08090169943749476em black,
+ 0.07431448254773941em 0.06691306063588583em black,
+ 0.08660254037844387em 0.05000000000000002em black,
+ 0.09510565162951536em 0.030901699437494747em black,
+ 0.09945218953682733em 0.010452846326765346em black,
+ 0.09945218953682734em -0.010452846326765334em black,
+ 0.09510565162951537em -0.030901699437494736em black,
+ 0.08660254037844388em -0.04999999999999998em black,
+ 0.07431448254773945em -0.0669130606358858em black,
+ 0.05877852522924733em -0.08090169943749474em black,
+ 0.04067366430758001em -0.09135454576426011em black,
+ 0.02079116908177593em -0.09781476007338058em black,
+ 1.2246467991473533e-17em -0.1em black,
+ -0.020791169081775907em -0.09781476007338058em black,
+ -0.04067366430757999em -0.09135454576426011em black,
+ -0.05877852522924731em -0.08090169943749476em black,
+ -0.07431448254773941em -0.06691306063588585em black,
+ -0.08660254037844384em -0.050000000000000044em black,
+ -0.09510565162951536em -0.030901699437494757em black,
+ -0.09945218953682733em -0.010452846326765424em black,
+ -0.09945218953682733em 0.010452846326765387em black,
+ -0.09510565162951537em 0.030901699437494726em black,
+ -0.08660254037844387em 0.05000000000000002em black,
+ -0.07431448254773941em 0.06691306063588585em black,
+ -0.05877852522924734em 0.08090169943749474em black,
+ -0.040673664307580015em 0.09135454576426011em black,
+ -0.020791169081775987em 0.09781476007338057em black;
+}
diff --git a/ui/client-style/src/props.css b/ui/client-style/src/props.css
new file mode 100644
index 0000000..83b4e79
--- /dev/null
+++ b/ui/client-style/src/props.css
@@ -0,0 +1,60 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+*/
+
+.props p {
+ margin: 0.4em;
+ font-size: small;
+ font-weight: bolder;
+ display: inline-block;
+ padding: 0.2em;
+ background: var(--background-prop);
+ border-radius: 4px;
+}
+.props p.federation {
+ background: rgba(22, 101, 133, 0.603);
+}
+.props p.federation::before {
+ content: "link";
+}
+.props p.watched {
+ background: rgba(75, 175, 44, 0.644);
+}
+.props p.watched::before {
+ content: "check";
+}
+.props p.progress {
+ background: rgba(156, 89, 35, 0.63);
+}
+.props p.progress::before {
+ content: "pending";
+}
+.props p.pending {
+ background: rgba(156, 35, 69, 0.63);
+}
+.props p.pending::before {
+ content: "playlist_add_check";
+}
+.props p.rating {
+ background: rgba(138, 156, 35, 0.63);
+}
+.props p.rating::before {
+ content: "stars";
+}
+.props p.visibility {
+ background-color: rgba(56, 70, 71, 0.63);
+}
+.props p.visibility::before {
+ content: "visibility_off";
+}
+/* .props p.likes::before {
+ content: "thumb_up";
+} */
+
+@media (max-width: 500px) {
+ .props {
+ display: none;
+ }
+} \ No newline at end of file
diff --git a/ui/client-style/src/themes.css b/ui/client-style/src/themes.css
new file mode 100644
index 0000000..18a79b5
--- /dev/null
+++ b/ui/client-style/src/themes.css
@@ -0,0 +1,94 @@
+/*
+ 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) 2026 metamuffin <metamuffin.org>
+*/
+body {
+ --video-brackground: black;
+ --c-danger: rgb(177, 36, 36);
+}
+body.theme-dark {
+ --accent-light: rgb(255, 163, 87);
+ --accent-dark: rgb(199, 90, 0);
+ --c-error: rgb(255, 117, 117);
+ --c-warn: rgb(252, 255, 78);
+ --c-success: rgb(117, 255, 117);
+ --c-nav: #1c1c1c9a;
+ --c-nav-hover: #ffffff10;
+ --c-fade: black;
+ --overlay-poster: #0005;
+ --overlay: #0006;
+ --overlay-hover: #0009;
+ --background-dark: #070707;
+ --background-light: #1c1c1c;
+ --background-very-light: #323232;
+ --background-disable: rgb(128, 128, 128);
+ --background-prop: rgba(50, 50, 50, 0.8);
+ --font: #f1f1f1;
+ --font-dark: rgb(122, 122, 122);
+ --font-highlight: white;
+ --image-loading-placeholder: rgb(50, 50, 50);
+}
+body.theme-light {
+ --accent-light: #e46600;
+ --accent-dark: #ff9036;
+ --c-error: rgb(255, 117, 117);
+ --c-warn: rgb(252, 255, 78);
+ --c-success: rgb(117, 255, 117);
+ --c-nav: #c4c4c4d7;
+ --c-nav-hover: #ffffff10;
+ --c-fade: white;
+ --overlay: rgba(255, 255, 255, 0.267);
+ --overlay-hover: rgba(255, 255, 255, 0.533);
+ --background-dark: #ffffff;
+ --background-light: #c0c0c0;
+ --background-very-light: #b9b9b9;
+ --background-disable: rgb(128, 128, 128);
+ --background-prop: #e9e9e9b2;
+ --font: #0f0f0f;
+ --font-dark: #606060;
+ --font-highlight: black;
+ --image-loading-placeholder: rgb(200, 200, 200);
+}
+body.theme-purple {
+ --accent-light: rgb(191, 87, 255);
+ --accent-dark: rgb(143, 43, 205);
+ --c-error: rgb(255, 117, 117);
+ --c-warn: rgb(252, 255, 78);
+ --c-success: rgb(117, 255, 117);
+ --c-nav: #1c1c1c9a;
+ --c-nav-hover: #ffffff10;
+ --c-fade: black;
+ --overlay: #0005;
+ --overlay-hover: #0008;
+ --background-dark: #070707;
+ --background-light: #1c1c1c;
+ --background-very-light: #323232;
+ --background-disable: rgb(128, 128, 128);
+ --background-prop: rgba(50, 50, 50, 0.8);
+ --font: #f1f1f1;
+ --font-dark: rgb(122, 122, 122);
+ --font-highlight: white;
+ --image-loading-placeholder: rgb(50, 50, 50);
+}
+body.theme-black {
+ --accent-light: hsl(250, 100%, 67%);
+ --accent-dark: hsl(250, 60%, 42%);
+ --c-error: rgb(255, 117, 117);
+ --c-warn: rgb(252, 255, 78);
+ --c-success: rgb(117, 255, 117);
+ --c-nav: #0000009a;
+ --c-nav-hover: #ffffff10;
+ --c-fade: black;
+ --overlay: rgba(114, 114, 114, 0.333);
+ --overlay-hover: rgba(255, 255, 255, 0.533);
+ --background-dark: #000000;
+ --background-light: #000000;
+ --background-very-light: #323232;
+ --background-disable: rgb(128, 128, 128);
+ --background-prop: rgba(50, 50, 50, 0.8);
+ --font: #e8e8e8;
+ --font-dark: rgb(102, 102, 102);
+ --font-highlight: white;
+ --image-loading-placeholder: rgb(20, 20, 20);
+}