Updated styling, formatting, structure of frontend

This commit is contained in:
rockerBOO
2025-04-30 20:04:21 -04:00
parent 18ce902aa0
commit 71a30512c9
60 changed files with 3683 additions and 2451 deletions

9
assets/css/animation.css Normal file
View File

@@ -0,0 +1,9 @@
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,18 +10,18 @@
}
.watch-on-invidious {
font-size: 1.3em !important;
font-size: 1.3em;
font-weight: bold;
white-space: nowrap;
margin: 0 1em 0 1em !important;
margin: 0 1em;
order: 3;
}
/**/
/* .watch-on-invidious > a { */
/* color: white; */
/* } */
.watch-on-invidious > a {
color: white;
}
.watch-on-invidious > a:hover,
.watch-on-invidious > a:focus {
color: rgba(0, 182, 240, 1);;
}
/* .watch-on-invidious > a:hover, */
/* .watch-on-invidious > a:focus { */
/* color: ; */
/* } */

View File

@@ -1,16 +1,32 @@
/* Widget covers the whole page */
#search-widget {
text-align: center;
margin: 20vh 0 50px 0;
position: absolute;
height: 100%;
width: 100%;
display: grid;
justify-content: center;
grid-gap: var(--gap);
top: 0;
left: 0;
pointer-events: none;
}
#logo > h1 {
font-size: 3.5em;
margin: 0;
padding: 0;
.search-homepage {
display: grid;
grid-template-rows: 1fr 1fr;
}
@media screen and (max-width: 1500px) and (max-height: 1000px) {
#logo > h1 {
font-size: 10vmin;
}
#search-widget > h1 {
align-self: flex-end;
font-size: 3em;
text-transform: uppercase;
margin: 0;
padding: 0;
text-align: center;
}
.searchbar {
/* reset pointer events for interactive components */
pointer-events: initial;
}

View File

@@ -1,92 +1,106 @@
.video-js {
font-family: inherit;
font-size: inherit;
}
/* Youtube player style */
.video-js.player-style-youtube .vjs-progress-control {
height: 0;
}
/* .video-js.player-style-youtube .vjs-progress-control { */
/* height: 0; */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, */
/* .video-js.player-style-youtube .vjs-progress-control { */
/* position: absolute; */
/* right: 0; */
/* left: 0; */
/* width: 100%; */
/* margin: 0; */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-control-bar { */
/* background: linear-gradient(rgb(0 0 0 / 10%), rgba(0 0 0 / 50%)); */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-slider { */
/* background-color: rgb(255 255 255 / 20%); */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-load-progress > div { */
/* background-color: rgb(255 255 255 / 20%); */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-play-progress { */
/* background-color: var(--accent-bg-color); */
/* } */
/**/
/* .video-js.player-style-youtube */
/* .vjs-progress-control:hover */
/* .vjs-progress-holder { */
/* font-size: 1em; */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-control-bar > .vjs-spacer { */
/* flex: 1; */
/* order: 2; */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-play-progress .vjs-time-tooltip { */
/* display: none; */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-play-progress::before { */
/* color: var(--accent-bg-color); */
/* font-size: 0.85em; */
/* display: none; */
/* } */
/**/
/* .video-js.player-style-youtube */
/* .vjs-progress-holder:hover */
/* .vjs-play-progress::before { */
/* display: unset; */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-control-bar { */
/* display: flex; */
/* flex-direction: row; */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-big-play-button { */
/* /* */
/* Styles copied from video-js.min.css, definition of */
/* .vjs-big-play-centered .vjs-big-play-button */
/* */
*/
/* top: 50%; */
/* left: 50%; */
/* margin-top: -0.8167em; */
/* margin-left: -1.5em; */
/* } */
.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {
position: absolute;
right: 0;
left: 0;
width: 100%;
margin: 0;
}
/* .video-js.player-style-youtube .vjs-menu-button-popup .vjs-menu { */
/* margin-bottom: 2em; */
/* padding-top: 2em; */
/* } */
/**/
/* .video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, */
/* .video-js.player-style-youtube .vjs-progress-control { */
/* height: 0.3em; */
/* margin-bottom: 0.6em; */
/* } */
/**/
/* ul.vjs-menu-content::-webkit-scrollbar { */
/* display: none; */
/* } */
/**/
/* .vjs-user-inactive { */
/* cursor: none; */
/* } */
.video-js.player-style-youtube .vjs-control-bar {
background: linear-gradient(rgba(0,0,0,0.1), rgba(0, 0, 0,0.5));
}
.video-js.player-style-youtube .vjs-slider {
background-color: rgba(255,255,255,0.2);
}
.video-js.player-style-youtube .vjs-load-progress > div {
background-color: rgba(255,255,255,0.5);
}
.video-js.player-style-youtube .vjs-play-progress {
background-color: red;
}
.video-js.player-style-youtube .vjs-progress-control:hover .vjs-progress-holder {
font-size: 15px;
}
.video-js.player-style-youtube .vjs-control-bar > .vjs-spacer {
flex: 1;
order: 2;
}
.video-js.player-style-youtube .vjs-play-progress .vjs-time-tooltip {
display: none;
}
.video-js.player-style-youtube .vjs-play-progress::before {
color: red;
font-size: 0.85em;
display: none;
}
.video-js.player-style-youtube .vjs-progress-holder:hover .vjs-play-progress::before {
display: unset;
}
.video-js.player-style-youtube .vjs-control-bar {
display: flex;
flex-direction: row;
}
.video-js.player-style-youtube .vjs-big-play-button {
/*
Styles copied from video-js.min.css, definition of
.vjs-big-play-centered .vjs-big-play-button
*/
top: 50%;
left: 50%;
margin-top: -0.81666em;
margin-left: -1.5em;
}
.video-js.player-style-youtube .vjs-menu-button-popup .vjs-menu {
margin-bottom: 2em;
padding-top: 2em
}
.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {height: 5px;
margin-bottom: 10px;}
ul.vjs-menu-content::-webkit-scrollbar {
display: none;
}
.vjs-user-inactive {
cursor: none;
}
.video-js .vjs-text-track-display > div > div > div {
background-color: rgba(0, 0, 0, 0.75) !important;
border-radius: 9px !important;
padding: 5px !important;
}
/* .video-js .vjs-text-track-display > div > div > div { */
/* background-color: rgb(0 0 0 / 75%); */
/* border-radius: 0.5em; */
/* padding: 0.3em; */
/* } */
.vjs-play-control,
.vjs-volume-panel,
@@ -123,143 +137,252 @@ ul.vjs-menu-content::-webkit-scrollbar {
order: 7;
}
.vjs-playback-rate > .vjs-menu {
width: 50px;
/* .vjs-playback-rate > .vjs-menu { */
/* width: 3.2em; */
/* } */
/**/
/* Make the video relative, instead of absolute, so that
the parent container will size based on the video. Also,
note the max-height rule. Note the line-height 0 is to prevent
a small artifact on the bottom of the video.
https://stackoverflow.com/questions/46747320/limit-the-height-in-videojs-in-fluid-mode/47039499#47039499
*/
.video-js.vjs-fluid,
.video-js.vjs-16-9,
.video-js.vjs-4-3,
video.video-js,
video.vjs-tech {
max-height: 85vh;
position: relative !important;
width: 100%;
height: auto;
max-width: 100% !important;
padding-top: 0 !important;
line-height: 0;
}
.vjs-control-bar {
.video-js.vjs-16-9 {
/* Keep the video spaced to fit */
aspect-ratio: 16 / 9;
}
.video-js.vjs-4-3 {
/* Keep the video spaced to fit */
aspect-ratio: 4 / 3;
}
#player {
/* Default to 16/9 video spacing */
aspect-ratio: 16 / 9;
max-width: 100%;
}
.vjs-error .vjs-error-display:before,
.video-js .vjs-volume-tooltip,
.video-js .vjs-time-tooltip,
.vjs-menu .vjs-menu-content {
font-family: inherit;
}
.vjs-menu-button-popup .vjs-menu .vjs-menu-content {
background-color: var(--secondary-bg-color-dark);
}
.vjs-menu li.vjs-menu-item:focus,
.vjs-menu li.vjs-menu-item:hover,
.js-focus-visible .vjs-menu li.vjs-menu-item:hover {
background-color: var(--accent-bg-color-dark);
}
.video-js .vjs-slider {
background-color: rgb(166 166 166 / 50%);
}
.video-js .vjs-load-progress {
background-color: rgb(227 227 227 / 75%);
}
@media (max-width: 480px) {
#player {
/* Default to 16/9 video spacing */
aspect-ratio: unset;
}
}
.vjs-fullscreen video {
max-height: 100vh !important;
}
.video-js .vjs-control-bar {
display: flex;
flex-direction: row;
scrollbar-width: none;
/* Fix the control bar due to us resetting the line-height on the video-js */
line-height: 1;
background: linear-gradient(
to bottom,
transparent 0%,
var(--secondary-bg-color-dark) 50%
);
}
.vjs-control-bar button:hover,
.vjs-control-bar button:focus {
outline-width: 0;
}
.vjs-control-bar::-webkit-scrollbar {
display: none;
}
.video-js .vjs-icon-cog {
font-size: 18px;
.vjs-playback-rate .vjs-playback-rate-value {
font-size: 1em;
line-height: 3rem;
}
.video-js .vjs-control-bar,
.vjs-menu-button-popup .vjs-menu .vjs-menu-content {
background-color: rgba(35, 35, 35, 0.75);
.vjs-button > .vjs-icon-placeholder::before {
font-size: 1.25em;
line-height: 3rem;
}
.vjs-menu li.vjs-menu-item:focus,
.vjs-menu li.vjs-menu-item:hover {
background-color: rgba(255, 255, 255, 0.75);
color: rgba(49, 49, 51, 0.75);
.vjs-menu li {
font-size: 1em;
}
.vjs-menu li.vjs-selected,
.vjs-menu li.vjs-selected:focus,
.vjs-menu li.vjs-selected:hover {
background-color: rgba(0, 182, 240, 0.75);
.video-js .vjs-control {
width: 3em;
}
/* Progress Bar */
.video-js .vjs-slider {
background-color: rgba(15, 15, 15, 0.5);
.video-js .vjs-time-control {
width: auto;
}
.video-js .vjs-load-progress,
.video-js .vjs-load-progress div {
background: rgba(87, 87, 88, 1);
.vjs-poster {
background-size: cover;
}
.video-js .vjs-share__short-link-wrapper {
color: var(--fg-color-dark);
background-color: var(--secondary-bg-color-dark);
height: 2em;
margin: 0 auto;
margin-bottom: var(--secondary-gap);
border-radius: var(--radius);
}
.video-js .vjs-share__short-link {
padding: var(--secondary-gap);
background-color: var(--secondary-bg-color-dark);
color: var(--fg-color-dark);
font-family: var(--monospace);
min-width: 15em;
}
.video-js .vjs-share__btn {
background-color: var(--secondary-bg-color-dark);
color: var(--fg-color-dark);
height: 2em;
width: 2.25em;
padding: var(--secondary-gap);
}
.video-js .vjs-share__title {
font-size: 1.25em;
color: var(--fg-color-dark);
}
.video-js .vjs-share__subtitle {
font-size: 1em;
color: var(--fg-color-dark);
margin: 0 auto var(--secondary-gap);
}
.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button {
height: var(--gap);
}
.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button::before {
content: "";
}
.video-js .vjs-control.vjs-close-button .vjs-icon-placeholder::before {
line-height: 1.67;
}
.video-js .vjs-share__middle {
padding: 0 var(--secondary-gap);
}
.vjs-modal-dialog .vjs-modal-dialog-content {
font-size: 1em;
line-height: 1.1;
}
/**/
/* .video-js .vjs-icon-cog { */
/* font-size: 1em; */
/* } */
/**/
/* .video-js .vjs-control-bar, */
/* .vjs-menu-button-popup .vjs-menu .vjs-menu-content { */
/* background-color: rgb(35 35 35 / 75%); */
/* } */
/**/
/* .vjs-menu li.vjs-menu-item:focus, */
/* .vjs-menu li.vjs-menu-item:hover { */
/* background-color: rgb(255 255 255 / 75%); */
/* color: rgb(49 49 51 / 75%); */
/* } */
/**/
/* .vjs-menu li.vjs-selected, */
/* .vjs-menu li.vjs-selected:focus, */
/* .vjs-menu li.vjs-selected:hover { */
/* background-color: rgb(0 182 240 / 75%); */
/* } */
/**/
/* /* Progress Bar */
*/
/* .video-js .vjs-slider { */
/* background-color: rgb(15 15 15 / 50%); */
/* } */
/**/
/* .video-js .vjs-load-progress, */
/* .video-js .vjs-load-progress div { */
/* background: rgb(87 87 88); */
/* } */
/**/
.video-js .vjs-slider:hover,
.video-js button:hover {
color: rgba(0, 182, 240, 1);
background-color: var(--accent-bg-color);
}
.video-js.player-style-invidious .vjs-play-progress {
background-color: rgba(0, 182, 240, 1);
background-color: var(--accent-bg-color);
}
/* Overlay */
.video-js .vjs-overlay {
background-color: rgba(35, 35, 35, 0.75) !important;
}
.video-js .vjs-overlay * {
color: rgba(255, 255, 255, 1) !important;
text-align: center;
.vjs-modal-dialog .vjs-modal-dialog-content {
padding: var(--gap);
}
/* ProgressBar marker */
.vjs-marker {
background-color: rgba(255, 255, 255, 1);
z-index: 0;
}
/* Big "Play" Button */
.video-js .vjs-big-play-button {
background-color: rgba(35, 35, 35, 0.5);
font-size: var(--gap);
line-height: var(--gap);
height: var(--gap);
width: calc(var(--gap) * 2);
top: var(--secondary-gap);
left: var(--secondary-gap);
background-color: var(--secondary-bg-color);
border: none;
opacity: 0.7;
transition: opacity 240ms;
}
.video-js:hover .vjs-big-play-button {
background-color: rgba(35, 35, 35, 0.75);
.video-js .vjs-big-play-button:hover {
opacity: 0.9;
}
.video-js .vjs-current-time,
.video-js .vjs-time-divider,
.video-js .vjs-duration {
display: block;
}
.video-js .vjs-time-divider {
min-width: 0px;
padding-left: 0px;
padding-right: 0px;
}
.video-js .vjs-poster {
background-size: cover;
object-fit: cover;
}
.player-dimensions.vjs-fluid {
padding-top: 82vh;
}
video.video-js {
position: absolute;
height: 100%;
}
#player-container {
position: relative;
padding-left: 0;
padding-right: 0;
margin-left: 1em;
margin-right: 1em;
padding-bottom: 82vh;
height: 0;
}
.mobile-operations-bar {
display: flex;
position: absolute;
top: 0;
right: 1px !important;
left: initial !important;
width: initial !important;
}
.mobile-operations-bar ul {
position: absolute !important;
bottom: unset !important;
top: 1.5em;
}
@media screen and (max-width: 700px) {
.video-js .vjs-share {
justify-content: unset;
}
}
@media screen and (max-width: 650px) {
.vjs-modal-dialog-content {
overflow-x: hidden;
}
.vjs-control-bar {
background-color: var(--secondary-bg-color-dark);
}

73
assets/css/pure-fix.css Normal file
View File

@@ -0,0 +1,73 @@
/** fixes for pure to support our colors */
.pure-form input[type="color"],
.pure-form input[type="date"],
.pure-form input[type="datetime-local"],
.pure-form input[type="datetime"],
.pure-form input[type="email"],
.pure-form input[type="month"],
.pure-form input[type="number"],
.pure-form input[type="password"],
.pure-form input[type="search"],
.pure-form input[type="tel"],
.pure-form input[type="text"],
.pure-form input[type="time"],
.pure-form input[type="url"],
.pure-form input[type="week"],
.pure-form select,
.pure-form textarea {
font-size: inherit;
padding: var(--secondary-gap);
color: var(--fg-color);
background-color: var(--secondary-bg-color);
border: 1px solid var(--secondary-bg-color);
box-shadow: unset;
border-radius: var(--radius);
}
.pure-menu-heading,
.pure-g,
.pure-g [class*="pure-u"] {
font-family: inherit;
letter-spacing: initial;
}
.pure-form legend {
color: var(--fg-color);
border-bottom: 1px solid var(--accent-color);
}
legend {
border: initial;
padding: initial;
}
.pure-button {
font-family: inherit;
font-size: inherit;
padding: 0;
color: currentcolor;
border: none;
border: none transparent;
background-color: transparent;
text-decoration: none;
border-radius: inherit;
}
.pure-button-hover,
.pure-button:focus,
.pure-button:hover {
background-image: none;
}
/* Wider settings name to less word wrap */
.pure-form-aligned .pure-control-group label {
width: 19em;
}
.pure-menu-heading {
color: var(--accent-color);
}
a:active, a:hover {
outline: inherit;
}

View File

@@ -1,121 +1,111 @@
summary {
/* This should hide the marker */
display: block;
font-size: 1.17em;
font-weight: bold;
margin: 0 auto 10px auto;
cursor: pointer;
display: block;
font-size: 1.17em;
margin: 0 auto 0.625em;
cursor: pointer;
}
summary::-webkit-details-marker,
summary::marker { display: none; }
summary:before {
border-radius: 5px;
content: "[ + ]";
margin: -2px 10px 0 10px;
padding: 1px 0 3px 0;
text-align: center;
width: 40px;
summary::marker {
display: none;
}
details[open] > summary:before { content: "[ ]"; }
#filters-box {
padding: 10px 20px 20px 10px;
margin: 10px 15px;
summary::before {
content: "+ ";
text-align: center;
}
#filters-flex {
details[open] > summary::before {
content: " ";
}
.filters {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(12em, 1fr));
grid-gap: var(--secondary-gap);
margin: var(--gap) 0;
}
.filters fieldset {
display: grid;
grid-gap: var(--secondary-gap);
align-content: baseline;
}
.filters fieldset div {
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-items: flex-start;
align-content: flex-start;
justify-content: flex-start;
gap: var(--secondary-gap);
}
fieldset, legend {
display: contents !important;
border: none !important;
margin: 0 !important;
padding: 0 !important;
}
.filter-column {
display: inline-block;
display: inline-flex;
width: max-content;
min-width: max-content;
max-width: 16em;
margin: 15px;
flex-grow: 2;
flex-basis: auto;
flex-direction: column;
}
.filter-name, .filter-options {
display: block;
padding: 5px 10px;
margin: 0;
text-align: start;
}
.filter-options div { margin: 6px 0; }
.filter-options div * { vertical-align: middle; }
.filter-options label { margin: 0 10px; }
#filters-apply {
text-align: right; /* IE11 only */
text-align: end; /* Override for compatible browsers */
}
/* #filters-box { */
/* background-color: var(--secondary-bg-color); */
/* } */
/**/
/* #filters-flex { */
/* display: flex; */
/* flex-flow: row wrap; */
/* align-items: flex-start; */
/* place-content: flex-start flex-start; */
/* } */
/**/
/* .filter-column { */
/* display: inline-block; */
/* display: inline-flex; */
/* width: max-content; */
/* min-width: max-content; */
/* max-width: 16em; */
/* margin: 0.9375rem; */
/* flex-grow: 2; */
/* flex-basis: auto; */
/* flex-direction: column; */
/* } */
/**/
/* .filter-name, */
/* .filter-options { */
/* display: block; */
/* padding: 0.3125rem 0.625rem; */
/* margin: 0; */
/* text-align: start; */
/* } */
/**/
/* .filter-options div { */
/* margin: 0.375rem 0; */
/* } */
/**/
/* .filter-options div * { */
/* vertical-align: middle; */
/* } */
/**/
/* .filter-options label { */
/* margin: 0 0.625rem; */
/* } */
/**/
/* #filters-apply { */
/* text-align: right; /* IE11 only */ */
/* text-align: end; /* Override for compatible browsers */ */
/* } */
/* Error message */
.no-results-error {
text-align: center;
line-height: 180%;
font-size: 110%;
padding: 15px 15px 125px 15px;
text-align: center;
font-size: 1.1em;
padding: 1em 1em 8em;
}
/* Responsive rules */
@media only screen and (max-width: 800px) {
summary { font-size: 1.30em; }
#filters-box {
margin: 10px 0 0 0;
padding: 0;
}
#filters-apply {
text-align: center;
padding: 15px;
}
}
/* Light theme */
.light-theme #filters-box {
background: #dfdfdf;
}
@media (prefers-color-scheme: light) {
.no-theme #filters-box {
background: #dfdfdf;
}
}
/* Dark theme */
.dark-theme #filters-box {
background: #373737;
}
@media (prefers-color-scheme: dark) {
.no-theme #filters-box {
background: #373737;
}
}
/* @media only screen and (max-width: 50px) { */
/* summary { */
/* font-size: 1.3em; */
/* } */
/**/
/* #filters-box { */
/* margin: 0.6em 0 0; */
/* padding: 0; */
/* } */
/**/
/* #filters-apply { */
/* text-align: center; */
/* padding: 1em; */
/* } */
/* } */
/**/

View File

@@ -0,0 +1,16 @@
:root {
--fg-color-dark: #f8f8f2;
--bg-color-dark: #eff1f5;
--accent-color-dark: #fff;
--accent-bg-color-dark: #181926;
--secondary-color-dark: #e3e3e3;
--secondary-bg-color-dark: #494d64;
/* light theme colors */
--fg-color-light: #4c4f69;
--bg-color-light: #eff1f5;
--accent-color-light: #bcc0cc;
--accent-bg-color-light: #7287fd;
--secondary-color-light: #5c5f77;
--secondary-bg-color-light: #e6e9ef;
}

View File

@@ -0,0 +1,16 @@
:root {
--fg-color-dark: #f8f8f2;
--bg-color-dark: #24273a;
--accent-color-dark: #fff;
--accent-bg-color-dark: #181926;
--secondary-color-dark: #e3e3e3;
--secondary-bg-color-dark: #494d64;
/* light theme colors */
--fg-color-light: black;
--bg-color-light: #eee;
--accent-color-light: #3a3a3a;
--accent-bg-color-light: #008bec;
--secondary-color-light: #424242;
--secondary-bg-color-light: #d9d9d9;
}

View File

@@ -0,0 +1,16 @@
:root {
--fg-color-dark: #f8f8f2;
--bg-color-dark: #282a36;
--accent-color-dark: #fff;
--accent-bg-color-dark: #44475a;
--secondary-color-dark: #e3e3e3;
--secondary-bg-color-dark: #21222C;
/* light theme colors */
--fg-color-light: black;
--bg-color-light: #eee;
--accent-color-light: #3a3a3a;
--accent-bg-color-light: #008bec;
--secondary-color-light: #424242;
--secondary-bg-color-light: #d9d9d9;
}

View File

@@ -0,0 +1,16 @@
:root {
--fg-color-dark: #f8f8f2;
--bg-color-dark: #1a1b26;
--accent-color-dark: #fff;
--accent-bg-color-dark: #3e4f80;
--secondary-color-dark: #e3e3e3;
--secondary-bg-color-dark: #2e2e3e;
/* light theme colors */
--fg-color-light: black;
--bg-color-light: #eee;
--accent-color-light: #3a3a3a;
--accent-bg-color-light: #008bec;
--secondary-color-light: #424242;
--secondary-bg-color-light: #d9d9d9;
}

59
assets/css/theme.css Normal file
View File

@@ -0,0 +1,59 @@
:root {
--fg-color-dark: #f0f0f0;
--bg-color-dark: #131313;
--accent-color-dark: #27a6ff;
--accent-bg-color-dark: #004a7e;
--secondary-color-dark: #e3e3e3;
--secondary-bg-color-dark: #313131;
--watched-overlay-color-dark: rgb(0 0 0 / 40%);
/* light theme colors */
--fg-color-light: black;
--bg-color-light: #eee;
--accent-color-light: #044c99;
--accent-bg-color-light: #3eaefd;
--secondary-color-light: #404040;
--secondary-bg-color-light: #dbdbdb;
--watched-overlay-color-light: rgb(255 255 255 / 40%);
/** apply default colors to dark */
--fg-color: var(--fg-color-dark);
--bg-color: var(--bg-color-dark);
--accent-color: var(--accent-color-dark);
--accent-bg-color: var(--accent-bg-color-dark);
--secondary-color: var(--secondary-color-dark);
--secondary-bg-color: var(--secondary-bg-color-dark);
--watched-overlay-color: var(--watched-overlay-color-dark);
}
@media (prefers-color-scheme: light) {
:root {
--fg-color: var(--fg-color-light);
--bg-color: var(--bg-color-light);
--accent-color: var(--accent-color-light);
--accent-bg-color: var(--accent-bg-color-light);
--secondary-color: var(--secondary-color-light);
--secondary-bg-color: var(--secondary-bg-color-light);
--watched-overlay-color: var(--watched-overlay-color-dark);
}
}
.light-theme {
--fg-color: var(--fg-color-light);
--bg-color: var(--bg-color-light);
--accent-color: var(--accent-color-light);
--accent-bg-color: var(--accent-bg-color-light);
--secondary-color: var(--secondary-color-light);
--secondary-bg-color: var(--secondary-bg-color-light);
--watched-overlay-color: var(--watched-overlay-color-light);
}
.dark-theme {
--fg-color: var(--fg-color-dark);
--bg-color: var(--bg-color-dark);
--accent-color: var(--accent-color-dark);
--accent-bg-color: var(--accent-bg-color-dark);
--secondary-color: var(--secondary-color-dark);
--secondary-bg-color: var(--secondary-bg-color-dark);
--watched-overlay-color: var(--watched-overlay-color-dark);
}

View File

@@ -1,6 +1,6 @@
var video_data = JSON.parse(document.getElementById('video_data').textContent);
var spinnerHTML = '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
var spinnerHTML = '<div class="loading"><i class="icon ion-ios-refresh"></i></div>';
var spinnerHTMLwithHR = spinnerHTML + '<hr>';
String.prototype.supplant = function (o) {
@@ -11,14 +11,14 @@ String.prototype.supplant = function (o) {
};
function toggle_comments(event) {
var target = event.target;
var body = target.parentNode.parentNode.parentNode.children[1];
if (body.style.display === 'none') {
target.textContent = '[ ]';
body.style.display = '';
const target = event.target;
const comments = document.querySelector(".comments");
if (comments.style.display === 'none') {
target.textContent = '';
comments.style.display = '';
} else {
target.textContent = '[ + ]';
body.style.display = 'none';
target.textContent = '+';
comments.style.display = 'none';
}
}
@@ -39,6 +39,7 @@ function hide_youtube_replies(event) {
function show_youtube_replies(event) {
var target = event.target;
console.log(target);
var sub_text = target.getAttribute('data-inner-text');
var inner_text = target.getAttribute('data-sub-text');
@@ -75,23 +76,24 @@ function get_youtube_comments() {
helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, {
on200: function (response) {
var commentInnerHtml = ' \
<div> \
<h3> \
<a href="javascript:void(0)">[ ]</a> \
<nav class="comments-header"> \
<ul> \
<li> \
<button class="secondary" id="toggle-comments"></button> \
{commentsText} \
</h3> \
<b> \
'
</li> \
\
<li>'
if (video_data.support_reddit) {
commentInnerHtml += ' <a href="javascript:void(0)" data-comments="reddit"> \
commentInnerHtml += ' <button data-comments="reddit"> \
{redditComments} \
</a> \
</button> \
'
}
commentInnerHtml += ' </b> \
</div> \
<div>{contentHtml}</div> \
<hr>'
commentInnerHtml += ' </li> \
</ul> \
</nav> \
<div class="comments">{contentHtml}</div>'
commentInnerHtml = commentInnerHtml.supplant({
contentHtml: response.contentHtml,
redditComments: video_data.reddit_comments_text,
@@ -104,9 +106,9 @@ function get_youtube_comments() {
})
});
comments.innerHTML = commentInnerHtml;
comments.children[0].children[0].children[0].onclick = toggle_comments;
document.getElementById("toggle-comments").onclick = toggle_comments;
if (video_data.support_reddit) {
comments.children[0].children[1].children[0].onclick = swap_comments;
comments.children[1].children[1].onclick = swap_comments;
}
},
onNon200: onNon200, // declared above
@@ -122,7 +124,7 @@ function get_youtube_comments() {
function get_youtube_replies(target, load_more, load_replies) {
var continuation = target.getAttribute('data-continuation');
var body = target.parentNode.parentNode;
var body = target.parentNode;
var fallback = body.innerHTML;
body.innerHTML = spinnerHTML;
var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id
@@ -140,26 +142,24 @@ function get_youtube_replies(target, load_more, load_replies) {
helpers.xhr('GET', url, {}, {
on200: function (response) {
if (load_more) {
body = body.parentNode.parentNode;
body = body.parentNode;
body.removeChild(body.lastElementChild);
body.insertAdjacentHTML('beforeend', response.contentHtml);
} else {
body.removeChild(body.lastElementChild);
var p = document.createElement('p');
var a = document.createElement('a');
p.appendChild(a);
var div = document.createElement('div');
var button = document.createElement('button');
div.appendChild(button);
a.href = 'javascript:void(0)';
a.onclick = hide_youtube_replies;
a.setAttribute('data-sub-text', video_data.hide_replies_text);
a.setAttribute('data-inner-text', video_data.show_replies_text);
a.textContent = video_data.hide_replies_text;
button.onclick = hide_youtube_replies;
button.setAttribute('data-sub-text', video_data.hide_replies_text);
button.setAttribute('data-inner-text', video_data.show_replies_text);
button.textContent = video_data.hide_replies_text;
var div = document.createElement('div');
div.innerHTML = response.contentHtml;
body.appendChild(p);
body.appendChild(div);
}
},
@@ -171,4 +171,4 @@ function get_youtube_replies(target, load_more, load_replies) {
body.innerHTML = fallback;
}
});
}
}

View File

@@ -58,13 +58,13 @@
el.onclick = function () { mark_unwatched(el); };
});
document.querySelectorAll('[data-onclick="add_playlist_video"]').forEach(function (el) {
el.onclick = function () { add_playlist_video(el); };
el.onclick = function (e) { add_playlist_video(e); };
});
document.querySelectorAll('[data-onclick="add_playlist_item"]').forEach(function (el) {
el.onclick = function () { add_playlist_item(el); };
el.onclick = function (e) { add_playlist_item(e); };
});
document.querySelectorAll('[data-onclick="remove_playlist_item"]').forEach(function (el) {
el.onclick = function () { remove_playlist_item(el); };
el.onclick = function (e) { remove_playlist_item(e); };
});
document.querySelectorAll('[data-onclick="revoke_token"]').forEach(function (el) {
el.onclick = function () { revoke_token(el); };

View File

@@ -1,131 +1,197 @@
'use strict';
var notification_data = JSON.parse(document.getElementById('notification_data').textContent);
"use strict";
var notification_data = JSON.parse(
document.getElementById("notification_data").textContent,
);
/** Boolean meaning 'some tab have stream' */
const STORAGE_KEY_STREAM = 'stream';
const STORAGE_KEY_STREAM = "stream";
/** Number of notifications. May be increased or reset */
const STORAGE_KEY_NOTIF_COUNT = 'notification_count';
const STORAGE_KEY_NOTIF_COUNT = "notification_count";
var notifications, delivered;
var notifications_mock = { close: function () { } };
var notifications_mock = { close: function () {} };
function get_subscriptions() {
helpers.xhr('GET', '/api/v1/auth/subscriptions', {
async function get_subscriptions_call() {
return new Promise((resolve) => {
helpers.xhr(
"GET",
"/api/v1/auth/subscriptions",
{
retries: 5,
entity_name: 'subscriptions'
}, {
on200: create_notification_stream
});
entity_name: "subscriptions",
},
{
on200: function (subscriptions) {
create_notification_stream(subscriptions);
resolve(subscriptions);
},
},
);
});
}
// Start the retry mechanism
const get_subscriptions = exponential_backoff(get_subscriptions_call, 100, 1000);
function create_notification_stream(subscriptions) {
// sse.js can't be replaced to EventSource in place as it lack support of payload and headers
// see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource
notifications = new SSE(
'/api/v1/auth/notifications', {
withCredentials: true,
payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId; }).join(','),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
delivered = [];
// sse.js can't be replaced to EventSource in place as it lack support of payload and headers
// see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource
notifications = new SSE("/api/v1/auth/notifications", {
withCredentials: true,
payload:
"topics=" +
subscriptions
.map(function (subscription) {
return subscription.authorId;
})
.join(","),
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
delivered = [];
var start_time = Math.round(new Date() / 1000);
var start_time = Math.round(new Date() / 1000);
notifications.onmessage = function (event) {
if (!event.id) return;
notifications.onmessage = function (event) {
if (!event.id) return;
var notification = JSON.parse(event.data);
console.info('Got notification:', notification);
var notification = JSON.parse(event.data);
console.info("Got notification:", notification);
// Ignore not actual and delivered notifications
if (start_time > notification.published || delivered.includes(notification.videoId)) return;
// Ignore not actual and delivered notifications
if (
start_time > notification.published ||
delivered.includes(notification.videoId)
)
return;
delivered.push(notification.videoId);
delivered.push(notification.videoId);
let notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0;
notification_count++;
helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count);
let notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0;
notification_count++;
helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count);
update_ticker_count();
update_ticker_count();
// permission for notifications handled on settings page. JS handler is in handlers.js
if (window.Notification && Notification.permission === 'granted') {
var notification_text = notification.liveNow ? notification_data.live_now_text : notification_data.upload_text;
notification_text = notification_text.replace('`x`', notification.author);
// permission for notifications handled on settings page. JS handler is in handlers.js
if (window.Notification && Notification.permission === "granted") {
var notification_text = notification.liveNow
? notification_data.live_now_text
: notification_data.upload_text;
notification_text = notification_text.replace("`x`", notification.author);
var system_notification = new Notification(notification_text, {
body: notification.title,
icon: '/ggpht' + new URL(notification.authorThumbnails[2].url).pathname,
img: '/ggpht' + new URL(notification.authorThumbnails[4].url).pathname
});
var system_notification = new Notification(notification_text, {
body: notification.title,
icon: "/ggpht" + new URL(notification.authorThumbnails[2].url).pathname,
img: "/ggpht" + new URL(notification.authorThumbnails[4].url).pathname,
});
system_notification.onclick = function (e) {
open('/watch?v=' + notification.videoId, '_blank');
};
}
};
system_notification.onclick = function (e) {
open("/watch?v=" + notification.videoId, "_blank");
};
}
};
notifications.addEventListener('error', function (e) {
console.warn('Something went wrong with notifications, trying to reconnect...');
notifications = notifications_mock;
setTimeout(get_subscriptions, 1000);
});
notifications.addEventListener("error", function (e) {
console.warn(
"Something went wrong with notifications, trying to reconnect...",
);
notifications = notifications_mock;
notifications.stream();
});
notifications.stream();
}
function update_ticker_count() {
var notification_ticker = document.getElementById('notification_ticker');
var notification_ticker = document.getElementById("notification_ticker");
const notification_count = helpers.storage.get(STORAGE_KEY_STREAM);
if (notification_count > 0) {
notification_ticker.innerHTML =
'<span id="notification_count">' + notification_count + '</span> <i class="icon ion-ios-notifications"></i>';
} else {
notification_ticker.innerHTML =
'<i class="icon ion-ios-notifications-outline"></i>';
}
const notification_count = helpers.storage.get(STORAGE_KEY_STREAM);
if (notification_count > 0) {
notification_ticker.innerHTML =
'<span id="notification_count">' +
notification_count +
'</span> <i class="icon ion-ios-notifications"></i>';
} else {
notification_ticker.innerHTML =
'<i class="icon ion-ios-notifications-outline"></i>';
}
}
function start_stream_if_needed() {
// random wait for other tabs set 'stream' flag
setTimeout(function () {
if (!helpers.storage.get(STORAGE_KEY_STREAM)) {
// if no one set 'stream', set it by yourself and start stream
helpers.storage.set(STORAGE_KEY_STREAM, true);
notifications = notifications_mock;
get_subscriptions();
}
}, Math.random() * 1000 + 50); // [0.050 .. 1.050) second
// random wait for other tabs set 'stream' flag
setTimeout(
function () {
if (!helpers.storage.get(STORAGE_KEY_STREAM)) {
// if no one set 'stream', set it by yourself and start stream
helpers.storage.set(STORAGE_KEY_STREAM, true);
notifications = notifications_mock;
get_subscriptions();
}
},
Math.random() * 1000 + 50,
); // [0.050 .. 1.050) second
}
addEventListener("storage", function (e) {
if (e.key === STORAGE_KEY_NOTIF_COUNT) update_ticker_count();
addEventListener('storage', function (e) {
if (e.key === STORAGE_KEY_NOTIF_COUNT)
update_ticker_count();
// if 'stream' key was removed
if (e.key === STORAGE_KEY_STREAM && !helpers.storage.get(STORAGE_KEY_STREAM)) {
if (notifications) {
// restore it if we have active stream
helpers.storage.set(STORAGE_KEY_STREAM, true);
} else {
start_stream_if_needed();
}
// if 'stream' key was removed
if (
e.key === STORAGE_KEY_STREAM &&
!helpers.storage.get(STORAGE_KEY_STREAM)
) {
if (notifications) {
// restore it if we have active stream
helpers.storage.set(STORAGE_KEY_STREAM, true);
} else {
start_stream_if_needed();
}
}
});
addEventListener('load', function () {
var notification_count_el = document.getElementById('notification_count');
var notification_count = notification_count_el ? parseInt(notification_count_el.textContent) : 0;
helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count);
addEventListener("load", function () {
var notification_count_el = document.getElementById("notification_count");
var notification_count = notification_count_el
? parseInt(notification_count_el.textContent)
: 0;
helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count);
if (helpers.storage.get(STORAGE_KEY_STREAM))
helpers.storage.remove(STORAGE_KEY_STREAM);
start_stream_if_needed();
if (helpers.storage.get(STORAGE_KEY_STREAM))
helpers.storage.remove(STORAGE_KEY_STREAM);
start_stream_if_needed();
});
addEventListener('unload', function () {
// let chance to other tabs to be a streamer via firing 'storage' event
if (notifications) helpers.storage.remove(STORAGE_KEY_STREAM);
addEventListener("unload", function () {
// let chance to other tabs to be a streamer via firing 'storage' event
if (notifications) helpers.storage.remove(STORAGE_KEY_STREAM);
});
function exponential_backoff(
fn,
maxRetries = 5,
initialDelay = 1000,
randomnessFactor = 0.5,
) {
let attempt = 0;
return function tryFunction() {
fn()
.then((response) => {
attempt = 0;
})
.catch((error) => {
if (attempt < maxRetries) {
attempt++;
let delay = initialDelay * Math.pow(2, attempt); // Exponential backoff
let randomMultiplier = 1 + Math.random() * randomnessFactor;
delay = delay * randomMultiplier;
console.log(
`Attempt ${attempt} failed. Retrying in ${(delay / 1000).toPrecision(2)} seconds...`,
);
setTimeout(tryFunction, delay); // Retry after delay
} else {
console.log("Max retries reached. Operation failed:", error);
}
});
}
}

View File

@@ -2,8 +2,9 @@
var playlist_data = JSON.parse(document.getElementById('playlist_data').textContent);
var payload = 'csrf_token=' + playlist_data.csrf_token;
function add_playlist_video(target) {
var select = target.parentNode.children[0].children[1];
function add_playlist_video(event) {
const target = event.target;
var select = document.querySelector("#playlists");
var option = select.children[select.selectedIndex];
var url = '/playlist_ajax?action_add_video=1&redirect=false' +
@@ -12,37 +13,43 @@ function add_playlist_video(target) {
helpers.xhr('POST', url, {payload: payload}, {
on200: function (response) {
option.textContent = '✓' + option.textContent;
option.textContent = '✓ ' + option.textContent;
}
});
}
function add_playlist_item(target) {
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
tile.style.display = 'none';
function add_playlist_item(event) {
event.preventDefault();
const target = event.target;
const video_id = target.getAttribute('data-id');
var card = document.querySelector(`#video-card-${video_id}`);
card.classList.add("hide");
var url = '/playlist_ajax?action_add_video=1&redirect=false' +
'&video_id=' + target.getAttribute('data-id') +
'&video_id=' + video_id +
'&playlist_id=' + target.getAttribute('data-plid');
helpers.xhr('POST', url, {payload: payload}, {
onNon200: function (xhr) {
tile.style.display = '';
card.classList.remove("hide");
}
});
}
function remove_playlist_item(target) {
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
tile.style.display = 'none';
function remove_playlist_item(event) {
event.preventDefault();
const target = event.target;
const video_index = target.getAttribute('data-index');
const card = document.querySelector(`.video-card [data-index="${video_index}"]`)
card.classList.add("hide");
var url = '/playlist_ajax?action_remove_video=1&redirect=false' +
'&set_video_id=' + target.getAttribute('data-index') +
'&set_video_id=' + video_index +
'&playlist_id=' + target.getAttribute('data-plid');
helpers.xhr('POST', url, {payload: payload}, {
onNon200: function (xhr) {
tile.style.display = '';
card.classList.remove("hide");
}
});
}

View File

@@ -3,7 +3,6 @@ var subscribe_data = JSON.parse(document.getElementById('subscribe_data').textCo
var payload = 'csrf_token=' + subscribe_data.csrf_token;
var subscribe_button = document.getElementById('subscribe');
subscribe_button.parentNode.action = 'javascript:void(0)';
if (subscribe_button.getAttribute('data-type') === 'subscribe') {
subscribe_button.onclick = subscribe;
@@ -11,10 +10,29 @@ if (subscribe_button.getAttribute('data-type') === 'subscribe') {
subscribe_button.onclick = unsubscribe;
}
function subscribe() {
var fallback = subscribe_button.innerHTML;
subscribe_button.onclick = unsubscribe;
subscribe_button.innerHTML = '<b>' + subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
function toggleSubscribeButton() {
subscribe_button.classList.remove("primary");
subscribe_button.classList.remove("secondary");
subscribe_button.classList.remove("unsubscribe");
subscribe_button.classList.remove("subscribe");
if (subscribe_button.getAttribute('data-type') === 'subscribe') {
subscribe_button.textContent = subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text;
subscribe_button.onclick = unsubscribe;
subscribe_button.classList.add("secondary");
subscribe_button.classList.add("unsubscribe");
} else {
subscribe_button.textContent = subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text;
subscribe_button.onclick = subscribe;
subscribe_button.classList.add("primary");
subscribe_button.classList.add("subscribe");
}
}
function subscribe(e) {
e.preventDefault();
var fallback = subscribe_button.textContent;
toggleSubscribeButton();
var url = '/subscription_ajax?action_create_subscription_to_channel=1&redirect=false' +
'&c=' + subscribe_data.ucid;
@@ -22,15 +40,15 @@ function subscribe() {
helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'subscribe request'}, {
onNon200: function (xhr) {
subscribe_button.onclick = subscribe;
subscribe_button.innerHTML = fallback;
subscribe_button.textContent = fallback;
}
});
}
function unsubscribe() {
var fallback = subscribe_button.innerHTML;
subscribe_button.onclick = subscribe;
subscribe_button.innerHTML = '<b>' + subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
function unsubscribe(e) {
e.preventDefault();
var fallback = subscribe_button.textContent;
toggleSubscribeButton();
var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' +
'&c=' + subscribe_data.ucid;
@@ -38,7 +56,7 @@ function unsubscribe() {
helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'unsubscribe request'}, {
onNon200: function (xhr) {
subscribe_button.onclick = unsubscribe;
subscribe_button.innerHTML = fallback;
subscribe_button.textContent = fallback;
}
});
}

44
assets/js/theme.js Normal file
View File

@@ -0,0 +1,44 @@
const themeSelector = document.querySelector("#theme-selector");
themeSelector.addEventListener("change", (event) => {
const select = event.target;
const selected = select.options[select.selectedIndex].text;
applyTheme(selected);
});
const colorSchemeSelector = document.querySelector("#color-scheme");
colorSchemeSelector.addEventListener("change", (event) => {
const select = event.target;
const selected = select.options[select.selectedIndex].text;
applyColorScheme(selected);
});
function applyTheme(theme) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = `/css/theme-${theme}.css`;
link.id = "theme-css";
const themeCss = document.querySelector("#theme-css");
if (themeCss) {
themeCss.parentNode.removeChild(themeCss);
}
const head = document.getElementsByTagName("head")[0];
head.appendChild(link);
}
function applyColorScheme(colorScheme) {
document.body.classList.remove("dark-theme");
document.body.classList.remove("light-theme");
if (colorScheme === "dark" || colorScheme === "light") {
document.body.classList.add(`${colorScheme}-theme`);
}
}
applyTheme(themeSelector.options[themeSelector.selectedIndex].text);
applyColorScheme("dark");
// <link rel="stylesheet" href="/css/theme-dracula.css" />
// <link rel="stylesheet" href="/css/theme-catppuccin-latte.css" />
// <link rel="stylesheet" href="/css/ionicons.min.css" />

View File

@@ -1,13 +1,13 @@
'use strict';
var toggle_theme = document.getElementById('toggle_theme');
toggle_theme.href = 'javascript:void(0)';
const STORAGE_KEY_THEME = 'dark_mode';
const THEME_DARK = 'dark';
const THEME_LIGHT = 'light';
// TODO: theme state controlled by system
toggle_theme.addEventListener('click', function () {
toggle_theme.addEventListener('click', function (e) {
e.preventDefault();
const isDarkTheme = helpers.storage.get(STORAGE_KEY_THEME) === THEME_DARK;
const newTheme = isDarkTheme ? THEME_LIGHT : THEME_DARK;
setTheme(newTheme);

View File

@@ -69,6 +69,8 @@ function get_playlist(plid) {
helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, {
on200: function (response) {
if (response === null) return;
playlist.innerHTML = response.playlistHtml;
if (!response.nextVideo) return;