mirror of
https://github.com/iv-org/invidious.git
synced 2025-07-15 18:08:29 +00:00
Merge 60c8c55ff2
into df8839d1f0
This commit is contained in:
commit
0724433e96
@ -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
81
assets/css/dark.css
Normal 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;
|
||||
}
|
@ -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
67
assets/css/light.css
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
52
src/invidious/routes/get_theme_css_file.cr
Normal file
52
src/invidious/routes/get_theme_css_file.cr
Normal 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
|
@ -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
|
||||
|
@ -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>
|
||||
|
10
src/invidious/views/theme.css.ecr
Normal file
10
src/invidious/views/theme.css.ecr
Normal 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%>}
|
Loading…
Reference in New Issue
Block a user