This commit is contained in:
syeopite 2025-05-28 20:22:46 +00:00 committed by GitHub
commit 0724433e96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 215 additions and 291 deletions

View File

@ -97,23 +97,3 @@ DEALINGS IN THE SOFTWARE.
width: 50%;
z-index: 1;
}
.light-theme .slider-nav {
background-color: #ddd;
}
.dark-theme .slider-nav {
background-color: #0005;
}
@media (prefers-color-scheme: light) {
.no-theme .slider-nav {
background-color: #ddd;
}
}
@media (prefers-color-scheme: dark) {
.no-theme .slider-nav {
background-color: #0005;
}
}

81
assets/css/dark.css Normal file
View File

@ -0,0 +1,81 @@
/* Contains the CSS for the Invidious dark theme
--------
This file should not get loaded by the user as the css here will be used
to generate theme.css as to support user-agent dark/light theme
in addition to the class-based swap method without duplicating any styles.
------
*/
.dark-theme a:hover,
.dark-theme a:active,
.dark-theme summary:hover,
.dark-theme a:focus,
.dark-theme summary:focus {
color: rgb(0, 182, 240);
}
.dark-theme .pure-button-primary:hover,
.dark-theme .pure-button-primary:focus,
.dark-theme .pure-button-secondary:hover,
.dark-theme .pure-button-secondary:focus {
color: #fff !important;
border-color: rgb(0, 182, 240) !important;
background-color: rgba(0, 182, 240, 1) !important;
}
.dark-theme .pure-button-secondary {
background-color: #0002;
color: #ddd;
}
.dark-theme a {
color: #adadad;
text-decoration: none;
}
body.dark-theme {
background-color: rgba(35, 35, 35, 1);
color: #f0f0f0;
}
.dark-theme .pure-form legend,
.dark-theme .pure-menu-heading {
color: #f0f0f0;
}
.dark-theme input,
.dark-theme select,
.dark-theme textarea {
color: rgba(35, 35, 35, 1);
}
.dark-theme .pure-form input[type="file"] {
color: #f0f0f0;
}
.dark-theme .searchbar input {
background-color: inherit;
color: inherit;
}
.dark-theme .error-card {
border: 1px solid #5e5e5e;
}
.dark-theme footer {
color: #adadad;
}
.dark-theme footer a {
color: #adadad !important;
}
.dark-theme #filters-box {
background: #373737;
}
.dark-theme .slider-nav {
background-color: #0005;
}

View File

@ -466,22 +466,6 @@ footer {
max-height: 30vh;
}
.light-theme footer {
color: #7c7c7c;
}
.dark-theme footer {
color: #adadad;
}
.light-theme footer a {
color: #7c7c7c !important;
}
.dark-theme footer a {
color: #adadad !important;
}
footer span {
margin: 4px 0;
display: block;
@ -508,236 +492,6 @@ span > select {
}
/*
* Light theme
*/
.light-theme a:hover,
.light-theme a:active,
.light-theme summary:hover,
.light-theme a:focus,
.light-theme summary:focus {
color: #075A9E !important;
}
.light-theme .pure-button-primary:hover,
.light-theme .pure-button-primary:focus,
.light-theme .pure-button-secondary:hover,
.light-theme .pure-button-secondary:focus {
color: #fff !important;
border-color: rgba(0, 182, 240, 0.75) !important;
background-color: rgba(0, 182, 240, 0.75) !important;
}
.light-theme .pure-button-secondary:not(.low-profile) {
color: #335d7a;
background-color: #fff2;
}
.light-theme a {
color: #335d7a;
text-decoration: none;
}
/* All links that do not fit with the default color goes here */
.light-theme a:not([data-id]) > .icon,
.light-theme .pure-u-lg-1-5 > .h-box > a[href^="/watch?"],
.light-theme .playlist-restricted > ol > li > a {
color: #303030;
}
.light-theme .pure-menu-heading {
color: #565d64;
}
.light-theme .error-card {
border: 1px solid black;
}
@media (prefers-color-scheme: light) {
.no-theme a:hover,
.no-theme a:active,
.no-theme summary:hover,
.no-theme a:focus,
.no-theme summary:focus {
color: #075A9E !important;
}
.no-theme .pure-button-primary:hover,
.no-theme .pure-button-primary:focus,
.no-theme .pure-button-secondary:hover,
.no-theme .pure-button-secondary:focus {
color: #fff !important;
border-color: rgba(0, 182, 240, 0.75) !important;
background-color: rgba(0, 182, 240, 0.75) !important;
}
.no-theme .pure-button-secondary:not(.low-profile) {
color: #335d7a;
background-color: #fff2;
}
.no-theme a {
color: #335d7a;
text-decoration: none;
}
/* All links that do not fit with the default color goes here */
.no-theme a:not([data-id]) > .icon,
.no-theme .pure-u-lg-1-5 > .h-box > a[href^="/watch?"],
.no-theme .playlist-restricted > ol > li > a {
color: #303030;
}
.no-theme footer {
color: #7c7c7c;
}
.no-theme footer a {
color: #7c7c7c !important;
}
.light-theme .pure-menu-heading {
color: #565d64;
}
.no-theme .error-card {
border: 1px solid black;
}
}
/*
* Dark theme
*/
.dark-theme a:hover,
.dark-theme a:active,
.dark-theme summary:hover,
.dark-theme a:focus,
.dark-theme summary:focus {
color: rgb(0, 182, 240);
}
.dark-theme .pure-button-primary:hover,
.dark-theme .pure-button-primary:focus,
.dark-theme .pure-button-secondary:hover,
.dark-theme .pure-button-secondary:focus {
color: #fff !important;
border-color: rgb(0, 182, 240) !important;
background-color: rgba(0, 182, 240, 1) !important;
}
.dark-theme .pure-button-secondary {
background-color: #0002;
color: #ddd;
}
.dark-theme a {
color: #adadad;
text-decoration: none;
}
body.dark-theme {
background-color: rgba(35, 35, 35, 1);
color: #f0f0f0;
}
.dark-theme .pure-form legend {
color: #f0f0f0;
}
.dark-theme .pure-menu-heading {
color: #f0f0f0;
}
.dark-theme input,
.dark-theme select,
.dark-theme textarea {
color: rgba(35, 35, 35, 1);
}
.dark-theme .pure-form input[type="file"] {
color: #f0f0f0;
}
.dark-theme .searchbar input {
background-color: inherit;
color: inherit;
}
.dark-theme .error-card {
border: 1px solid #5e5e5e;
}
@media (prefers-color-scheme: dark) {
.no-theme a:hover,
.no-theme a:active,
.no-theme a:focus {
color: rgb(0, 182, 240);
}
.no-theme .pure-button-primary:hover,
.no-theme .pure-button-primary:focus,
.no-theme .pure-button-secondary:hover,
.no-theme .pure-button-secondary:focus {
color: #fff !important;
border-color: rgb(0, 182, 240) !important;
background-color: rgba(0, 182, 240, 1) !important;
}
.no-theme .pure-button-secondary {
background-color: #0002;
color: #ddd;
}
.no-theme a {
color: #adadad;
text-decoration: none;
}
body.no-theme {
background-color: rgba(35, 35, 35, 1);
color: #f0f0f0;
}
.no-theme .pure-form legend {
color: #f0f0f0;
}
.no-theme .pure-menu-heading {
color: #f0f0f0;
}
.no-theme input,
.no-theme select,
.no-theme textarea {
color: rgba(35, 35, 35, 1);
}
.no-theme .pure-form input[type="file"] {
color: #f0f0f0;
}
.no-theme .searchbar input {
background-color: inherit;
color: inherit;
}
.no-theme footer {
color: #adadad;
}
.no-theme footer a {
color: #adadad !important;
}
.no-theme .error-card {
border: 1px solid #5e5e5e;
}
}
/*
* Miscellanous
*/

67
assets/css/light.css Normal file
View File

@ -0,0 +1,67 @@
/* Contains the CSS for the Invidious light theme
--------
This file should not get loaded by the user as the css here will be used
to generate theme.css as to support user-agent dark/light theme
in addition to the class-based swap method without duplicating any styles.
------
*/
.light-theme a:hover,
.light-theme a:active,
.light-theme summary:hover,
.light-theme a:focus,
.light-theme summary:focus {
color: #075A9E !important;
}
.light-theme .pure-button-primary:hover,
.light-theme .pure-button-primary:focus,
.light-theme .pure-button-secondary:hover,
.light-theme .pure-button-secondary:focus {
color: #fff !important;
border-color: rgba(0, 182, 240, 0.75) !important;
background-color: rgba(0, 182, 240, 0.75) !important;
}
.light-theme .pure-button-secondary:not(.low-profile) {
color: #335d7a;
background-color: #fff2;
}
.light-theme a {
color: #335d7a;
text-decoration: none;
}
/* All links that do not fit with the default color goes here */
.light-theme a:not([data-id]) > .icon,
.light-theme .pure-u-lg-1-5 > .h-box > a[href^="/watch?"],
.light-theme .playlist-restricted > ol > li > a {
color: #303030;
}
.light-theme .pure-menu-heading {
color: #565d64;
}
.light-theme footer {
color: #7c7c7c;
}
.light-theme footer a {
color: #7c7c7c !important;
}
.light-theme .error-card {
border: 1px solid black;
}
.light-theme #filters-box {
background: #dfdfdf;
}
.light-theme .slider-nav {
background-color: #ddd;
}

View File

@ -95,27 +95,3 @@ fieldset, legend {
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;
}
}

View File

@ -159,7 +159,9 @@ module Kemal
redirect_to context, expanded_path + '/'
end
return call_next(context) if file_info.nil?
return call_next(context) if file_info.nil? ||
file_path.ends_with?("dark.css") ||
file_path.ends_with?("light.css")
if is_dir
if config.is_a?(Hash) && config["dir_listing"] == true

View File

@ -0,0 +1,52 @@
# No need to initialize yet another namespace
#
# :nodoc:
module Invidious::Routes::Misc
private CSS_THEME_LIGHT = File.read("assets/css/light.css")
private CSS_THEME_UA_LIGHT = CSS_THEME_LIGHT.gsub(".light-theme", ".no-theme")
private CSS_THEME_DARK = File.read("assets/css/dark.css")
private CSS_THEME_UA_DARK = CSS_THEME_DARK.gsub(".dark-theme", ".no-theme")
private THEME_LAST_MODIFIED = {
File.info("assets/css/light.css").modification_time,
File.info("assets/css/dark.css").modification_time,
}.min
def self.theme_css(env)
env.response.headers["Content-Type"] = "text/css; charset=utf-8"
# Replicate cache header behavior of static file handler
env.response.headers["Etag"] = %{W/"#{THEME_LAST_MODIFIED.to_unix}"}
env.response.headers["Last-Modified"] = HTTP.format_time(THEME_LAST_MODIFIED)
if cache_request?(env, THEME_LAST_MODIFIED)
env.response.status = HTTP::Status::NOT_MODIFIED
return
end
# Usually added in `send_file` when static_headers proc is called
env.response.headers["Cache-Control"] = "max-age=2629800"
rendered "theme.css"
end
# Taken from https://github.com/crystal-lang/crystal/blob/1.16.3/src/http/server/handlers/static_file_handler.cr#L236
private def self.cache_request?(context : HTTP::Server::Context, last_modified : Time) : Bool
# According to RFC 7232:
# A recipient must ignore If-Modified-Since if the request contains an If-None-Match header field
if if_none_match = context.request.if_none_match
match = {"*", context.response.headers["Etag"]}
if_none_match.any? { |etag| match.includes?(etag) }
elsif if_modified_since = context.request.headers["If-Modified-Since"]?
header_time = HTTP.parse_time(if_modified_since)
# File mtime probably has a higher resolution than the header value.
# An exact comparison might be slightly off, so we add 1s padding.
# Static files should generally not be modified in subsecond intervals, so this is perfectly safe.
# This might be replaced by a more sophisticated time comparison when it becomes available.
!!(header_time && last_modified <= header_time + 1.second)
else
false
end
end
end

View File

@ -21,6 +21,7 @@ module Invidious::Routing
get "/privacy", Routes::Misc, :privacy
get "/licenses", Routes::Misc, :licenses
get "/redirect", Routes::Misc, :cross_instance_redirect
get "/css/theme.css", Routes::Misc, :theme_css
self.register_channel_routes
self.register_watch_routes

View File

@ -21,6 +21,7 @@
<link rel="stylesheet" href="/css/grids-responsive-min.css?v=<%= ASSET_COMMIT %>">
<link rel="stylesheet" href="/css/ionicons.min.css?v=<%= ASSET_COMMIT %>">
<link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>">
<link rel="stylesheet" href="/css/theme.css?v=<%= ASSET_COMMIT %>">
<link rel="stylesheet" href="/css/carousel.css?v=<%= ASSET_COMMIT %>">
<script src="/js/_helpers.js?v=<%= ASSET_COMMIT %>"></script>
</head>

View File

@ -0,0 +1,10 @@
/*
This files contains an automatically generated CSS file that contains the styles
for the dark and light themes of Invidious. Automatically generating this file allows
supporting both the user-agent color scheme preference and class-based theme preference
without duplicating the defined styles twice for each theme.
*/
<%=CSS_THEME_LIGHT-%>
@media (prefers-color-scheme: light) {<%= CSS_THEME_UA_LIGHT %>}
<%=CSS_THEME_DARK%>
@media (prefers-color-scheme: dark) {<%=CSS_THEME_UA_DARK%>}