Change general design theme

This commit is contained in:
Jesús 2020-12-14 23:44:29 -05:00
parent 9d0be82e74
commit 7a765dc664
No known key found for this signature in database
GPG Key ID: F6EE7BC59A315766
30 changed files with 5448 additions and 1526 deletions

View File

@ -1,20 +1,20 @@
# youtube-local # yt-local
![screenshot](https://user-images.githubusercontent.com/28744867/64483429-8a890780-d1b6-11e9-8423-6956ff7c588d.png) Fork of [youtube-local](https://github.com/user234683/youtube-local)
youtube-local is a browser-based client written in Python for watching Youtube anonymously and without the lag of the slow page used by Youtube. One of the primary features is that all requests are routed through Tor, except for the video file at googlevideo.com. This is analogous to what HookTube (defunct) and Invidious do, except that you do not have to trust a third-party to respect your privacy. The assumption here is that Google won't put the effort in to incorporate the video file requests into their tracking, as it's not worth pursuing the incredibly small number of users who care about privacy (Tor video routing is also provided as an option). Tor has high latency, so this will not be as fast as regular Youtube. However, using Tor is optional; when not routing through Tor, video pages may load faster than they do with Youtube's page depending on your browser.
yt-local is a browser-based client written in Python for watching Youtube anonymously and without the lag of the slow page used by Youtube. One of the primary features is that all requests are routed through Tor, except for the video file at googlevideo.com. This is analogous to what HookTube (defunct) and Invidious do, except that you do not have to trust a third-party to respect your privacy. The assumption here is that Google won't put the effort in to incorporate the video file requests into their tracking, as it's not worth pursuing the incredibly small number of users who care about privacy (Tor video routing is also provided as an option). Tor has high latency, so this will not be as fast as regular Youtube. However, using Tor is optional; when not routing through Tor, video pages may load faster than they do with Youtube's page depending on your browser.
The Youtube API is not used, so no keys or anything are needed. It uses the same requests as the Youtube webpage. The Youtube API is not used, so no keys or anything are needed. It uses the same requests as the Youtube webpage.
## Screenshots ## Screenshots
[Gray theme](https://user-images.githubusercontent.com/28744867/64483431-8e1c8e80-d1b6-11e9-999c-14d36ddd582f.png)
[Dark theme](https://user-images.githubusercontent.com/28744867/64483432-8fe65200-d1b6-11e9-90bd-32869542e32e.png) [Light theme](https://pic.infini.fr/l7WINjzS/0Ru6MrhA.png)
[Non-Theater mode](https://user-images.githubusercontent.com/28744867/64483433-92e14280-d1b6-11e9-9b56-2ef5d64c372f.png) [Gray theme](https://pic.infini.fr/znnQXWNc/hL78CRzo.png)
[Channel](https://user-images.githubusercontent.com/28744867/64483436-95dc3300-d1b6-11e9-8efc-b19b1f1f3bcf.png) [Dark theme](https://pic.infini.fr/iXwFtTWv/mt2kS5bv.png)
[Downloads](https://user-images.githubusercontent.com/28744867/64483437-a2608b80-d1b6-11e9-9e5a-4114391b7304.png) [Channel](https://pic.infini.fr/JsenWVYe/SbdIQlS6.png)
## Features ## Features
* Standard pages of Youtube: search, channels, playlists * Standard pages of Youtube: search, channels, playlists
@ -86,9 +86,9 @@ To run the program on windows, open `run.bat`. On Linux/MacOS, run `python3 serv
Access youtube URLs by prefixing them with `http://localhost:8080/`, For instance, `http://localhost:8080/https://www.youtube.com/watch?v=vBgulDeV2RU` Access youtube URLs by prefixing them with `http://localhost:8080/`, For instance, `http://localhost:8080/https://www.youtube.com/watch?v=vBgulDeV2RU`
You can use an addon such as Redirector ([Firefox](https://addons.mozilla.org/en-US/firefox/addon/redirector/)|[Chrome](https://chrome.google.com/webstore/detail/redirector/ocgpenflpmgnfapjedencafcfakcekcd)) to automatically redirect Youtube URLs to youtube-local. I use the include pattern `^(https?://(?:[a-zA-Z0-9_-]*\.)?(?:youtube\.com|youtu\.be)/.*)` and the redirect pattern `http://localhost:8080/$1` (Make sure you're using regular expression mode). You can use an addon such as Redirector ([Firefox](https://addons.mozilla.org/en-US/firefox/addon/redirector/)|[Chrome](https://chrome.google.com/webstore/detail/redirector/ocgpenflpmgnfapjedencafcfakcekcd)) to automatically redirect Youtube URLs to yt-local. I use the include pattern `^(https?://(?:[a-zA-Z0-9_-]*\.)?(?:youtube\.com|youtu\.be)/.*)` and the redirect pattern `http://localhost:8080/$1` (Make sure you're using regular expression mode).
youtube-local can be added as a search engine in firefox to make searching more convenient. See [here](https://support.mozilla.org/en-US/kb/add-or-remove-search-engine-firefox) for information on firefox search plugins. yt-local can be added as a search engine in firefox to make searching more convenient. See [here](https://support.mozilla.org/en-US/kb/add-or-remove-search-engine-firefox) for information on firefox search plugins.
### Using Tor ### Using Tor
@ -102,7 +102,7 @@ Pull requests and issues are welcome
If you wish to route the video through Tor, set "Route Tor" to "On, including video". Because this is bandwidth-intensive, you are strongly encouraged to donate to the [consortium of Tor node operators](https://torservers.net/donate.html). For instance, donations to [NoiseTor](https://noisetor.net/) go straight towards funding nodes. Using their numbers for bandwidth costs, together with an average of 485 kbit/sec for a diverse sample of videos, and assuming n hours of video watched per day, gives $0.03n/month. A $1/month donation will be a very generous amount to not only offset losses, but help keep the network healthy. If you wish to route the video through Tor, set "Route Tor" to "On, including video". Because this is bandwidth-intensive, you are strongly encouraged to donate to the [consortium of Tor node operators](https://torservers.net/donate.html). For instance, donations to [NoiseTor](https://noisetor.net/) go straight towards funding nodes. Using their numbers for bandwidth costs, together with an average of 485 kbit/sec for a diverse sample of videos, and assuming n hours of video watched per day, gives $0.03n/month. A $1/month donation will be a very generous amount to not only offset losses, but help keep the network healthy.
In general, Tor video routing will be slower (for instance, moving around in the video is quite slow). I've never seen any signs that watch history in youtube-local affects on-site Youtube recommendations. It's likely that requests to googlevideo are logged for some period of time, but are not integrated into Youtube's larger advertisement/recommendation systems, since those presumably depend more heavily on in-page tracking through Javascript rather than CDN requests to googlevideo. In general, Tor video routing will be slower (for instance, moving around in the video is quite slow). I've never seen any signs that watch history in yt-local affects on-site Youtube recommendations. It's likely that requests to googlevideo are logged for some period of time, but are not integrated into Youtube's larger advertisement/recommendation systems, since those presumably depend more heavily on in-page tracking through Javascript rather than CDN requests to googlevideo.
## License ## License

557
youtube/static/channel.css Normal file
View File

@ -0,0 +1,557 @@
html {
font-family: "liberation serif", "times new roman", calibri, carlito, serif;
background: var(--background);
color: var(--text);
}
body {
display: grid;
grid-gap: 20px;
grid-template-areas:
"header"
"main"
"footer";
/* Fix height */
height: 100vh;
grid-template-rows: auto 1fr auto;
/* fix top and bottom */
margin-left: 1rem;
margin-right: 1rem;
}
img {
width: 100%;
height: auto;
}
a:link {
color: var(--link);
}
a:visited {
color: var(--link-visited);
}
input[type="text"],
input[type="search"] {
background: var(--background);
border: 1px solid var(--button-border);
padding: 0.4rem 0.4rem;
font-size: 15px;
color: var(--search-text);
}
input[type='search'] {
border-bottom: 1px solid var(--button-border);
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-radius: 0px;
}
header {
display: grid;
grid-gap: 1px;
grid-template-areas:
"home"
"form"
"playlist";
grid-area: header;
}
.home {
grid-area: home;
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.form {
display: grid;
grid-gap: 4px;
grid-template-areas:
"search-box"
"search-button"
"dropdown";
grid-area: form;
}
.search-box {
grid-area: search-box;
}
.search-button {
grid-area: search-button;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.search-button:hover {
background-color: var(--buttom-hover);
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
}
.dropdown-label {
grid-area: dropdown-label;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.dropdown-label:hover {
background-color: var(--buttom-hover);
}
/* playlist */
.playlist {
display: grid;
grid-gap: 4px;
grid-template-areas:
"play-box"
"play-hidden"
"play-add"
"play-clean";
grid-area: playlist;
}
.play-box {
grid-area: play-box;
}
.play-hidden {
grid-area: play-hidden;
}
.play-add {
grid-area: play-add;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-add:hover {
background-color: var(--buttom-hover);
}
.play-clean {
display: grid;
grid-area: play-clean;
}
.play-clean > button {
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-clean > button:hover {
background-color: var(--buttom-hover);
}
/* /playlist */
/* ------------- Menu Mobile sin JS ---------------- */
/* input hidden */
.opt-box {
display: none;
}
.dropdown-content {
display: none;
grid-area: dropdown-content;
}
label[for=options-toggle-cbox] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#options-toggle-cbox:checked ~ .dropdown-content {
display: inline-grid;
white-space: nowrap;
}
/*- ----------- End Menu Mobile sin JS ------------- */
.main {
grid-area: main;
display: grid;
grid-row-gap: 1rem;
}
/* fix hr when is children of grid */
hr {
width: 100%;
}
.author-container {
display: grid;
grid-template-columns: 1fr;
margin: auto;
grid-template-areas:
"author"
"summary"
"subscribe";
}
.author {
grid-area: author;
display: grid;
grid-template-columns: 100px 1fr;
grid-column-gap: 1rem;
align-items: center;
justify-self: center;
}
.summary { grid-area: summary; }
.summary p {
text-align: center;
}
.subscribe {
grid-area: subscribe;
justify-self: center;
}
.subscribe .btn-subscribe {
background-color: var(--buttom);
color: var(--buttom-text);
text-shadow: none;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
border: none;
border-radius: 0.2rem;
}
/* Video list item */
.video-container {
display: grid;
grid-row-gap: 0.5rem;
}
.item-box {
display: grid;
grid-template-columns: 1.9fr 0.1fr;
grid-template-rows: 1fr;
grid-gap: 1px;
grid-template-areas:
"item-video item-checkbox";
}
.item-video {
grid-area: item-video;
display: grid;
grid-template-columns: auto;
grid-template-rows: repeat(4, auto);
grid-row-gap: 0.4rem;
grid-template-areas:
"thumbnail-box"
"info-box";
align-items: center;
font-size: 0.7rem;
}
.item-video a {
text-decoration: none;
cursor: pointer;
}
.item-video.channel-item {
border-radius: 50%;
width: 150px;
height: 150px;
}
.thumbnail-box {
grid-area: thumbnail-box;
position: relative;
}
.thumbnail {
padding: 28.125%;
position: relative;
box-sizing: border-box;
}
.thumbnail-img {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
object-fit: cover;
}
.length {
z-index: 100;
position: absolute;
background-color: rgba(35, 35, 35, 0.75);
color: #fff;
border-radius: 2px;
padding: 2px;
font-size: 16px;
right: 0.25em;
bottom: -0.75em;
}
.playlist-item .thumbnail-info {
position: absolute;
right: 0px;
bottom: 0px;
height: 100%;
width: 50%;
text-align: center;
white-space: pre-line;
opacity: .8;
color: var(--text);
font-size: 0.8125rem;
background: var(--secondary-background);
padding: 0;
}
.playlist-item .thumbnail-info span {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
text-transform: none;
}
.thumbnail-img {
margin: auto;
display: block;
max-height: 100%;
max-width: 100%;
}
.info-box {
grid-area: info-box;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto auto auto auto;
grid-gap: 1px;
grid-template-areas:
"."
"."
"."
"."
".";
}
.title {
font-size: 0.8rem;
margin: 0px;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.info-box address {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.thumbnail-info {
background-color: var(--time-background);
color: #fff;
padding: 2px 5px;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
position: absolute;
right: 0;
bottom: .2rem;
}
.item-checkbox {
grid-area: item-checkbox;
justify-self: start;
align-self: center;
min-width: 30px;
margin: 0px;
}
.stats {
display: flex;
justify-content: space-between;
}
.horizontal-stats {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.horizontal-stats > li {
display: inline;
}
.horizontal-stats > li:first-child::after {
content: " | ";
}
/* pagination */
.main .pagination-container {
display: grid;
justify-content: center;
}
.main .pagination-container .pagination-list {
display: grid;
grid-auto-flow: column;
grid-column-gap: 0.5rem;
}
.main .pagination-container .pagination-list .page-link {
border-style: none;
font-weight: bold;
text-align: center;
background: var(--secondary-focus);
text-decoration: none;
align-self: center;
padding: .5rem;
width: 1rem;
}
.main .pagination-container .pagination-list .page-link.is-current {
background: var(--secondary-background);
}
/* /video list item */
.footer {
grid-area: footer;
}
.footer > p {
text-align: center;
}
@media (min-width: 480px) {
.item-video {
font-size: 0.85rem;
}
.info-box {
grid-gap: 2px;
}
.title {
font-size: 1rem;
}
}
@media (min-width: 600px) {
.video-container {
display: grid;
grid-row-gap: 0.5rem;
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 992px) {
body {
display: grid;
grid-template-columns: 0.3fr 2fr 1fr 0.3fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header header"
"main main main main"
"footer footer footer footer";
}
.form {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". search-box search-button dropdown";
grid-area: form;
position: relative;
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-columns: minmax(50px, 100px);
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
background: var(--background);
padding-right: 4rem;
z-index: 1;
position: absolute;
}
.author-container {
max-width: 50vw;
}
/* playlist */
.playlist {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". play-box play-add play-clean";
grid-area: playlist;
}
.play-clean {
grid-template-columns: minmax(50px, 100px);
}
.play-clean > button {
padding-left: 0px;
padding-right: 0px;
padding-bottom: 6px;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
color: var(--buttom-text);
border-radius: 5px;
cursor: pointer;
}
.video-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-row-gap: 1rem;
grid-column-gap: 1rem;
}
}

View File

@ -1,148 +1,283 @@
.video-metadata{ html {
font-family: "liberation serif", "times new roman", calibri, carlito, serif;
background: var(--background);
color: var(--text);
}
body {
display: grid;
grid-gap: 20px;
grid-template-areas:
"header"
"main"
"footer";
/* Fix height */
height: 100vh;
grid-template-rows: auto 1fr auto;
/* fix top and bottom */
margin-left: 1rem;
margin-right: 1rem;
}
h1, h2, h3, h4, h5, h6, div, button {
margin: 0;
padding: 0;
}
img {
width: 100%;
height: auto;
}
a:link {
color: var(--link);
}
a:visited {
color: var(--link-visited);
}
input[type="text"],
input[type="search"] {
background: var(--background);
border: 1px solid var(--button-border);
border-radius: 5px;
padding: 0.4rem 0.4rem;
font-size: 15px;
color: var(--search-text);
}
input[type='search'] {
border-bottom: 1px solid var(--button-border);
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-radius: 0px;
}
header {
display: grid;
grid-gap: 1px;
grid-template-areas:
"home"
"form";
grid-area: header;
}
.home {
grid-area: home;
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.form {
display: grid;
grid-gap: 4px;
grid-template-areas:
"search-box"
"search-button"
"dropdown";
grid-area: form;
}
.search-box {
grid-area: search-box;
}
.search-button {
grid-area: search-button;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.search-button:hover {
background-color: var(--buttom-hover);
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
background: var(--background);
padding-right: 4rem;
z-index: 1;
}
.dropdown-label {
grid-area: dropdown-label;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.dropdown-label:hover {
background-color: var(--buttom-hover);
}
/* ------------- Menu Mobile sin JS ---------------- */
/* input hidden */
.opt-box {
display: none;
}
.dropdown-content {
display: none;
grid-area: dropdown-content;
}
label[for=options-toggle-cbox] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#options-toggle-cbox:checked ~ .dropdown-content {
display: inline-grid;
white-space: nowrap;
}
/*- ----------- End Menu Mobile sin JS ------------- */
.main {
grid-area: main;
margin: 0 auto;
max-width: 80ch;
}
/* comments */
.comments-area {
display: grid;
grid-row-gap: 0.5rem;
}
.comments-area textarea {
resize: vertical;
}
.video-metadata {
display: grid; display: grid;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
grid-column-gap: 0.5rem;
grid-template-rows: auto auto 1fr auto; grid-template-rows: auto auto 1fr auto;
grid-template-areas:
"video-metadata-thumbnail-box ."
"video-metadata-thumbnail-box ."
"video-metadata-thumbnail-box .";
} }
.video-metadata > .video-metadata-thumbnail-box{
grid-row: 1 / span 3;
}
.video-metadata > .title{
word-wrap:break-word;
grid-row: 1;
}
.video-metadata > h2{
grid-row: 2;
font-size: 0.875rem;
}
.video-metadata > span{
grid-row:3;
}
.video-metadata > hr{
grid-row: 4;
grid-column: 1 / span 2;
width: 100%;
}
.comment-form{ .video-metadata > h2 {
font-size: 0.875rem;
}
.video-metadata-thumbnail-box {
grid-area: video-metadata-thumbnail-box;
}
.comment-form {
display: grid; display: grid;
align-content: start; grid-row-gap: 0.5rem;
justify-items: start;
align-items: start;
} }
#comment-account-options{
display:grid;
grid-auto-flow: column;
grid-column-gap: 10px;
margin-top:10px;
margin-bottom:10px;
}
#comment-account-options a{
margin-left:10px;
}
.comments-area{ .post-comment-button {
display:grid; justify-self: end;
} }
.comments-area textarea{
resize: vertical;
justify-self:stretch;
}
.post-comment-button{
margin-top:10px;
justify-self:end;
}
.comment-links{
display:grid;
grid-auto-flow: column;
grid-column-gap: 10px;
justify-content:start;
}
.comments{ .comments {
margin-top:10px;
grid-row-gap: 10px;
display: grid; display: grid;
align-content:start; grid-row-gap: 0.5rem;
} }
.comment{ .comment {
display:grid;
grid-template-columns: auto auto 100px 1fr;
grid-template-rows: 0fr 0fr 0fr 0fr;
background-color: var(--interface-color);
justify-content: start;
}
.comment .author-avatar{
grid-column: 1;
grid-row: 1 / span 3;
align-self: start;
margin-right: 5px;
height:32px;
width:32px;
}
.comment .author-avatar-img{
max-height: 100%;
}
.comment address{
grid-column: 2;
grid-row: 1;
margin-right:15px;
white-space: nowrap;
overflow:hidden;
}
.comment .text{
grid-column: 2 / span 3;
grid-row: 2;
white-space: pre-wrap;
min-width: 0;
word-wrap: break-word;
}
.comment .permalink{
grid-column: 3;
grid-row: 1;
white-space: nowrap;
}
.comment .likes{
grid-column:2;
grid-row:3;
font-weight:bold;
white-space: nowrap;
}
.comment .bottom-row{
grid-column:2 / span 3;
grid-row:4;
justify-self:start;
display: grid; display: grid;
grid-auto-flow: column; grid-template-columns: repeat(3, auto) 3fr;
grid-column-gap: 10px; grid-template-rows: repeat(4, auto);
grid-column-gap: 0.4rem;
grid-template-areas:
"author-avatar author-name permalink permalink"
"author-avatar comment-text comment-text comment-text"
". comment-likes comment-likes comment-likes"
". button-row button-row button-row";
background: var(--secondary-background);
} }
details.replies > summary{ .author-avatar { grid-area: author-avatar; }
background-color: var(--interface-color); .author-name { grid-area: author-name; }
border-style: outset; .permalink { grid-area: permalink; }
border-width: 1px; .comment-text { grid-area: comment-text; }
font-weight: bold; .comment-likes { grid-area: comment-likes; }
padding-bottom: 0px; .button-row { grid-area: button-row; }
}
.replies-open-new-tab{ .more-comments {
display: inline-block; justify-self: center;
margin-top: 5px; margin-top: 10px;
}
details.replies .comment{
width: 600px;
}
.more-comments{
justify-self:center;
margin-top:10px;
margin-bottom: 10px; margin-bottom: 10px;
background: var(--secondary-background);
padding: 5px;
/* disable text selection */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.footer {
grid-area: footer;
}
.footer > p {
text-align: center;
}
@media (min-width: 780px) {
body {
display: grid;
grid-template-columns: 0.3fr 2fr 1fr 0.3fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header header"
"main main main main"
"footer footer footer footer";
}
.form {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". search-box search-button dropdown";
grid-area: form;
position: relative;
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-columns: minmax(50px, 100px);
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
position: absolute;
}
} }

View File

@ -1,39 +1,17 @@
body{ :root {
--interface-color: #333333; --background: #121212;
--text-color: #cccccc; --text: #FFF;
--background-color: #000000; --secondary-hover: #73828c;
--video-background-color: #080808; --secondary-focus: #616161;
--secondary-inverse: #FFF;
--secondary-background: #424242;
--link: #22aaff;
--link-visited: #7755ff;
--buttom: #dcdcdb;
--buttom-text: #415462;
--button-border: #91918c;
--buttom-hover: #BBB;
--search-text: #FFF;
--time-background: #000;
--time-text: #FFF;
} }
a:link {
color: #22aaff;
}
a:visited {
color: #7755ff;
}
a:not([href]){
color: var(--text-color);
}
.comment .permalink{
color: #ffffff;
}
.setting-item{
background-color: #444444;
}
.muted{
background-color: #111111;
color: gray;
}
.muted a:link {
color: #10547f;
}

View File

@ -1,18 +1,17 @@
body{ :root {
--interface-color: #dadada; --background: #2d3743;
--text-color: #222222; --text: #FFF;
--background-color: #bcbcbc; --secondary-hover: #73828c;
--video-background-color: #dadada; --secondary-focus: rgba(115, 130, 140, 0.125);
} --secondary-inverse: #FFF;
--secondary-background: #102027;
.comment .permalink{ --link: #22aaff;
color: #000000; --link-visited: #7755ff;
} --buttom: #dcdcdb;
--buttom-text: #415462;
.setting-item{ --button-border: #91918c;
background-color: #eeeeee; --buttom-hover: #BBB;
} --search-text: #FFF;
--time-background: #000;
.muted{ --time-text: #FFF;
background-color: #888888;
} }

197
youtube/static/home.css Normal file
View File

@ -0,0 +1,197 @@
html {
font-family: "liberation serif", "times new roman", calibri, carlito, serif;
background: var(--background);
color: var(--text);
}
body {
display: grid;
grid-gap: 20px;
grid-template-areas:
"header"
"main"
"footer";
/* Fix height */
height: 100vh;
grid-template-rows: auto 1fr auto;
/* fix top and bottom */
margin-left: 1rem;
margin-right: 1rem;
}
a:link {
color: var(--link);
}
a:visited {
color: var(--link-visited);
}
input[type="text"],
input[type="search"] {
background: var(--background);
border: 1px solid var(--button-border);
border-radius: 5px;
padding: 0.4rem 0.4rem;
font-size: 15px;
color: var(--search-text);
}
input[type='search'] {
border-bottom: 1px solid var(--button-border);
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-radius: 0px;
}
header {
display: grid;
grid-gap: 1px;
grid-template-areas:
"home"
"form";
grid-area: header;
}
.home {
grid-area: home;
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.form {
display: grid;
grid-gap: 4px;
grid-template-areas:
"search-box"
"search-button"
"dropdown";
grid-area: form;
}
.search-box {
grid-area: search-box;
}
.search-button {
grid-area: search-button;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.search-button:hover {
background-color: var(--buttom-hover);
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
}
.dropdown-label {
grid-area: dropdown-label;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.dropdown-label:hover {
background-color: var(--buttom-hover);
}
/* ------------- Menu Mobile sin JS ---------------- */
/* input hidden */
.opt-box {
display: none;
}
.dropdown-content {
display: none;
grid-area: dropdown-content;
}
label[for=options-toggle-cbox] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#options-toggle-cbox:checked ~ .dropdown-content {
display: inline-grid;
white-space: nowrap;
}
/*- ----------- End Menu Mobile sin JS ------------- */
.main {
grid-area: main;
margin: 0 auto;
max-width: 80ch;
}
.code-error {
background: var(--secondary-background);
padding: 1rem;
}
.footer {
grid-area: footer;
}
.footer > p {
text-align: center;
}
@media (min-width: 780px) {
body {
display: grid;
grid-template-columns: 0.3fr 2fr 1fr 0.3fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header header"
"main main main main"
"footer footer footer footer";
}
.form {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". search-box search-button dropdown";
grid-area: form;
position: relative;
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-columns: minmax(50px, 100px);
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
position: absolute;
}
}

View File

@ -1,19 +1,17 @@
body{ :root {
--interface-color: #ffffff; --background: #FAFAFA;
--text-color: #222222; --text: #415462;
--background-color: #f8f8f8; --secondary-hover: #415462;
--video-background-color: #ffffff; --secondary-focus: rgba(115, 130, 140, 0.125);
--secondary-inverse: #FFF;
--secondary-background: #eeeeee;
--link: #22aaff;
--link-visited: #7755ff;
--buttom: #dcdcdb;
--buttom-text: #415462;
--button-border: #91918c;
--buttom-hover: #BBB;
--search-text: #415462;
--time-background: #000;
--time-text: #FFF;
} }
.comment .permalink{
color: #000000;
}
.setting-item{
background-color: #f8f8f8;
}
.muted{
background-color: #888888;
}

View File

@ -0,0 +1,527 @@
html {
font-family: "liberation serif", "times new roman", calibri, carlito, serif;
background: var(--background);
color: var(--text);
}
body {
display: grid;
grid-gap: 20px;
grid-template-areas:
"header"
"main"
"footer";
/* Fix height */
height: 100vh;
grid-template-rows: auto 1fr auto;
/* fix top and bottom */
margin-left: 1rem;
margin-right: 1rem;
}
img {
width: 100%;
height: auto;
}
a:link {
color: var(--link);
}
a:visited {
color: var(--link-visited);
}
input[type="text"],
input[type="search"] {
background: var(--background);
border: 1px solid var(--button-border);
padding: 0.4rem 0.4rem;
font-size: 15px;
color: var(--search-text);
}
input[type='search'] {
border-bottom: 1px solid var(--button-border);
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-radius: 0px;
}
header {
display: grid;
grid-gap: 1px;
grid-template-areas:
"home"
"form"
"playlist";
grid-area: header;
}
.home {
grid-area: home;
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.form {
display: grid;
grid-gap: 4px;
grid-template-areas:
"search-box"
"search-button"
"dropdown";
grid-area: form;
}
.search-box {
grid-area: search-box;
}
.search-button {
grid-area: search-button;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.search-button:hover {
background-color: var(--buttom-hover);
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
}
.dropdown-label {
grid-area: dropdown-label;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.dropdown-label:hover {
background-color: var(--buttom-hover);
}
/* playlist */
.playlist {
display: grid;
grid-gap: 4px;
grid-template-areas:
"play-box"
"play-hidden"
"play-add"
"play-clean";
grid-area: playlist;
}
.play-box {
grid-area: play-box;
}
.play-hidden {
grid-area: play-hidden;
}
.play-add {
grid-area: play-add;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-add:hover {
background-color: var(--buttom-hover);
}
.play-clean {
display: grid;
grid-area: play-clean;
}
.play-clean > button {
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-clean > button:hover {
background-color: var(--buttom-hover);
}
/* /playlist */
/* ------------- Menu Mobile sin JS ---------------- */
/* input hidden */
.opt-box {
display: none;
}
.dropdown-content {
display: none;
grid-area: dropdown-content;
}
label[for=options-toggle-cbox] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#options-toggle-cbox:checked ~ .dropdown-content {
display: inline-grid;
white-space: nowrap;
}
/*- ----------- End Menu Mobile sin JS ------------- */
.main {
grid-area: main;
display: grid;
grid-row-gap: 1rem;
}
/* fix hr when is children of grid */
hr {
width: 100%;
}
.playlist-metadata {
display: grid;
grid-template-columns: 1fr;
margin: auto;
grid-template-areas:
"play-title"
"play-action";
}
.play-title {
grid-area: play-title;
text-align: center;
}
.play-action {
grid-area: play-action;
}
/* Video list item */
.video-container {
display: grid;
grid-row-gap: 0.5rem;
}
.item-box {
display: grid;
grid-template-columns: 1.9fr 0.1fr;
grid-template-rows: 1fr;
grid-gap: 1px;
grid-template-areas:
"item-video item-checkbox";
}
.item-video {
grid-area: item-video;
display: grid;
grid-template-columns: auto;
grid-template-rows: repeat(4, auto);
grid-row-gap: 0.4rem;
grid-template-areas:
"thumbnail-box"
"info-box";
align-items: center;
font-size: 0.7rem;
}
.item-video a {
text-decoration: none;
cursor: pointer;
}
.thumbnail-box {
grid-area: thumbnail-box;
position: relative;
}
.thumbnail {
padding: 28.125%;
position: relative;
box-sizing: border-box;
}
.thumbnail-img {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
object-fit: cover;
}
.length {
z-index: 100;
position: absolute;
background-color: rgba(35, 35, 35, 0.75);
color: #fff;
border-radius: 2px;
padding: 2px;
font-size: 16px;
right: 0.25em;
bottom: -0.75em;
}
.playlist-item .thumbnail-info {
position: absolute;
right: 0px;
bottom: 0px;
height: 100%;
width: 50%;
text-align: center;
white-space: pre-line;
opacity: .8;
color: var(--text);
font-size: 0.8125rem;
background: var(--secondary-background);
padding: 0;
}
.playlist-item .thumbnail-info span {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
text-transform: none;
}
.thumbnail-img {
margin: auto;
display: block;
max-height: 100%;
max-width: 100%;
}
.info-box {
grid-area: info-box;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto auto auto auto;
grid-gap: 1px;
grid-template-areas:
"."
"."
"."
"."
".";
}
.title {
font-size: 0.8rem;
margin: 0px;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.info-box address {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.thumbnail-info {
background-color: var(--time-background);
color: #fff;
padding: 2px 5px;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
position: absolute;
right: 0;
bottom: .2rem;
}
.item-checkbox {
grid-area: item-checkbox;
justify-self: start;
align-self: center;
min-width: 30px;
margin: 0px;
}
.stats {
display: flex;
justify-content: space-between;
}
.horizontal-stats {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.horizontal-stats > li {
display: inline;
}
.horizontal-stats > li:first-child::after {
content: " | ";
}
/* pagination */
.main .pagination-container {
display: grid;
justify-content: center;
}
.main .pagination-container .pagination-list {
display: grid;
grid-auto-flow: column;
grid-column-gap: 0.5rem;
}
.main .pagination-container .pagination-list .page-link {
border-style: none;
font-weight: bold;
text-align: center;
background: var(--secondary-focus);
text-decoration: none;
align-self: center;
padding: .5rem;
width: 1rem;
}
.main .pagination-container .pagination-list .page-link.is-current {
background: var(--secondary-background);
}
/* /video list item */
.footer {
grid-area: footer;
}
.footer > p {
text-align: center;
}
@media (min-width: 480px) {
.item-video {
font-size: 0.85rem;
}
.info-box {
grid-gap: 2px;
}
.title {
font-size: 1rem;
}
}
@media (min-width: 600px) {
.video-container {
display: grid;
grid-row-gap: 0.5rem;
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 992px) {
body {
display: grid;
grid-template-columns: 0.3fr 2fr 1fr 0.3fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header header"
"main main main main"
"footer footer footer footer";
}
.form {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". search-box search-button dropdown";
grid-area: form;
position: relative;
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-columns: minmax(50px, 100px);
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
background: var(--background);
padding-right: 4rem;
z-index: 1;
position: absolute;
}
.playlist-metadata {
max-width: 50vw;
}
/* playlist */
.playlist {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". play-box play-add play-clean";
grid-area: playlist;
}
.play-clean {
grid-template-columns: minmax(50px, 100px);
}
.play-clean > button {
padding-left: 0px;
padding-right: 0px;
padding-bottom: 6px;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
color: var(--buttom-text);
border-radius: 5px;
cursor: pointer;
}
.video-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-row-gap: 1rem;
grid-column-gap: 1rem;
}
}

349
youtube/static/normalize.css vendored Normal file
View File

@ -0,0 +1,349 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

537
youtube/static/playlist.css Normal file
View File

@ -0,0 +1,537 @@
html {
font-family: "liberation serif", "times new roman", calibri, carlito, serif;
background: var(--background);
color: var(--text);
}
body {
display: grid;
grid-gap: 20px;
grid-template-areas:
"header"
"main"
"footer";
/* Fix height */
height: 100vh;
grid-template-rows: auto 1fr auto;
/* fix top and bottom */
margin-left: 1rem;
margin-right: 1rem;
}
img {
width: 100%;
height: auto;
}
a:link {
color: var(--link);
}
a:visited {
color: var(--link-visited);
}
input[type="text"],
input[type="search"] {
background: var(--background);
border: 1px solid var(--button-border);
padding: 0.4rem 0.4rem;
font-size: 15px;
color: var(--search-text);
}
input[type='search'] {
border-bottom: 1px solid var(--button-border);
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-radius: 0px;
}
header {
display: grid;
grid-gap: 1px;
grid-template-areas:
"home"
"form"
"playlist";
grid-area: header;
}
.home {
grid-area: home;
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.form {
display: grid;
grid-gap: 4px;
grid-template-areas:
"search-box"
"search-button"
"dropdown";
grid-area: form;
}
.search-box {
grid-area: search-box;
}
.search-button {
grid-area: search-button;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.search-button:hover {
background-color: var(--buttom-hover);
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
}
.dropdown-label {
grid-area: dropdown-label;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.dropdown-label:hover {
background-color: var(--buttom-hover);
}
/* playlist */
.playlist {
display: grid;
grid-gap: 4px;
grid-template-areas:
"play-box"
"play-hidden"
"play-add"
"play-clean";
grid-area: playlist;
}
.play-box {
grid-area: play-box;
}
.play-hidden {
grid-area: play-hidden;
}
.play-add {
grid-area: play-add;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-add:hover {
background-color: var(--buttom-hover);
}
.play-clean {
display: grid;
grid-area: play-clean;
}
.play-clean > button {
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-clean > button:hover {
background-color: var(--buttom-hover);
}
/* /playlist */
/* ------------- Menu Mobile sin JS ---------------- */
/* input hidden */
.opt-box {
display: none;
}
.dropdown-content {
display: none;
grid-area: dropdown-content;
}
label[for=options-toggle-cbox] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#options-toggle-cbox:checked ~ .dropdown-content {
display: inline-grid;
white-space: nowrap;
}
/*- ----------- End Menu Mobile sin JS ------------- */
.main {
grid-area: main;
display: grid;
grid-row-gap: 1rem;
}
/* fix hr when is children of grid */
hr {
width: 100%;
}
.playlist-metadata {
display: grid;
grid-template-columns: 1fr;
margin: auto;
grid-template-areas:
"author"
"summary"
"playlist-stats";
}
.author {
grid-area: author;
display: grid;
grid-template-columns: 100px 1fr;
grid-column-gap: 1rem;
align-items: center;
justify-self: center;
}
.summary {
grid-area: summary;
justify-self: start;
}
.playlist-stats {
grid-area: playlist-stats;
justify-self: start;
}
/* Video list item */
.video-container {
display: grid;
grid-row-gap: 0.5rem;
}
.item-box {
display: grid;
grid-template-columns: 1.9fr 0.1fr;
grid-template-rows: 1fr;
grid-gap: 1px;
grid-template-areas:
"item-video item-checkbox";
}
.item-video {
grid-area: item-video;
display: grid;
grid-template-columns: auto;
grid-template-rows: repeat(4, auto);
grid-row-gap: 0.4rem;
grid-template-areas:
"thumbnail-box"
"info-box";
align-items: center;
font-size: 0.7rem;
}
.item-video a {
text-decoration: none;
cursor: pointer;
}
.thumbnail-box {
grid-area: thumbnail-box;
position: relative;
}
.thumbnail {
padding: 28.125%;
position: relative;
box-sizing: border-box;
}
.thumbnail-img {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
object-fit: cover;
}
.length {
z-index: 100;
position: absolute;
background-color: rgba(35, 35, 35, 0.75);
color: #fff;
border-radius: 2px;
padding: 2px;
font-size: 16px;
right: 0.25em;
bottom: -0.75em;
}
.playlist-item .thumbnail-info {
position: absolute;
right: 0px;
bottom: 0px;
height: 100%;
width: 50%;
text-align: center;
white-space: pre-line;
opacity: .8;
color: var(--text);
font-size: 0.8125rem;
background: var(--secondary-background);
padding: 0;
}
.playlist-item .thumbnail-info span {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
text-transform: none;
}
.thumbnail-img {
margin: auto;
display: block;
max-height: 100%;
max-width: 100%;
}
.info-box {
grid-area: info-box;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto auto auto auto;
grid-gap: 1px;
grid-template-areas:
"."
"."
"."
"."
".";
}
.title {
font-size: 0.8rem;
margin: 0px;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.info-box address {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.thumbnail-info {
background-color: var(--time-background);
color: #fff;
padding: 2px 5px;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
position: absolute;
right: 0;
bottom: .2rem;
}
.item-checkbox {
grid-area: item-checkbox;
justify-self: start;
align-self: center;
min-width: 30px;
margin: 0px;
}
.stats {
display: flex;
justify-content: space-between;
}
.horizontal-stats {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.horizontal-stats > li {
display: inline;
}
.horizontal-stats > li:first-child::after {
content: " | ";
}
/* pagination */
.main .pagination-container {
display: grid;
justify-content: center;
}
.main .pagination-container .pagination-list {
display: grid;
grid-auto-flow: column;
grid-column-gap: 0.5rem;
}
.main .pagination-container .pagination-list .page-link {
border-style: none;
font-weight: bold;
text-align: center;
background: var(--secondary-focus);
text-decoration: none;
align-self: center;
padding: .5rem;
width: 1rem;
}
.main .pagination-container .pagination-list .page-link.is-current {
background: var(--secondary-background);
}
/* /video list item */
.footer {
grid-area: footer;
}
.footer > p {
text-align: center;
}
@media (min-width: 480px) {
.item-video {
font-size: 0.85rem;
}
.info-box {
grid-gap: 2px;
}
.title {
font-size: 1rem;
}
}
@media (min-width: 600px) {
.video-container {
display: grid;
grid-row-gap: 0.5rem;
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 992px) {
body {
display: grid;
grid-template-columns: 0.3fr 2fr 1fr 0.3fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header header"
"main main main main"
"footer footer footer footer";
}
.form {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". search-box search-button dropdown";
grid-area: form;
position: relative;
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-columns: minmax(50px, 100px);
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
background: var(--background);
padding-right: 4rem;
z-index: 1;
position: absolute;
}
.playlist-metadata {
max-width: 50vw;
}
/* playlist */
.playlist {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". play-box play-add play-clean";
grid-area: playlist;
}
.play-clean {
grid-template-columns: minmax(50px, 100px);
}
.play-clean > button {
padding-left: 0px;
padding-right: 0px;
padding-bottom: 6px;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
color: var(--buttom-text);
border-radius: 5px;
cursor: pointer;
}
.video-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-row-gap: 1rem;
grid-column-gap: 1rem;
}
}

495
youtube/static/search.css Normal file
View File

@ -0,0 +1,495 @@
html {
font-family: "liberation serif", "times new roman", calibri, carlito, serif;
background: var(--background);
color: var(--text);
}
body {
display: grid;
grid-gap: 20px;
grid-template-areas:
"header"
"main"
"footer";
/* Fix height */
height: 100vh;
grid-template-rows: auto 1fr auto;
/* fix top and bottom */
margin-left: 1rem;
margin-right: 1rem;
}
img {
width: 100%;
height: auto;
}
a:link {
color: var(--link);
}
a:visited {
color: var(--link-visited);
}
input[type="text"],
input[type="search"] {
background: var(--background);
border: 1px solid var(--button-border);
padding: 0.4rem 0.4rem;
font-size: 15px;
color: var(--search-text);
}
input[type='search'] {
border-bottom: 1px solid var(--button-border);
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-radius: 0px;
}
header {
display: grid;
grid-gap: 1px;
grid-template-areas:
"home"
"form"
"playlist";
grid-area: header;
}
.home {
grid-area: home;
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.form {
display: grid;
grid-gap: 4px;
grid-template-areas:
"search-box"
"search-button"
"dropdown";
grid-area: form;
}
.search-box {
grid-area: search-box;
}
.search-button {
grid-area: search-button;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.search-button:hover {
background-color: var(--buttom-hover);
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
}
.dropdown-label {
grid-area: dropdown-label;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.dropdown-label:hover {
background-color: var(--buttom-hover);
}
/* playlist */
.playlist {
display: grid;
grid-gap: 4px;
grid-template-areas:
"play-box"
"play-hidden"
"play-add"
"play-clean";
grid-area: playlist;
}
.play-box {
grid-area: play-box;
}
.play-hidden {
grid-area: play-hidden;
}
.play-add {
grid-area: play-add;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-add:hover {
background-color: var(--buttom-hover);
}
.play-clean {
display: grid;
grid-area: play-clean;
}
.play-clean > button {
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-clean > button:hover {
background-color: var(--buttom-hover);
}
/* /playlist */
/* ------------- Menu Mobile sin JS ---------------- */
/* input hidden */
.opt-box {
display: none;
}
.dropdown-content {
display: none;
grid-area: dropdown-content;
}
label[for=options-toggle-cbox] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#options-toggle-cbox:checked ~ .dropdown-content {
display: inline-grid;
white-space: nowrap;
}
/*- ----------- End Menu Mobile sin JS ------------- */
.main {
grid-area: main;
display: grid;
grid-row-gap: 1rem;
}
/* fix hr when is children of grid */
hr {
width: 100%;
}
.result-info {
justify-self: center;
}
/* pagination */
.main .pagination-container {
display: grid;
justify-content: center;
}
.main .pagination-container .pagination-list {
display: grid;
grid-auto-flow: column;
grid-column-gap: 0.5rem;
}
.main .pagination-container .pagination-list .page-link {
border-style: none;
font-weight: bold;
text-align: center;
background: var(--secondary-focus);
text-decoration: none;
align-self: center;
padding: .5rem;
width: 1rem;
}
.main .pagination-container .pagination-list .page-link.is-current {
background: var(--secondary-background);
}
/* Video list item */
.video-container {
display: grid;
grid-row-gap: 0.5rem;
}
.length {
z-index: 100;
position: absolute;
background-color: rgba(35, 35, 35, 0.75);
color: #fff;
border-radius: 2px;
padding: 2px;
font-size: 16px;
right: 0.25em;
bottom: -0.75em;
}
.item-box {
display: grid;
grid-template-columns: 1.9fr 0.1fr;
grid-template-rows: 1fr;
grid-gap: 1px;
grid-template-areas:
"item-video item-checkbox";
}
.item-video {
grid-area: item-video;
display: grid;
grid-template-columns: auto;
grid-template-rows: repeat(4, auto);
grid-row-gap: 0.4rem;
grid-template-areas:
"thumbnail-box"
"info-box";
align-items: center;
font-size: 0.7rem;
}
.item-video a {
text-decoration: none;
cursor: pointer;
}
.item-video.channel-item .thumbnail.channel {
padding: 0px;
text-align: center;
}
.item-video.channel-item .thumbnail-img.channel {
width: 56.25%;
position: relative;
}
.thumbnail-box {
grid-area: thumbnail-box;
position: relative;
}
.playlist-item .thumbnail-info {
position: absolute;
right: 0px;
bottom: 0px;
height: 100%;
width: 50%;
text-align: center;
white-space: pre-line;
opacity: .8;
color: var(--text);
font-size: 0.8125rem;
background: var(--secondary-background);
padding: 0;
}
.playlist-item .thumbnail-info span {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
text-transform: none;
}
.thumbnail {
padding: 28.125%;
position: relative;
box-sizing: border-box;
}
.thumbnail-img {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
object-fit: cover;
}
.title {
font-size: 0.8rem;
margin: 0px;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.info-box address {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.thumbnail-info {
background-color: var(--time-background);
color: #fff;
padding: 2px 5px;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
position: absolute;
right: 0;
bottom: .2rem;
}
.item-checkbox {
grid-area: item-checkbox;
justify-self: start;
align-self: center;
min-width: 30px;
margin: 0px;
}
.stats {
display: flex;
justify-content: space-between;
}
.horizontal-stats > li {
display: inline;
}
.horizontal-stats > li:first-child::after {
content: " | ";
}
/* /video list item */
.footer {
grid-area: footer;
}
.footer > p {
text-align: center;
}
@media (min-width: 480px) {
.item-video {
font-size: 0.85rem;
}
.info-box {
grid-gap: 2px;
}
.title {
font-size: 1rem;
}
}
@media (min-width: 600px) {
.video-container {
display: grid;
grid-row-gap: 0.5rem;
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 992px) {
body {
display: grid;
grid-template-columns: 0.3fr 2fr 1fr 0.3fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header header"
"main main main main"
"footer footer footer footer";
}
.form {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". search-box search-button dropdown";
grid-area: form;
position: relative;
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-columns: minmax(50px, 100px);
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
background: var(--background);
padding-right: 4rem;
z-index: 1;
position: absolute;
}
/* playlist */
.playlist {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". play-box play-add play-clean";
grid-area: playlist;
}
.play-clean {
grid-template-columns: minmax(50px, 100px);
}
.play-clean > button {
padding-left: 0px;
padding-right: 0px;
padding-bottom: 6px;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
color: var(--buttom-text);
border-radius: 5px;
cursor: pointer;
}
.video-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-row-gap: 1rem;
grid-column-gap: 1rem;
}
}

241
youtube/static/settings.css Normal file
View File

@ -0,0 +1,241 @@
html {
font-family: "liberation serif", "times new roman", calibri, carlito, serif;
background: var(--background);
color: var(--text);
}
body {
display: grid;
grid-gap: 20px;
grid-template-areas:
"header"
"main"
"footer";
/* Fix height */
height: 100vh;
grid-template-rows: auto 1fr auto;
/* fix top and bottom */
margin-left: 1rem;
margin-right: 1rem;
}
a:link {
color: var(--link);
}
a:visited {
color: var(--link-visited);
}
input[type="text"],
input[type="search"] {
background: var(--background);
border: 1px solid var(--button-border);
border-radius: 5px;
padding: 0.4rem 0.4rem;
font-size: 15px;
color: var(--search-text);
}
input[type='search'] {
border-bottom: 1px solid var(--button-border);
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-radius: 0px;
}
header {
display: grid;
grid-gap: 1px;
grid-template-areas:
"home"
"form";
grid-area: header;
}
.home {
grid-area: home;
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.form {
display: grid;
grid-gap: 4px;
grid-template-areas:
"search-box"
"search-button"
"dropdown";
grid-area: form;
}
.search-box {
grid-area: search-box;
}
.search-button {
grid-area: search-button;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.search-button:hover {
background-color: var(--buttom-hover);
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
}
.dropdown-label {
grid-area: dropdown-label;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.dropdown-label:hover {
background-color: var(--buttom-hover);
}
/* ------------- Menu Mobile sin JS ---------------- */
/* input hidden */
.opt-box {
display: none;
}
.dropdown-content {
display: none;
grid-area: dropdown-content;
}
label[for=options-toggle-cbox] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#options-toggle-cbox:checked ~ .dropdown-content {
display: inline-grid;
white-space: nowrap;
}
/*- ----------- End Menu Mobile sin JS ------------- */
.main {
grid-area: main;
margin: 0 auto;
width: 100%;
}
.settings-form {
background-color: var(--secondary-background);
padding: 1rem;
}
.settings-list {
display: grid;
grid-row-gap: 1rem;
list-style: none;
padding: 0px;
}
.setting-item {
display: grid;
grid-template-columns: auto auto;
background-color: var(--secondary-focus);
align-items: center;
padding: 5px;
}
.setting-item select {
max-width: 100%;
justify-self: end;
}
.setting-item input {
max-width: 80px;
justify-self: end;
}
.settings-form input[type="submit"] {
cursor: pointer;
}
.footer {
grid-area: footer;
}
.footer > p {
text-align: center;
}
@media (min-width: 780px) {
body {
display: grid;
grid-template-columns: 0.3fr 2fr 1fr 0.3fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header header"
"main main main main"
"footer footer footer footer";
}
.form {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". search-box search-button dropdown";
grid-area: form;
position: relative;
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-columns: minmax(50px, 100px);
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
position: absolute;
background: var(--background);
padding-right: 4rem;
}
.main {
display: grid;
justify-content: center;
}
.settings-form {
width: 50vw;
}
.setting-item select {
max-width: 250px;
}
.setting-item input {
max-width: 250px;
}
}

View File

@ -0,0 +1,530 @@
html {
font-family: "liberation serif", "times new roman", calibri, carlito, serif;
background: var(--background);
color: var(--text);
}
body {
display: grid;
grid-gap: 20px;
grid-template-areas:
"header"
"main"
"footer";
/* Fix height */
height: 100vh;
grid-template-rows: auto 1fr auto;
/* fix top and bottom */
margin-left: 1rem;
margin-right: 1rem;
}
img {
width: 100%;
height: auto;
}
a:link {
color: var(--link);
}
a:visited {
color: var(--link-visited);
}
input[type="text"],
input[type="search"] {
background: var(--background);
border: 1px solid var(--button-border);
padding: 0.4rem 0.4rem;
font-size: 15px;
color: var(--search-text);
}
input[type='search'] {
border-bottom: 1px solid var(--button-border);
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-radius: 0px;
}
header {
display: grid;
grid-gap: 1px;
grid-template-areas:
"home"
"form"
"playlist";
grid-area: header;
}
.home {
grid-area: home;
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.form {
display: grid;
grid-gap: 4px;
grid-template-areas:
"search-box"
"search-button"
"dropdown";
grid-area: form;
}
.search-box {
grid-area: search-box;
}
.search-button {
grid-area: search-button;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.search-button:hover {
background-color: var(--buttom-hover);
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
}
.dropdown-label {
grid-area: dropdown-label;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.dropdown-label:hover {
background-color: var(--buttom-hover);
}
/* playlist */
.playlist {
display: grid;
grid-gap: 4px;
grid-template-areas:
"play-box"
"play-hidden"
"play-add"
"play-clean";
grid-area: playlist;
}
.play-box {
grid-area: play-box;
}
.play-hidden {
grid-area: play-hidden;
}
.play-add {
grid-area: play-add;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-add:hover {
background-color: var(--buttom-hover);
}
.play-clean {
display: grid;
grid-area: play-clean;
}
.play-clean > button {
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-clean > button:hover {
background-color: var(--buttom-hover);
}
/* /playlist */
/* ------------- Menu Mobile sin JS ---------------- */
/* input hidden */
.opt-box {
display: none;
}
.dropdown-content {
display: none;
grid-area: dropdown-content;
}
label[for=options-toggle-cbox] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#options-toggle-cbox:checked ~ .dropdown-content {
display: inline-grid;
white-space: nowrap;
}
/*- ----------- End Menu Mobile sin JS ------------- */
.main {
grid-area: main;
display: grid;
grid-row-gap: 1rem;
}
/* fix hr when is children of grid */
hr {
width: 100%;
}
.sidebar-links {
display: grid;
grid-template-columns: 1fr;
margin: auto;
grid-template-areas:
"sidebar-title"
"sidebar-action";
align-items: center;
justify-items: center;
justify-content: center;
}
.sidebar-title {
grid-area: sidebar-title;
text-align: center;
}
.sidebar-action {
grid-area: sidebar-action;
}
/* Video list item */
.video-container {
display: grid;
grid-row-gap: 0.5rem;
}
.item-box {
display: grid;
grid-template-columns: 1.9fr 0.1fr;
grid-template-rows: 1fr;
grid-gap: 1px;
grid-template-areas:
"item-video item-checkbox";
}
.item-video {
grid-area: item-video;
display: grid;
grid-template-columns: auto;
grid-template-rows: repeat(4, auto);
grid-row-gap: 0.4rem;
grid-template-areas:
"thumbnail-box"
"info-box";
align-items: center;
font-size: 0.7rem;
}
.item-video a {
text-decoration: none;
cursor: pointer;
}
.thumbnail-box {
grid-area: thumbnail-box;
position: relative;
}
.thumbnail {
padding: 28.125%;
position: relative;
box-sizing: border-box;
}
.thumbnail-img {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
object-fit: cover;
}
.length {
z-index: 100;
position: absolute;
background-color: rgba(35, 35, 35, 0.75);
color: #fff;
border-radius: 2px;
padding: 2px;
font-size: 16px;
right: 0.25em;
bottom: -0.75em;
}
.playlist-item .thumbnail-info {
position: absolute;
right: 0px;
bottom: 0px;
height: 100%;
width: 50%;
text-align: center;
white-space: pre-line;
opacity: .8;
color: var(--text);
font-size: 0.8125rem;
background: var(--secondary-background);
padding: 0;
}
.playlist-item .thumbnail-info span {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
text-transform: none;
}
.thumbnail-img {
margin: auto;
display: block;
max-height: 100%;
max-width: 100%;
}
.info-box {
grid-area: info-box;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto auto auto auto;
grid-gap: 1px;
grid-template-areas:
"."
"."
"."
"."
".";
}
.title {
font-size: 0.8rem;
margin: 0px;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.info-box address {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.thumbnail-info {
background-color: var(--time-background);
color: #fff;
padding: 2px 5px;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
position: absolute;
right: 0;
bottom: .2rem;
}
.item-checkbox {
grid-area: item-checkbox;
justify-self: start;
align-self: center;
min-width: 30px;
margin: 0px;
}
.stats {
display: flex;
justify-content: space-between;
}
.horizontal-stats {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.horizontal-stats > li {
display: inline;
}
.horizontal-stats > li:first-child::after {
content: " | ";
}
/* pagination */
.main .pagination-container {
display: grid;
justify-content: center;
}
.main .pagination-container .pagination-list {
display: grid;
grid-auto-flow: column;
grid-column-gap: 0.5rem;
}
.main .pagination-container .pagination-list .page-link {
border-style: none;
font-weight: bold;
text-align: center;
background: var(--secondary-focus);
text-decoration: none;
align-self: center;
padding: .5rem;
width: 1rem;
}
.main .pagination-container .pagination-list .page-link.is-current {
background: var(--secondary-background);
}
/* /video list item */
.footer {
grid-area: footer;
}
.footer > p {
text-align: center;
}
@media (min-width: 480px) {
.item-video {
font-size: 0.85rem;
}
.info-box {
grid-gap: 2px;
}
.title {
font-size: 1rem;
}
}
@media (min-width: 600px) {
.video-container {
display: grid;
grid-row-gap: 0.5rem;
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 992px) {
body {
display: grid;
grid-template-columns: 0.3fr 2fr 1fr 0.3fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header header"
"main main main main"
"footer footer footer footer";
}
.form {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". search-box search-button dropdown";
grid-area: form;
position: relative;
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-columns: minmax(50px, 100px);
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
background: var(--background);
padding-right: 4rem;
z-index: 1;
position: absolute;
}
.sidebar-links {
max-width: 50vw;
}
/* playlist */
.playlist {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". play-box play-add play-clean";
grid-area: playlist;
}
.play-clean {
grid-template-columns: minmax(50px, 100px);
}
.play-clean > button {
padding-left: 0px;
padding-right: 0px;
padding-bottom: 6px;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
color: var(--buttom-text);
border-radius: 5px;
cursor: pointer;
}
.video-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-row-gap: 1rem;
grid-column-gap: 1rem;
}
}

View File

@ -0,0 +1,403 @@
html {
font-family: "liberation serif", "times new roman", calibri, carlito, serif;
background: var(--background);
color: var(--text);
}
body {
display: grid;
grid-gap: 20px;
grid-template-areas:
"header"
"main"
"footer";
/* Fix height */
height: 100vh;
grid-template-rows: auto 1fr auto;
/* fix top and bottom */
margin-left: 1rem;
margin-right: 1rem;
}
img {
width: 100%;
height: auto;
}
a:link {
color: var(--link);
}
a:visited {
color: var(--link-visited);
}
input[type="text"],
input[type="search"] {
background: var(--background);
border: 1px solid var(--button-border);
padding: 0.4rem 0.4rem;
font-size: 15px;
color: var(--search-text);
}
input[type='search'] {
border-bottom: 1px solid var(--button-border);
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-radius: 0px;
}
header {
display: grid;
grid-gap: 1px;
grid-template-areas:
"home"
"form"
"playlist";
grid-area: header;
}
.home {
grid-area: home;
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.form {
display: grid;
grid-gap: 4px;
grid-template-areas:
"search-box"
"search-button"
"dropdown";
grid-area: form;
}
.search-box {
grid-area: search-box;
}
.search-button {
grid-area: search-button;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.search-button:hover {
background-color: var(--buttom-hover);
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
}
.dropdown-label {
grid-area: dropdown-label;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.dropdown-label:hover {
background-color: var(--buttom-hover);
}
/* playlist */
.playlist {
display: grid;
grid-gap: 4px;
grid-template-areas:
"play-box"
"play-hidden"
"play-add"
"play-clean";
grid-area: playlist;
}
.play-box {
grid-area: play-box;
}
.play-hidden {
grid-area: play-hidden;
}
.play-add {
grid-area: play-add;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-add:hover {
background-color: var(--buttom-hover);
}
.play-clean {
display: grid;
grid-area: play-clean;
}
.play-clean > button {
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-clean > button:hover {
background-color: var(--buttom-hover);
}
/* /playlist */
/* ------------- Menu Mobile sin JS ---------------- */
/* input hidden */
.opt-box {
display: none;
}
.dropdown-content {
display: none;
grid-area: dropdown-content;
}
label[for=options-toggle-cbox] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#options-toggle-cbox:checked ~ .dropdown-content {
display: inline-grid;
white-space: nowrap;
}
/*- ----------- End Menu Mobile sin JS ------------- */
.main {
grid-area: main;
display: grid;
grid-row-gap: 1rem;
}
/* fix hr when is children of grid */
hr {
width: 100%;
}
.import-export {
display: grid;
grid-template-columns: 1fr;
margin: auto;
grid-template-areas:
"subscriptions-import-form";
align-items: center;
justify-items: center;
justify-content: center;
}
.subscriptions-import-form {
grid-area: subscriptions-import-form;
text-align: center;
}
.sub-list-controls {
display: grid;
grid-row-gap: 0.2rem;
}
.subscriptions-import-form input[type='submit'],
.sub-list-controls button[type='submit'],
.sub-list-controls input[type='reset'] {
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.tag-group-list {
display: grid;
grid-template-columns: 1fr;
margin: auto;
}
.sub-list {
display: grid;
grid-template-columns: 1fr;
margin: auto;
}
/* pagination */
.main .pagination-container {
display: grid;
justify-content: center;
}
.main .pagination-container .pagination-list {
display: grid;
grid-auto-flow: column;
grid-column-gap: 0.5rem;
}
.main .pagination-container .pagination-list .page-link {
border-style: none;
font-weight: bold;
text-align: center;
background: var(--secondary-focus);
text-decoration: none;
align-self: center;
padding: .5rem;
width: 1rem;
}
.main .pagination-container .pagination-list .page-link.is-current {
background: var(--secondary-background);
}
/* /video list item */
.footer {
grid-area: footer;
}
.footer > p {
text-align: center;
}
@media (min-width: 480px) {
.item-video {
font-size: 0.85rem;
}
.info-box {
grid-gap: 2px;
}
.title {
font-size: 1rem;
}
}
@media (min-width: 600px) {
.video-container {
display: grid;
grid-row-gap: 0.5rem;
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 992px) {
body {
display: grid;
grid-template-columns: 0.3fr 2fr 1fr 0.3fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header header"
"main main main main"
"footer footer footer footer";
}
.form {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". search-box search-button dropdown";
grid-area: form;
position: relative;
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-columns: minmax(50px, 100px);
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
background: var(--background);
padding-right: 4rem;
z-index: 1;
position: absolute;
}
.import-export {
max-width: 50vw;
}
.sub-list-controls {
grid-template-columns: repeat(8, auto);
max-height: 1rem;
grid-column-gap: 0.5rem;
justify-content: center;
align-items: center;
}
/* playlist */
.playlist {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". play-box play-add play-clean";
grid-area: playlist;
}
.play-clean {
grid-template-columns: minmax(50px, 100px);
}
.play-clean > button {
padding-left: 0px;
padding-right: 0px;
padding-bottom: 6px;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
color: var(--buttom-text);
border-radius: 5px;
cursor: pointer;
}
.video-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-row-gap: 1rem;
grid-column-gap: 1rem;
}
}

633
youtube/static/watch.css Normal file
View File

@ -0,0 +1,633 @@
html {
font-family: "liberation serif", "times new roman", calibri, carlito, serif;
background: var(--background);
color: var(--text);
}
body {
display: grid;
grid-gap: 20px;
grid-template-areas:
"header"
"main"
"footer";
/* Fix height */
height: 100vh;
grid-template-rows: auto 1fr auto;
/* fix top and bottom */
margin-left: 1rem;
margin-right: 1rem;
}
img {
width: 100%;
height: auto;
}
video {
width: 100%;
height: auto;
max-height: 480px;
}
a:link {
color: var(--link);
}
a:visited {
color: var(--link-visited);
}
input[type="text"],
input[type="search"] {
background: var(--background);
border: 1px solid var(--button-border);
border-radius: 5px;
padding: 0.4rem 0.4rem;
font-size: 15px;
color: var(--search-text);
}
input[type='search'] {
border-bottom: 1px solid var(--button-border);
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-radius: 0px;
}
header {
display: grid;
grid-gap: 4px;
grid-template-areas:
"home"
"form"
"playlist";
grid-area: header;
}
.home {
grid-area: home;
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
margin-top: 1rem;
}
.form {
display: grid;
grid-gap: 4px;
grid-template-areas:
"search-box"
"search-button"
"dropdown";
grid-area: form;
}
.search-box {
grid-area: search-box;
}
.search-button {
grid-area: search-button;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.search-button:hover {
background-color: var(--buttom-hover);
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
z-index: 1;
}
.dropdown-label {
grid-area: dropdown-label;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.dropdown-label:hover {
background-color: var(--buttom-hover);
}
.playlist {
display: grid;
grid-gap: 4px;
grid-template-areas:
"play-box"
"play-hidden"
"play-add"
"play-clean";
grid-area: playlist;
}
.play-box {
grid-area: play-box;
}
.play-hidden {
grid-area: play-hidden;
}
.play-add {
grid-area: play-add;
cursor: pointer;
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-add:hover {
background-color: var(--buttom-hover);
}
.play-clean {
display: grid;
grid-area: play-clean;
}
.play-clean > button {
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
border: 1px solid var(--button-border);
color: var(--buttom-text);
border-radius: 5px;
}
.play-clean > button:hover {
background-color: var(--buttom-hover);
}
/* ------------- Menu Mobile sin JS ---------------- */
/* input hidden */
.opt-box {
display: none;
}
.dropdown-content {
display: none;
grid-area: dropdown-content;
}
label[for=options-toggle-cbox] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#options-toggle-cbox:checked ~ .dropdown-content {
display: inline-grid;
white-space: nowrap;
padding-left: 1rem;
}
/*- ----------- End Menu Mobile sin JS ------------- */
.main {
grid-area: main;
margin: 0 auto;
display: grid;
grid-grap: 1px;
grid-template-columns: 1fr;
grid-template-areas:
"sc-video"
"sc-info";
}
.sc-video { grid-area: sc-video; }
.sc-info {
display: grid;
grid-template-columns: 1fr;
grid-gap: 1px;
grid-template-areas:
"video-info"
"side-videos"
"comments-area-outer";
grid-area: sc-info;
}
.video-info {
grid-area: video-info;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-areas:
"v-title v-title"
"v-uploaded v-views"
"v-published v-likes-dislikes"
"external-player-controls v-checkbox"
"v-download v-download"
"v-description v-description"
"v-music-list v-music-list"
"v-more-info v-more-info";
}
.v-title {
grid-area: v-title;
margin: 0px;
}
.v-uploaded { grid-area: v-uploaded; }
.v-views {
grid-area: v-views;
justify-self: end;
}
.v-published { grid-area: v-published; }
.v-likes-dislikes {
grid-area: v-likes-dislikes;
justify-self: end;
}
.external-player-controls {
grid-area: external-player-controls;
}
.external-player-controls input.speed {
width: 65px;
text-align: center;
}
.v-checkbox {
grid-area: v-checkbox;
justify-self: end;
}
.v-download { grid-area: v-download; }
.v-download > ul.download-dropdown-content {
background: var(--secondary-background);
padding-left: 0px;
}
.v-download > ul.download-dropdown-content > li.download-format {
list-style: none;
padding: 0.4rem 0;
padding-left: 1rem;
}
.v-download > ul.download-dropdown-content > li.download-format a.download-link {
text-decoration: none;
}
.v-description {
grid-area: v-description;
background-color: var(--secondary-background);
margin-top: 0.4rem;
white-space: pre-wrap;
word-wrap: break-word;
padding: 5px;
}
.v-music-list {
grid-area: v-music-list;
padding-bottom: 1rem;
}
.v-music-list table,th,td{
border: 1px solid;
}
.v-music-list th,td{
padding-left:4px;
padding-right:5px;
}
.v-music-list caption{
text-align:left;
font-weight:bold;
margin-bottom:5px;
}
.v-more-info {
grid-area: v-more-info;
}
.v-more-info > .more-info-content {
background-color: var(--secondary-background);
padding: 5px;
}
.side-videos { grid-area: side-videos; }
/* playlist items */
.side-videos .site-playlist {
border-style: solid;
border-width: 2px;
border-color: var(--secondary-focus);
margin-bottom: 1rem;
}
.side-videos .site-playlist .playlist-header {
background-color: var(--secondary-background);
padding: 1rem;
border-bottom-style: solid;
border-bottom-width: 2px;
border-bottom-color: var(--secondary-focus);
}
.side-videos .site-playlist .playlist-header h3 {
margin: 0px;
padding: 0px;
}
.side-videos .site-playlist .playlist-header .playlist-metadata {
list-style: none;
display: grid;
justify-content: start;
padding: 0;
margin: 0px;
grid-template-columns: repeat(3, auto);
grid-column-gap: 1rem;
}
.side-videos .site-playlist .playlist-videos {
display: grid;
grid-row-gap: 1rem;
height: 300px;
overflow-y: scroll;
padding-top: 1rem;
}
.side-videos .site-playlist .playlist-videos article.item-box {
padding-left: 1rem;
}
/* /playlist items */
.comments-area-outer { grid-area: comments-area-outer; }
.related-videos-inner {
padding-top: 10px;
display: grid;
grid-row-gap: 1rem;
}
.item-box {
display: grid;
grid-template-columns: 1.9fr 0.1fr;
grid-template-rows: 1fr;
grid-gap: 1px;
grid-template-areas:
"item-video item-checkbox";
}
.item-video {
grid-area: item-video;
display: grid;
grid-template-columns: auto;
grid-template-rows: repeat(4, auto);
grid-row-gap: 0.4rem;
grid-template-areas:
"thumbnail-box"
"info-box";
align-items: center;
font-size: 0.7rem;
}
.item-video a {
text-decoration: none;
cursor: pointer;
}
.thumbnail-box {
grid-area: thumbnail-box;
position: relative;
}
.thumbnail {
padding: 28.125%;
position: relative;
box-sizing: border-box;
}
.thumbnail-img {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
object-fit: cover;
}
.length {
z-index: 100;
position: absolute;
background-color: rgba(35, 35, 35, 0.75);
color: #fff;
border-radius: 2px;
padding: 2px;
font-size: 16px;
right: 0.25em;
bottom: -0.75em;
}
.info-box {
grid-area: info-box;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto auto auto auto;
grid-gap: 1px;
grid-template-areas:
"."
"."
"."
"."
".";
}
.title {
font-size: 0.8rem;
margin: 0px;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.info-box address {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.thumbnail-info {
background-color: var(--time-background);
color: #fff;
padding: 2px 5px;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
position: absolute;
right: 0;
bottom: .2rem;
}
.item-checkbox {
grid-area: item-checkbox;
justify-self: start;
align-self: center;
min-width: 30px;
margin: 0px;
}
.stats {
display: flex;
justify-content: space-between;
}
/* comments */
.comments-area {
display: grid;
grid-row-gap: 0.5rem;
}
.comments {
display: grid;
grid-row-gap: 0.5rem;
}
.comment {
display: grid;
grid-template-columns: repeat(3, auto) 3fr;
grid-template-rows: repeat(4, auto);
grid-column-gap: 0.4rem;
grid-template-areas:
"author-avatar author-name permalink permalink"
"author-avatar comment-text comment-text comment-text"
". comment-likes comment-likes comment-likes"
". button-row button-row button-row";
background: var(--secondary-background);
}
.author-avatar { grid-area: author-avatar; }
.author-name { grid-area: author-name; }
.permalink { grid-area: permalink; }
.comment-text { grid-area: comment-text; }
.comment-likes { grid-area: comment-likes; }
.button-row { grid-area: button-row; }
.more-comments {
justify-self: center;
margin-top: 10px;
margin-bottom: 10px;
background: var(--secondary-background);
padding: 5px;
/* disable text selection */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.footer {
grid-area: footer;
}
.footer > p {
text-align: center;
}
@media (min-width: 480px) {
.item-video {
font-size: 0.85rem;
}
.title {
font-size: 1rem;
}
}
@media (min-width: 992px) {
body {
display: grid;
grid-template-columns: 0.3fr 2fr 1fr 0.3fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header header"
". main main ."
"footer footer footer footer";
}
.form {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". search-box search-button dropdown";
grid-area: form;
position: relative;
}
.dropdown {
display: grid;
grid-gap: 1px;
grid-template-columns: minmax(50px, 120px);
grid-template-areas:
"dropdown-label"
"dropdown-content";
grid-area: dropdown;
background: var(--background);
padding-right: 4rem;
z-index: 1;
position: absolute;
}
.playlist {
display: grid;
grid-gap: 1px;
grid-template-columns: 1fr 1.4fr 0.3fr 1.3fr;
grid-template-areas: ". play-box play-add play-clean";
grid-area: playlist;
}
.play-clean {
grid-template-columns: minmax(50px, 120px);
}
.play-clean > button {
padding-bottom: 6px;
padding-left: .75em;
padding-right: .75em;
padding-top: 6px;
text-align: center;
white-space: nowrap;
background-color: var(--buttom);
color: var(--buttom-text);
border-radius: 5px;
cursor: pointer;
}
.main {
grid-area: main;
margin: 0px;
}
.sc-info {
display: grid;
grid-template-columns: 3fr 1fr;
grid-gap: 1px 40px;
grid-template-areas:
"video-info side-videos"
"comments-area-outer side-videos"
". side-videos";
grid-area: sc-info;
}
}

View File

@ -1,203 +1,238 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; media-src 'self' https://*.googlevideo.com; {{ "img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com;" if not settings.proxy_images else "" }}">
<title>{{ page_title }}</title> <title>{{ page_title }}</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; media-src 'self' https://*.googlevideo.com; <link title="Youtube local" href="/youtube.com/opensearch.xml" rel="search" type="application/opensearchdescription+xml"/>
{{ "img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com;" if not settings.proxy_images else "" }}"> <link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon"/>
<link href="{{ theme_path }}" type="text/css" rel="stylesheet"> <link href="/youtube.com/static/normalize.css" rel="stylesheet"/>
<link href="/youtube.com/shared.css" type="text/css" rel="stylesheet"> <link href="{{ theme_path }}" rel="stylesheet"/>
<link href="/youtube.com/static/comments.css" type="text/css" rel="stylesheet"> {% block style %}
<link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon"> {{ style }}
<link title="Youtube local" href="/youtube.com/opensearch.xml" rel="search" type="application/opensearchdescription+xml"> {% endblock %}
<style type="text/css">
{% block style %}
{{ style }}
{% endblock %}
</style>
</head> </head>
<body> <body>
<header> <header class="header">
<a href="/youtube.com" id="home-link">Home</a> <nav class="home">
<form id="site-search" action="/youtube.com/search"> <a href="/youtube.com" id="home-link">YouTube Local</a>
<input type="search" name="query" class="search-box" value="{{ search_box_value }}" </nav>
{{ "autofocus" if request.path == "/" else "" }}> <form class="form" id="site-search" action="/youtube.com/search">
<button type="submit" value="Search" class="search-button">Search</button> <input type="search" name="query" class="search-box" value="{{ search_box_value }}"
<div class="dropdown"> {{ "autofocus" if request.path == "/" else "" }} placeholder="Type to search...">
<button class="dropdown-label">Options</button> <button type="submit" value="Search" class="search-button">Search</button>
<div class="css-sucks"> <!-- options -->
<div class="dropdown-content"> <div class="dropdown">
<h3>Sort by</h3> <!-- hidden box -->
<input type="radio" id="sort_relevance" name="sort" value="0"> <input id="options-toggle-cbox" class="opt-box" role="button" type="checkbox">
<label for="sort_relevance">Relevance</label> <!-- end hidden box -->
<label class="dropdown-label" for="options-toggle-cbox">Options</label>
<input type="radio" id="sort_upload_date" name="sort" value="2"> <div class="dropdown-content">
<label for="sort_upload_date">Upload date</label> <h3>Sort by</h3>
<div class="option">
<input type="radio" id="sort_view_count" name="sort" value="3"> <input type="radio" id="sort_relevance" name="sort" value="0">
<label for="sort_view_count">View count</label> <label for="sort_relevance">Relevance</label>
<input type="radio" id="sort_rating" name="sort" value="1">
<label for="sort_rating">Rating</label>
<h3>Upload date</h3>
<input type="radio" id="time_any" name="time" value="0">
<label for="time_any">Any</label>
<input type="radio" id="time_last_hour" name="time" value="1">
<label for="time_last_hour">Last hour</label>
<input type="radio" id="time_today" name="time" value="2">
<label for="time_today">Today</label>
<input type="radio" id="time_this_week" name="time" value="3">
<label for="time_this_week">This week</label>
<input type="radio" id="time_this_month" name="time" value="4">
<label for="time_this_month">This month</label>
<input type="radio" id="time_this_year" name="time" value="5">
<label for="time_this_year">This year</label>
<h3>Type</h3>
<input type="radio" id="type_any" name="type" value="0">
<label for="type_any">Any</label>
<input type="radio" id="type_video" name="type" value="1">
<label for="type_video">Video</label>
<input type="radio" id="type_channel" name="type" value="2">
<label for="type_channel">Channel</label>
<input type="radio" id="type_playlist" name="type" value="3">
<label for="type_playlist">Playlist</label>
<input type="radio" id="type_movie" name="type" value="4">
<label for="type_movie">Movie</label>
<input type="radio" id="type_show" name="type" value="5">
<label for="type_show">Show</label>
<h3>Duration</h3>
<input type="radio" id="duration_any" name="duration" value="0">
<label for="duration_any">Any</label>
<input type="radio" id="duration_short" name="duration" value="1">
<label for="duration_short">Short (&lt; 4 minutes)</label>
<input type="radio" id="duration_long" name="duration" value="2">
<label for="duration_long">Long (&gt; 20 minutes)</label>
</div>
</div> </div>
<div class="option">
<input type="radio" id="sort_upload_date" name="sort" value="2">
<label for="sort_upload_date">Upload date</label>
</div>
<div class="option">
<input type="radio" id="sort_view_count" name="sort" value="3">
<label for="sort_view_count">View count</label>
</div>
<div class="option">
<input type="radio" id="sort_rating" name="sort" value="1">
<label for="sort_rating">Rating</label>
</div>
<h3>Upload date</h3>
<div class="option">
<input type="radio" id="time_any" name="time" value="0">
<label for="time_any">Any</label>
</div>
<div class="option">
<input type="radio" id="time_last_hour" name="time" value="1">
<label for="time_last_hour">Last hour</label>
</div>
<div class="option">
<input type="radio" id="time_today" name="time" value="2">
<label for="time_today">Today</label>
</div>
<div class="option">
<input type="radio" id="time_this_week" name="time" value="3">
<label for="time_this_week">This week</label>
</div>
<div class="option">
<input type="radio" id="time_this_month" name="time" value="4">
<label for="time_this_month">This month</label>
</div>
<div class="option">
<input type="radio" id="time_this_year" name="time" value="5">
<label for="time_this_year">This year</label>
</div>
<h3>Type</h3>
<div class="option">
<input type="radio" id="type_any" name="type" value="0">
<label for="type_any">Any</label>
</div>
<div class="option">
<input type="radio" id="type_video" name="type" value="1">
<label for="type_video">Video</label>
</div>
<div class="option">
<input type="radio" id="type_channel" name="type" value="2">
<label for="type_channel">Channel</label>
</div>
<div class="option">
<input type="radio" id="type_playlist" name="type" value="3">
<label for="type_playlist">Playlist</label>
</div>
<div class="option">
<input type="radio" id="type_movie" name="type" value="4">
<label for="type_movie">Movie</label>
</div>
<div class="option">
<input type="radio" id="type_show" name="type" value="5">
<label for="type_show">Show</label>
</div>
<h3>Duration</h3>
<div class="option">
<input type="radio" id="duration_any" name="duration" value="0">
<label for="duration_any">Any</label>
</div>
<div class="option">
<input type="radio" id="duration_short" name="duration" value="1">
<label for="duration_short">Short (&lt; 4 minutes)</label>
</div>
<div class="option">
<input type="radio" id="duration_long" name="duration" value="2">
<label for="duration_long">Long (&gt; 20 minutes)</label>
</div>
</div>
</div>
</form>
{% if header_playlist_names is defined %}
<form class="playlist" id="playlist-edit" action="/youtube.com/edit_playlist" method="post" target="_self">
<input class="play-box" name="playlist_name" id="playlist-name-selection" list="playlist-options" type="search" placeholder="I added your playlist...">
<datalist class="play-hidden" id="playlist-options">
{% for playlist_name in header_playlist_names %}
<option value="{{ playlist_name }}">{{ playlist_name }}</option>
{% endfor %}
</datalist>
<button class="play-add" type="submit" id="playlist-add-button" name="action" value="add">+List</button>
<div class="play-clean">
<button type="reset" id="item-selection-reset">Clear selection</button>
</div> </div>
</form> </form>
{% if header_playlist_names is defined %}
<form id="playlist-edit" action="/youtube.com/edit_playlist" method="post" target="_self">
<input name="playlist_name" id="playlist-name-selection" list="playlist-options" type="text">
<datalist id="playlist-options">
{% for playlist_name in header_playlist_names %}
<option value="{{ playlist_name }}">{{ playlist_name }}</option>
{% endfor %}
</datalist>
<button type="submit" id="playlist-add-button" name="action" value="add">Add to playlist</button>
<button type="reset" id="item-selection-reset">Clear selection</button>
</form>
<script> <script>
/* Takes control of the form if javascript is enabled, so that adding stuff to a playlist will not cause things to stop loading, and will display a status message. If javascript is disabled, the form will still work using regular HTML methods, but causes things on the page (such as the video) to stop loading. */ (function main() {
var playlistAddForm = document.getElementById('playlist-edit'); /* Takes control of the form if javascript is enabled, so that adding stuff to a playlist will not cause things to stop loading, and will display a status message. If javascript is disabled, the form will still work using regular HTML methods, but causes things on the page (such as the video) to stop loading. */
const playlistAddForm = document.getElementById('playlist-edit');
function setStyle(element, property, value){ function setStyle(element, property, value){
element.style[property] = value; element.style[property] = value;
} }
function removeMessage(messageBox){ function removeMessage(messageBox){
messageBox.parentNode.removeChild(messageBox); messageBox.parentNode.removeChild(messageBox);
} }
function displayMessage(text, error=false){ function displayMessage(text, error=false){
let currentMessageBox = document.getElementById('message-box'); let currentMessageBox = document.getElementById('message-box');
if(currentMessageBox !== null){ if(currentMessageBox !== null){
currentMessageBox.parentNode.removeChild(currentMessageBox); currentMessageBox.parentNode.removeChild(currentMessageBox);
} }
let messageBox = document.createElement('div'); let messageBox = document.createElement('div');
if(error){ if(error){
messageBox.setAttribute('role', 'alert'); messageBox.setAttribute('role', 'alert');
} else { } else {
messageBox.setAttribute('role', 'status'); messageBox.setAttribute('role', 'status');
} }
messageBox.setAttribute('id', 'message-box'); messageBox.setAttribute('id', 'message-box');
let textNode = document.createTextNode(text); let textNode = document.createTextNode(text);
messageBox.appendChild(textNode); messageBox.appendChild(textNode);
document.querySelector('main').appendChild(messageBox); document.querySelector('main').appendChild(messageBox);
let currentstyle = window.getComputedStyle(messageBox); let currentstyle = window.getComputedStyle(messageBox);
let removalDelay; let removalDelay;
if(error){ if(error){
removalDelay = 5000; removalDelay = 5000;
} else { } else {
removalDelay = 1500; removalDelay = 1500;
} }
window.setTimeout(setStyle, 20, messageBox, 'opacity', 1); window.setTimeout(setStyle, 20, messageBox, 'opacity', 1);
window.setTimeout(setStyle, removalDelay, messageBox, 'opacity', 0); window.setTimeout(setStyle, removalDelay, messageBox, 'opacity', 0);
window.setTimeout(removeMessage, removalDelay+300, messageBox); window.setTimeout(removeMessage, removalDelay+300, messageBox);
} }
// https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript // https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript
function sendData(event){ function sendData(event){
var clicked_button = document.activeElement; var clicked_button = document.activeElement;
if(clicked_button === null || clicked_button.getAttribute('type') !== 'submit' || clicked_button.parentElement != event.target){ if(clicked_button === null || clicked_button.getAttribute('type') !== 'submit' || clicked_button.parentElement != event.target){
console.log('ERROR: clicked_button not valid'); console.log('ERROR: clicked_button not valid');
return; return;
} }
if(clicked_button.getAttribute('value') !== 'add'){ if(clicked_button.getAttribute('value') !== 'add'){
return; // video(s) are being removed from playlist, just let it refresh the page return; // video(s) are being removed from playlist, just let it refresh the page
} }
event.preventDefault(); event.preventDefault();
var XHR = new XMLHttpRequest(); var XHR = new XMLHttpRequest();
var FD = new FormData(playlistAddForm); var FD = new FormData(playlistAddForm);
if(FD.getAll('video_info_list').length === 0){ if(FD.getAll('video_info_list').length === 0){
displayMessage('Error: No videos selected', true); displayMessage('Error: No videos selected', true);
return; return;
} }
if(FD.get('playlist_name') === ""){ if(FD.get('playlist_name') === ""){
displayMessage('Error: No playlist selected', true); displayMessage('Error: No playlist selected', true);
return; return;
} }
// https://stackoverflow.com/questions/48322876/formdata-doesnt-include-value-of-buttons // https://stackoverflow.com/questions/48322876/formdata-doesnt-include-value-of-buttons
FD.append('action', 'add'); FD.append('action', 'add');
XHR.addEventListener('load', function(event){ XHR.addEventListener('load', function(event){
if(event.target.status == 204){ if(event.target.status == 204){
displayMessage('Added videos to playlist "' + FD.get('playlist_name') + '"'); displayMessage('Added videos to playlist "' + FD.get('playlist_name') + '"');
} else { } else {
displayMessage('Error adding videos to playlist: ' + event.target.status.toString(), true); displayMessage('Error adding videos to playlist: ' + event.target.status.toString(), true);
} }
}); });
XHR.addEventListener('error', function(event){ XHR.addEventListener('error', function(event){
if(event.target.status == 0){ if(event.target.status == 0){
displayMessage('XHR failed: Check that XHR requests are allowed', true); displayMessage('XHR failed: Check that XHR requests are allowed', true);
} else { } else {
displayMessage('XHR failed: Unknown error', true); displayMessage('XHR failed: Unknown error', true);
} }
}); });
XHR.open('POST', playlistAddForm.getAttribute('action')); XHR.open('POST', playlistAddForm.getAttribute('action'));
XHR.send(FD); XHR.send(FD);
} }
playlistAddForm.addEventListener('submit', sendData); playlistAddForm.addEventListener('submit', sendData);
}());
</script> </script>
{% endif %} {% endif %}
</header> </header>
<main> <main class="main">
{% block main %}
{{ main }} {% block main %}
{% endblock %} {{ main }}
{% endblock %}
</main> </main>
<footer class="footer">
<p>This site is Free/Libre Software</p>
<p>Current version: 3304bab @ master</p>
</footer>
</body> </body>
</html> </html>

View File

@ -7,114 +7,30 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "common_elements.html" as common_elements %} {% import "common_elements.html" as common_elements %}
{% block style %} {% block style %}
main{ <link href="/youtube.com/static/channel.css" rel="stylesheet">
display:grid;
{% if current_tab == 'about' %}
grid-template-rows: 0fr 0fr 1fr;
grid-template-columns: 0fr 1fr;
{% else %}
grid-template-rows: repeat(5, 0fr);
grid-template-columns: auto 1fr;
{% endif %}
}
main .avatar{
grid-row:1;
grid-column:1;
height:200px;
width:200px;
}
main .summary{
grid-row:1;
grid-column:2;
margin-left: 5px;
}
.summary subscribe-unsubscribe, .summary short-description{
margin-top: 10px;
}
main .channel-tabs{
grid-row:2;
grid-column: 1 / span 2;
display:grid;
grid-auto-flow: column;
justify-content:start;
background-color: var(--interface-color);
padding: 3px;
padding-left: 6px;
}
#links-metadata{
display: grid;
grid-auto-flow: column;
grid-column-gap: 10px;
grid-column: 1/span 2;
justify-content: start;
padding-top: 8px;
padding-bottom: 8px;
padding-left: 6px;
margin-bottom: 10px;
}
#number-of-results{
font-weight:bold;
}
.content{
grid-row: 4;
grid-column: 1 / span 2;
}
.search-content{
width: 800px;
margin-left: 10px;
}
.item-grid{
padding-left: 20px;
}
.item-list{
width:800px;
margin: auto;
}
.page-button-row{
margin-left: auto;
margin-right: auto;
}
.next-previous-button-row{
margin-left: auto;
margin-right: auto;
}
.tab{
padding: 5px 75px;
}
.channel-info{
grid-row: 3;
grid-column: 1 / span 3;
}
.channel-info ul{
padding-left: 40px;
}
.channel-info h3{
margin-left: 40px;
}
.channel-info .description{
white-space: pre-wrap;
min-width: 0;
margin-left: 40px;
}
.medium-item img{
max-width: 168px;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<img class="avatar" src="{{ avatar }}">
<div class="summary"> <div class="author-container">
<h2 class="title">{{ channel_name }}</h2> <div class="author">
<p class="short-description">{{ short_description }}</p> <img alt="{{ channel_name }}" src="{{ avatar }}"/>
<form method="POST" action="/youtube.com/subscriptions" class="subscribe-unsubscribe"> <h2>{{ channel_name }}</h2>
<input type="submit" value="{{ 'Unsubscribe' if subscribed else 'Subscribe' }}"> </div>
<input type="hidden" name="channel_id" value="{{ channel_id }}"> <div class="summary">
<input type="hidden" name="channel_name" value="{{ channel_name }}"> <p>{{ short_description }}</p>
<input type="hidden" name="action" value="{{ 'unsubscribe' if subscribed else 'subscribe' }}"> </div>
</form> <div class="subscribe">
<form method="POST" action="/youtube.com/subscriptions" class="subscribe-unsubscribe">
<input class="btn-subscribe" type="submit" value="{{ 'Unsubscribe' if subscribed else 'Subscribe' }}">
<input type="hidden" name="channel_id" value="{{ channel_id }}">
<input type="hidden" name="channel_name" value="{{ channel_name }}">
<input type="hidden" name="action" value="{{ 'unsubscribe' if subscribed else 'subscribe' }}">
</form>
</div>
</div> </div>
<hr/>
<nav class="channel-tabs"> <nav class="channel-tabs">
{% for tab_name in ('Videos', 'Playlists', 'About') %} {% for tab_name in ('Videos', 'Playlists', 'About') %}
{% if tab_name.lower() == current_tab %} {% if tab_name.lower() == current_tab %}
@ -153,8 +69,9 @@
</ul> </ul>
</div> </div>
{% else %} {% else %}
<div class="content {{ current_tab + '-content'}}">
<div id="links-metadata"> <!-- new-->
<div id="links-metadata">
{% if current_tab == 'videos' %} {% if current_tab == 'videos' %}
{% set sorts = [('1', 'views'), ('2', 'oldest'), ('3', 'newest')] %} {% set sorts = [('1', 'views'), ('2', 'oldest'), ('3', 'newest')] %}
<div id="number-of-results">{{ number_of_videos }} videos</div> <div id="number-of-results">{{ number_of_videos }} videos</div>
@ -177,16 +94,18 @@
<a class="sort-button" href="{{ channel_url + '/' + current_tab + '?sort=' + sort_number }}">{{ 'Sort by ' + sort_name }}</a> <a class="sort-button" href="{{ channel_url + '/' + current_tab + '?sort=' + sort_number }}">{{ 'Sort by ' + sort_name }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
<nav class="{{ 'item-list' if current_tab == 'search' else 'item-grid' }}"> <div class="video-container {{ current_tab + '-content'}}">
{% for item_info in items %} {% for item_info in items %}
{{ common_elements.item(item_info, include_author=false) }} {{ common_elements.item(item_info, include_author=false) }}
{% endfor %} {% endfor %}
</nav> </div>
<hr/>
<footer class="pagination-container">
{% if current_tab == 'videos' %} {% if current_tab == 'videos' %}
<nav class="page-button-row"> <nav class="pagination-list">
{{ common_elements.page_buttons(number_of_pages, channel_url + '/' + current_tab, parameters_dictionary) }} {{ common_elements.page_buttons(number_of_pages, channel_url + '/' + current_tab, parameters_dictionary) }}
</nav> </nav>
{% elif current_tab == 'search' %} {% elif current_tab == 'search' %}
@ -194,6 +113,8 @@
{{ common_elements.next_previous_buttons(is_last_page, channel_url + '/' + current_tab, parameters_dictionary) }} {{ common_elements.next_previous_buttons(is_last_page, channel_url + '/' + current_tab, parameters_dictionary) }}
</nav> </nav>
{% endif %} {% endif %}
</div> </footer>
<!-- /new-->
{% endif %} {% endif %}
{% endblock main %} {% endblock main %}

View File

@ -5,23 +5,24 @@
<div class="comment"> <div class="comment">
<a class="author-avatar" href="{{ comment['author_url'] }}" title="{{ comment['author'] }}"> <a class="author-avatar" href="{{ comment['author_url'] }}" title="{{ comment['author'] }}">
{% if include_avatar %} {% if include_avatar %}
<img class="author-avatar-img" src="{{ comment['author_avatar'] }}"> <img class="author-avatar-img" alt="{{ comment['author'] }}" src="{{ comment['author_avatar'] }}">
{% endif %} {% endif %}
</a> </a>
<address> <address class="author-name">
<a class="author" href="{{ comment['author_url'] }}" title="{{ comment['author'] }}">{{ comment['author'] }}</a> <a class="author" href="{{ comment['author_url'] }}" title="{{ comment['author'] }}">{{ comment['author'] }}</a>
</address> </address>
<a class="permalink" href="{{ comment['permalink'] }}" title="permalink"> <a class="permalink" href="{{ comment['permalink'] }}" title="permalink">
<time datetime="">{{ comment['time_published'] }}</time> <time datetime="2012-01-14T15:23:44+02:00">{{ comment['time_published'] }}</time>
</a> </a>
{% if timestamp_links %} {% if timestamp_links %}
<span class="text">{{ common_elements.text_runs(comment['text'])|timestamps|safe }}</span> <span class="comment-text">{{ common_elements.text_runs(comment['text'])|timestamps|safe }}</span>
{% else %} {% else %}
<span class="text">{{ common_elements.text_runs(comment['text']) }}</span> <span class="comment-text">{{ common_elements.text_runs(comment['text']) }}</span>
{% endif %} {% endif %}
<span class="likes">{{ comment['likes_text'] if comment['like_count'] else ''}}</span> <span class="comment-likes">{{ comment['likes_text'] if comment['like_count'] else ''}}</span>
<div class="bottom-row"> <div class="button-row">
{% if settings.use_comments_js and comment['reply_count'] %} {% if settings.use_comments_js and comment['reply_count'] %}
<details class="replies" src="{{ comment['replies_url'] }}"> <details class="replies" src="{{ comment['replies_url'] }}">
<summary>{{ comment['view_replies_text'] }}</summary> <summary>{{ comment['view_replies_text'] }}</summary>
@ -33,7 +34,6 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}
@ -57,8 +57,5 @@
<a class="page-button more-comments" href="{{ comments_info['more_comments_url'] }}">More comments</a> <a class="page-button more-comments" href="{{ comments_info['more_comments_url'] }}">More comments</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}

View File

@ -3,16 +3,11 @@
{% if not slim %} {% if not slim %}
{% extends "base.html" %} {% extends "base.html" %}
{% block style %} {% block style %}
.comments-area{ <link href="/youtube.com/static/comments.css" rel="stylesheet">
margin: auto;
width:640px;
}
{% endblock style %} {% endblock style %}
{% endif %} {% endif %}
{% block main %} {% block main %}
<section class="comments-area"> <section class="comments-area">
{% if not comments_info['is_replies'] %} {% if not comments_info['is_replies'] %}
@ -27,7 +22,6 @@
</section> </section>
{% endif %} {% endif %}
{% if not comments_info['is_replies'] %} {% if not comments_info['is_replies'] %}
<div class="comment-links"> <div class="comment-links">
{% for link_text, link_url in comments_info['comment_links'] %} {% for link_text, link_url in comments_info['comment_links'] %}
@ -51,5 +45,3 @@
<script src="/youtube.com/static/js/comments.js"></script> <script src="/youtube.com/static/js/comments.js"></script>
{% endif %} {% endif %}
{% endblock main %} {% endblock main %}

View File

@ -15,60 +15,55 @@
{% endmacro %} {% endmacro %}
{% macro item(info, description=false, horizontal=true, include_author=true, include_badges=true, lazy_load=false) %} {% macro item(info, description=false, horizontal=true, include_author=true, include_badges=true, lazy_load=false) %}
<div class="item-box {{ info['type'] + '-item-box' }} {{'horizontal-item-box' if horizontal else 'vertical-item-box'}} {{'has-description' if description else 'no-description'}}"> <article class="item-box">
{% if info['error'] %} {% if info['error'] %}
{{ info['error'] }} {{ info['error'] }}
{% else %} {% else %}
<div class="item {{ info['type'] + '-item' }}"> <div class="item-video {{ info['type'] + '-item' }}">
<a class="thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}"> <a class="thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}">
{% if lazy_load %} <div class="thumbnail {% if info['type'] == 'channel' %} channel {% endif %}">
<img class="thumbnail-img lazy" data-src="{{ info['thumbnail'] }}"> {% if lazy_load %}
{% else %} <img class="thumbnail-img lazy" alt="thumbnail" data-src="{{ info['thumbnail'] }}">
<img class="thumbnail-img" src="{{ info['thumbnail'] }}"> {% elif info['type'] == 'channel' %}
{% endif %} <img class="thumbnail-img channel" alt="thumbnail" src="{{ info['thumbnail'] }}">
{% if info['type'] != 'channel' %} {% else %}
<div class="thumbnail-info"> <img class="thumbnail-img" alt="thumbnail" src="{{ info['thumbnail'] }}">
<span>{{ (info['video_count']|commatize + ' videos') if info['type'] == 'playlist' else info['duration'] }}</span> {% endif %}
</div>
{% endif %}
</a>
<div class="title"><a class="title" href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a></div> {% if info['type'] != 'channel' %}
<p class="length">{{ (info['video_count']|commatize + ' videos') if info['type'] == 'playlist' else info['duration'] }}</p>
{% endif %}
</div>
</a>
<h4 class="title"><a href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a></h4>
{% if include_author %} {% if include_author %}
{% if info.get('author_url') %} {% if info.get('author_url') %}
<address title="{{ info['author'] }}">By <a href="{{ info['author_url'] }}">{{ info['author'] }}</a></address> <address title="{{ info['author'] }}"><b><a href="{{ info['author_url'] }}">{{ info['author'] }}</a></b></address>
{% else %} {% else %}
<address title="{{ info['author'] }}"><b>{{ info['author'] }}</b></address> <address title="{{ info['author'] }}"><b>{{ info['author'] }}</b></address>
{% endif %} {% endif %}
{% endif %} {% endif %}
<ul class="stats {{'horizontal-stats' if horizontal else 'vertical-stats'}}">
<div class="stats {{'horizontal-stats' if horizontal else 'vertical-stats'}}">
{% if info['type'] == 'channel' %} {% if info['type'] == 'channel' %}
<li><span>{{ info['approx_subscriber_count'] }} subscribers</span></li> <div>{{ info['approx_subscriber_count'] }} subscribers</div>
<li><span>{{ info['video_count']|commatize }} videos</span></li> <div>{{ info['video_count']|commatize }} videos</div>
{% else %} {% else %}
{% if info.get('approx_view_count') %}
<li><span class="views">{{ info['approx_view_count'] }} views</span></li>
{% endif %}
{% if info.get('time_published') %} {% if info.get('time_published') %}
<li><time>{{ info['time_published'] }}</time></li> <time>{{ info['time_published'] }}</time>
{% endif %}
{% if info.get('approx_view_count') %}
<div class="views">{{ info['approx_view_count'] }} views</div>
{% endif %} {% endif %}
{% endif %} {% endif %}
</ul> </div>
{% if description %}
<span class="description">{{ text_runs(info.get('description', '')) }}</span>
{% endif %}
{% if include_badges %}
<span class="badges">{{ info['badges']|join(' | ') }}</span>
{% endif %}
</div> </div>
{% if info['type'] == 'video' %} {% if info['type'] == 'video' %}
<input class="item-checkbox" type="checkbox" name="video_info_list" value="{{ info['video_info'] }}" form="playlist-edit"> <input class="item-checkbox" type="checkbox" name="video_info_list" value="{{ info['video_info'] }}" form="playlist-edit">
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </article>
{% endmacro %} {% endmacro %}
{% macro page_buttons(estimated_pages, url, parameters_dictionary) %} {% macro page_buttons(estimated_pages, url, parameters_dictionary) %}
@ -84,12 +79,12 @@
{% for page in range(page_start, page_end+1) %} {% for page in range(page_start, page_end+1) %}
{% if page == current_page %} {% if page == current_page %}
<div class="page-button">{{ page }}</div> <div class="page-link is-current">{{ page }}</div>
{% else %} {% else %}
{# IMPORTANT: Jinja SUCKS #} {# IMPORTANT: Jinja SUCKS #}
{# https://stackoverflow.com/questions/36886650/how-to-add-a-new-entry-into-a-dictionary-object-while-using-jinja2 #} {# https://stackoverflow.com/questions/36886650/how-to-add-a-new-entry-into-a-dictionary-object-while-using-jinja2 #}
{% set _ = parameters_dictionary.__setitem__('page', page) %} {% set _ = parameters_dictionary.__setitem__('page', page) %}
<a class="page-button" href="{{ url + '?' + parameters_dictionary|urlencode }}">{{ page }}</a> <a class="page-link" href="{{ url + '?' + parameters_dictionary|urlencode }}">{{ page }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -101,11 +96,11 @@
{% if current_page != 1 %} {% if current_page != 1 %}
{% set _ = parameters_dictionary.__setitem__('page', current_page - 1) %} {% set _ = parameters_dictionary.__setitem__('page', current_page - 1) %}
<a class="page-button previous-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Previous page</a> <a class="page-link previous-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Previous page</a>
{% endif %} {% endif %}
{% if not is_last_page %} {% if not is_last_page %}
{% set _ = parameters_dictionary.__setitem__('page', current_page + 1) %} {% set _ = parameters_dictionary.__setitem__('page', current_page + 1) %}
<a class="page-button next-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Next page</a> <a class="page-link next-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Next page</a>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}

View File

@ -4,9 +4,13 @@
{% extends "base.html" %} {% extends "base.html" %}
{% endif %} {% endif %}
{% block style %}
<link href="/youtube.com/static/home.css" rel="stylesheet">
{% endblock style %}
{% block main %} {% block main %}
{% if traceback %} {% if traceback %}
<div id="error-box"> <div class="code-error" id="error-box">
<h1>500 Uncaught exception:</h1> <h1>500 Uncaught exception:</h1>
<div class="code-box"><code>{{ traceback }}</code></div> <div class="code-box"><code>{{ traceback }}</code></div>
<p>Please report this issue at <a href="https://github.com/user234683/youtube-local/issues" target="_blank">https://github.com/user234683/youtube-local/issues</a></p> <p>Please report this issue at <a href="https://github.com/user234683/youtube-local/issues" target="_blank">https://github.com/user234683/youtube-local/issues</a></p>
@ -16,4 +20,3 @@
<div id="error-message">{{ error_message }}</div> <div id="error-message">{{ error_message }}</div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -1,16 +1,7 @@
{% set page_title = title %} {% set page_title = title %}
{% extends "base.html" %} {% extends "base.html" %}
{% block style %} {% block style %}
ul { <link href="/youtube.com/static/home.css" rel="stylesheet">
background-color: var(--interface-color);
padding: 20px;
width: 400px;
margin: auto;
margin-top: 20px;
}
li {
margin-bottom: 10px;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<ul> <ul>

View File

@ -2,41 +2,23 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "common_elements.html" as common_elements %} {% import "common_elements.html" as common_elements %}
{% block style %} {% block style %}
main > *{ <link href="/youtube.com/static/local_playlist.css" rel="stylesheet">
width: 800px;
margin: auto;
}
.playlist-metadata{
display: flex;
flex-direction: row;
justify-content: space-between;
}
.playlist-title{
}
#playlist-remove-button{
align-self: center;
white-space: nowrap;
}
#results{
display: grid;
grid-auto-rows: 0fr;
grid-row-gap: 10px;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<div class="playlist-metadata"> <div class="playlist-metadata">
<h2 class="playlist-title">{{ playlist_name }}</h2> <h2 class="play-title">{{ playlist_name }}</h2>
<input type="hidden" name="playlist_page" value="{{ playlist_name }}" form="playlist-edit"> <input type="hidden" name="playlist_page" value="{{ playlist_name }}" form="playlist-edit">
<button type="submit" id="playlist-remove-button" name="action" value="remove" form="playlist-edit" formaction="">Remove from playlist</button> <button class="play-action" type="submit" id="playlist-remove-button" name="action" value="remove" form="playlist-edit" formaction="">Remove from playlist</button>
</div> </div>
<div id="results"> <div id="results" class="video-container">
{% for video_info in videos %} {% for video_info in videos %}
{{ common_elements.item(video_info) }} {{ common_elements.item(video_info) }}
{% endfor %} {% endfor %}
</div> </div>
<nav class="page-button-row"> <footer class="pagination-container">
{{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlists/' + playlist_name, parameters_dictionary) }} <nav class="pagination-list">
</nav> {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlists/' + playlist_name, parameters_dictionary) }}
</nav>
</footer>
{% endblock main %} {% endblock main %}

View File

@ -2,20 +2,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block style %} {% block style %}
main{ <link href="/youtube.com/static/home.css" rel="stylesheet">
display: flex;
justify-content: center;
}
ul{
background-color: var(--interface-color);
margin-top: 20px;
padding: 20px;
min-width: 400px;
align-self: start;
}
li{
margin-bottom: 10px;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
@ -25,9 +12,3 @@ main{
{% endfor %} {% endfor %}
</ul> </ul>
{% endblock main %} {% endblock main %}

View File

@ -2,77 +2,39 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "common_elements.html" as common_elements %} {% import "common_elements.html" as common_elements %}
{% block style %} {% block style %}
main > * { <link href="/youtube.com/static/playlist.css" rel="stylesheet">
width: 800px;
margin:auto;
}
.playlist-metadata{
display:grid;
grid-template-columns: 0fr 1fr;
}
.playlist-thumbnail{
grid-row: 1 / span 5;
grid-column:1;
justify-self:start;
width:250px;
margin-right: 10px;
}
.playlist-title{
grid-row: 1;
grid-column:2;
}
.playlist-author{
grid-row:2;
grid-column:2;
}
.playlist-stats{
grid-row:3;
grid-column:2;
}
.playlist-description{
grid-row:4;
grid-column:2;
min-width:0px;
white-space: pre-line;
}
#results{
margin-top:10px;
display: grid;
grid-auto-rows: 0fr;
grid-row-gap: 10px;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<div class="playlist-metadata"> <div class="playlist-metadata">
<img class="playlist-thumbnail" src="{{ thumbnail }}"> <div class="author">
<h2 class="playlist-title">{{ title }}</h2> <img alt="{{ title }}" src="{{ thumbnail }}"/>
<a class="playlist-author" href="{{ author_url }}">{{ author }}</a> <h2>{{ title }}</h2>
</div>
<div class="summary">
<a class="playlist-author" href="{{ author_url }}">{{ author }}</a>
</div>
<div class="playlist-stats"> <div class="playlist-stats">
<div>{{ video_count|commatize }} videos</div> <div>{{ video_count|commatize }} videos</div>
<div>{{ view_count|commatize }} views</div> <div>{{ view_count|commatize }} views</div>
<div>Last updated {{ time_published }}</div> <div>Last updated {{ time_published }}</div>
</div> </div>
<div class="playlist-description">{{ common_elements.text_runs(description) }}</div>
</div> </div>
<hr/>
<div id="results">
<div id="results" class="video-container">
{% for info in video_list %} {% for info in video_list %}
{{ common_elements.item(info) }} {{ common_elements.item(info) }}
{% endfor %} {% endfor %}
</div> </div>
<nav class="page-button-row"> <hr/>
{{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlist', parameters_dictionary) }}
</nav> <footer class="pagination-container">
<nav class="pagination-list">
{{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlist', parameters_dictionary) }}
</nav>
</footer>
{% endblock main %} {% endblock main %}

View File

@ -3,44 +3,31 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "common_elements.html" as common_elements %} {% import "common_elements.html" as common_elements %}
{% block style %} {% block style %}
main > * { <link href="/youtube.com/static/search.css" rel="stylesheet">
max-width: 800px;
margin: auto;
}
#result-info{
margin-top: 10px;
margin-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
}
#number-of-results{
font-weight:bold;
}
.item-list{
padding-left: 10px;
padding-right: 10px;
}
.badge{
background-color:#cccccc;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<div id="result-info"> <div class="result-info" id="result-info">
<div id="number-of-results">Approximately {{ '{:,}'.format(estimated_results) }} results ({{ '{:,}'.format(estimated_pages) }} pages)</div> <div id="number-of-results">Approximately {{ '{:,}'.format(estimated_results) }} results ({{ '{:,}'.format(estimated_pages) }} pages)</div>
{% if corrections['type'] == 'showing_results_for' %} {% if corrections['type'] == 'showing_results_for' %}
<div>Showing results for <a>{{ common_elements.text_runs(corrections['corrected_query_text']) }}</a></div> <div>Showing results for <a>{{ common_elements.text_runs(corrections['corrected_query_text']) }}</a></div>
<div>Search instead for <a href="{{ corrections['original_query_url'] }}">{{ corrections['original_query_text'] }}</a></div> <div>Search instead for <a href="{{ corrections['original_query_url'] }}">{{ corrections['original_query_text'] }}</a></div>
{% elif corrections['type'] == 'did_you_mean' %} {% elif corrections['type'] == 'did_you_mean' %}
<div>Did you mean <a href="{{ corrections['corrected_query_url'] }}">{{ common_elements.text_runs(corrections['corrected_query_text']) }}</a></div> <div>Did you mean <a href="{{ corrections['corrected_query_url'] }}">{{ common_elements.text_runs(corrections['corrected_query_text']) }}</a></div>
{% endif %} {% endif %}
</div> </div>
<div class="item-list">
{% for info in results %} <!-- video item -->
{{ common_elements.item(info, description=true) }} <div class="video-container">
{% endfor %} {% for info in results %}
</div> {{ common_elements.item(info, description=true) }}
<nav class="page-button-row"> {% endfor %}
{{ common_elements.page_buttons(estimated_pages, '/https://www.youtube.com/search', parameters_dictionary) }} </div>
</nav> <hr/>
<!-- /video item -->
<footer class="pagination-container">
<nav class="pagination-list">
{{ common_elements.page_buttons(estimated_pages, '/https://www.youtube.com/search', parameters_dictionary) }}
</nav>
</footer>
{% endblock main %} {% endblock main %}

View File

@ -1,28 +1,7 @@
{% set page_title = 'Settings' %} {% set page_title = 'Settings' %}
{% extends "base.html" %} {% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
{% block style %} {% block style %}
.settings-form { <link href="/youtube.com/static/settings.css" rel="stylesheet">
margin: auto;
width: 600px;
margin-top:10px;
padding: 10px;
display: block;
background-color: var(--interface-color);
}
.settings-list{
list-style: none;
padding: 0px;
}
.setting-item{
margin-bottom: 10px;
padding: 5px;
}
.setting-item label{
display: inline-block;
width: 250px;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
@ -30,37 +9,37 @@
{% for categ in categories %} {% for categ in categories %}
<h2>{{ categ|capitalize }}</h2> <h2>{{ categ|capitalize }}</h2>
<ul class="settings-list"> <ul class="settings-list">
{% for setting_name, setting_info, value in settings_by_category[categ] %} {% for setting_name, setting_info, value in settings_by_category[categ] %}
{% if not setting_info.get('hidden', false) %} {% if not setting_info.get('hidden', false) %}
<li class="setting-item"> <li class="setting-item">
{% if 'label' is in(setting_info) %} {% if 'label' is in(setting_info) %}
<label for="{{ 'setting_' + setting_name }}">{{ setting_info['label'] }}</label> <label for="{{ 'setting_' + setting_name }}">{{ setting_info['label'] }}</label>
{% else %}
<label for="{{ 'setting_' + setting_name }}">{{ setting_name.replace('_', ' ')|capitalize }}</label>
{% endif %}
{% if setting_info['type'].__name__ == 'bool' %}
<input type="checkbox" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" {{ 'checked' if value else '' }}>
{% elif setting_info['type'].__name__ == 'int' %}
{% if 'options' is in(setting_info) %}
<select id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}">
{% for option in setting_info['options'] %}
<option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ option[1] }}</option>
{% endfor %}
</select>
{% else %} {% else %}
<input type="number" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}" step="1"> <label for="{{ 'setting_' + setting_name }}">{{ setting_name.replace('_', ' ')|capitalize }}</label>
{% endif %} {% endif %}
{% elif setting_info['type'].__name__ == 'float' %}
{% elif setting_info['type'].__name__ == 'str' %} {% if setting_info['type'].__name__ == 'bool' %}
<input type="text" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}"> <input type="checkbox" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" {{ 'checked' if value else '' }}>
{% else %} {% elif setting_info['type'].__name__ == 'int' %}
<span>Error: Unknown setting type: setting_info['type'].__name__</span> {% if 'options' is in(setting_info) %}
{% endif %} <select id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}">
</li> {% for option in setting_info['options'] %}
{% endif %} <option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ option[1] }}</option>
{% endfor %} {% endfor %}
</select>
{% else %}
<input type="number" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}" step="1">
{% endif %}
{% elif setting_info['type'].__name__ == 'float' %}
{% elif setting_info['type'].__name__ == 'str' %}
<input type="text" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}">
{% else %}
<span>Error: Unknown setting type: setting_info['type'].__name__</span>
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul> </ul>
{% endfor %} {% endfor %}
<input type="submit" value="Save settings"> <input type="submit" value="Save settings">

View File

@ -1,81 +1,7 @@
{% set page_title = 'Subscription Manager' %} {% set page_title = 'Subscription Manager' %}
{% extends "base.html" %} {% extends "base.html" %}
{% block style %} {% block style %}
.import-export{ <link href="/youtube.com/static/subscription_manager.css" rel="stylesheet">
display: flex;
flex-direction: row;
padding-left: 10px;
padding-top: 10px;
}
.subscriptions-import-form{
background-color: var(--interface-color);
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 300px;
padding:10px;
}
.subscriptions-import-form h2{
font-size: 1.25rem;
margin-bottom: 10px;
}
.import-submit-button{
margin-top:15px;
align-self: flex-end;
}
.subscriptions-export-links{
margin: 0px 0px 0px 20px;
background-color: var(--interface-color);
list-style: none;
max-width: 300px;
padding:10px;
}
.sub-list-controls{
background-color: var(--interface-color);
padding:10px;
}
.tag-group-list{
list-style: none;
margin-left: 10px;
margin-right: 10px;
padding: 0px;
}
.tag-group{
border-style: solid;
margin-bottom: 10px;
}
.sub-list{
list-style: none;
padding:10px;
column-width: 300px;
column-gap: 40px;
}
.sub-list-item{
display:flex;
margin-bottom: 10px;
break-inside:avoid;
}
.sub-list-item:not(.muted){
background-color: var(--interface-color);
}
.tag-list{
margin-left:15px;
font-weight:bold;
}
.sub-list-item-name{
margin-left:15px;
}
.sub-list-checkbox{
height: 1.5em;
min-width: 1.5em; // need min-width otherwise browser doesn't respect the width and squishes the checkbox down when there's too many tags
}
{% endblock style %} {% endblock style %}
@ -89,8 +15,6 @@
{% endfor %} {% endfor %}
{% endmacro %} {% endmacro %}
{% block main %} {% block main %}
<div class="import-export"> <div class="import-export">
<form class="subscriptions-import-form" enctype="multipart/form-data" action="/youtube.com/import_subscriptions" method="POST"> <form class="subscriptions-import-form" enctype="multipart/form-data" action="/youtube.com/import_subscriptions" method="POST">

View File

@ -7,79 +7,21 @@
{% import "common_elements.html" as common_elements %} {% import "common_elements.html" as common_elements %}
{% block style %} {% block style %}
main{ <link href="/youtube.com/static/subscription.css" rel="stylesheet">
display:flex;
flex-direction: row;
}
.video-section{
flex-grow: 1;
padding-left: 10px;
padding-top: 10px;
}
.current-tag{
margin-bottom:10px;
}
.video-section .page-button-row{
justify-content: center;
}
.subscriptions-sidebar{
flex-basis: 300px;
background-color: var(--interface-color);
border-left: 2px;
}
.sidebar-links{
display:flex;
justify-content: space-between;
padding-left:10px;
padding-right: 10px;
}
.sidebar-list{
list-style: none;
padding-left:10px;
padding-right: 10px;
}
.sidebar-list-item{
display:flex;
justify-content: space-between;
margin-bottom: 5px;
}
.sub-refresh-list .sidebar-item-name{
text-overflow: clip;
white-space: nowrap;
overflow: hidden;
max-width: 200px;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<div class="video-section">
{% if current_tag %}
<h2 class="current-tag">{{ current_tag }}</h2>
{% endif %}
<nav class="item-grid">
{% for video_info in videos %}
{{ common_elements.item(video_info) }}
{% endfor %}
</nav>
<nav class="page-button-row">
{{ common_elements.page_buttons(num_pages, '/youtube.com/subscriptions', parameters_dictionary) }}
</nav>
</div>
<div class="subscriptions-sidebar"> <div class="subscriptions-sidebar">
<div class="sidebar-links"> <div class="sidebar-links">
<a href="/youtube.com/subscription_manager" class="sub-manager-link">Subscription Manager</a> <a class="sidebar-title" href="/youtube.com/subscription_manager" class="sub-manager-link">Subscription Manager</a>
<form method="POST" class="refresh-all"> <form class="sidebar-action" method="POST" class="refresh-all">
<input type="submit" value="Check All"> <input type="submit" value="Check All">
<input type="hidden" name="action" value="refresh"> <input type="hidden" name="action" value="refresh">
<input type="hidden" name="type" value="all"> <input type="hidden" name="type" value="all">
</form> </form>
</div> </div>
<hr>
<ol class="sidebar-list tags"> <ol class="sidebar-list tags">
{% if current_tag %} {% if current_tag %}
<li class="sidebar-list-item"> <li class="sidebar-list-item">
@ -105,7 +47,6 @@
</ol> </ol>
<hr> <hr>
<ol class="sidebar-list sub-refresh-list"> <ol class="sidebar-list sub-refresh-list">
{% for subscription in subscription_list %} {% for subscription in subscription_list %}
<li class="sidebar-list-item {{ 'muted' if subscription['muted'] else '' }}"> <li class="sidebar-list-item {{ 'muted' if subscription['muted'] else '' }}">
@ -119,7 +60,23 @@
</li> </li>
{% endfor %} {% endfor %}
</ol> </ol>
</div> </div>
{% if current_tag %}
<h2 class="current-tag">{{ current_tag }}</h2>
{% endif %}
<div class="video-container">
{% for video_info in videos %}
{{ common_elements.item(video_info) }}
{% endfor %}
</div>
<hr/>
<footer class="pagination-container">
<nav class="pagination-list">
{{ common_elements.page_buttons(num_pages, '/youtube.com/subscriptions', parameters_dictionary) }}
</nav>
</footer>
{% endblock main %} {% endblock main %}

View File

@ -3,378 +3,17 @@
{% import "common_elements.html" as common_elements %} {% import "common_elements.html" as common_elements %}
{% import "comments.html" as comments with context %} {% import "comments.html" as comments with context %}
{% block style %} {% block style %}
details > summary{ <link href="/youtube.com/static/watch.css" rel="stylesheet">
background-color: var(--interface-color);
border-style: outset;
border-width: 2px;
font-weight: bold;
padding-bottom: 2px;
}
details > summary:hover{
text-decoration: underline;
}
.playability-error{
height: 360px;
width: 640px;
grid-column: 2;
background-color: var(--video-background-color);
text-align:center;
}
.playability-error span{
position: relative;
top: 50%;
transform: translate(-50%, -50%);
}
.live-url-choices{
height: 360px;
width: 640px;
grid-column: 2;
background-color: var(--video-background-color);
padding: 25px 0px 0px 25px;
}
.live-url-choices ol{
list-style: none;
padding:0px;
margin:0px;
margin-top: 15px;
}
.live-url-choices input{
width: 400px;
}
.url-choice-label{
display: inline-block;
width: 150px;
}
{% if settings.theater_mode %}
/* This is the constant aspect ratio trick
Percentages in padding-top declarations are based on the width of the
parent element. We can use this trick to achieve a constant aspect ratio
for video-container-inner by setting height to 0.
So the video height will decrease if the browser window is narrow,
but it will keep same aspect ratio. Must use absolute positioning on
video to keep it inside its container since the container's height is 0.
However, because we widen the video player longer than the video's
intrinsic width for long video to make scrubbing easier, we can't use
the aspect ratio to set the height. The height needs to be the
intrinsic height in these cases. So we use a media query so aspect
ratio trick is only used if page width is less than or equal to
intrinsic video width.
*/
#video-container{
grid-column: 1 / span 5;
justify-self: center;
max-width: 100%;
width: {{ theater_video_target_width }}px;
margin-bottom: 10px;
}
#video-container-inner{
height: {{ video_height}}px;
position: relative;
}
@media(max-width:{{ video_width }}px){
#video-container-inner{
padding-top: calc(100%*{{video_height}}/{{video_width}});
height: 0px;
}
}
video{
background-color: var(--video-background-color);
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
}
.side-videos{
grid-row: 2 /span 3;
width: 400px;
}
.video-info{
width: 640px;
}
{% else %}
#video-container{
grid-column: 2;
}
#video-container, #video-container-inner, video{
height: 360px;
width: 640px;
}
.side-videos{
grid-row: 1 /span 4;
}
{% endif %}
main{
display:grid;
grid-template-columns: 1fr 640px 40px 400px 1fr;
grid-template-rows: auto auto auto auto;
align-content: start;
}
.video-info{
grid-column: 2;
grid-row: 2;
display: grid;
grid-template-rows: 0fr 0fr 0fr 20px 0fr 0fr;
grid-template-columns: 1fr 1fr;
align-content: start;
}
.video-info > .title{
grid-column: 1 / span 2;
min-width: 0;
}
.video-info > .labels{
justify-self:start;
list-style: none;
padding: 0px;
margin: 5px 0px;
}
.video-info > .labels:empty{
margin: 0px;
}
.labels > li{
display: inline;
margin-right:5px;
background-color: var(--interface-color);
padding: 2px 5px;
border-style: solid;
border-width: 1px;
}
.video-info > address{
grid-column: 1;
grid-row: 3;
justify-self: start;
}
.video-info > .views{
grid-column: 2;
grid-row: 3;
justify-self:end;
}
.video-info > time{
grid-column: 1;
grid-row: 4;
justify-self:start;
}
.video-info > .likes-dislikes{
grid-column: 2;
grid-row: 4;
justify-self:end;
}
.video-info > .external-player-controls{
justify-self: start;
grid-row: 5;
grid-column: 1;
margin-bottom: 8px;
}
#speed-control{
width: 65px;
text-align: center;
background-color: var(--interface-color);
color: var(--text-color);
}
.video-info > .checkbox{
justify-self:end;
align-self: start;
grid-row: 5;
grid-column: 2;
}
.video-info > .download-dropdown{
grid-column:1 / span 2;
grid-row: 6;
}
.video-info > .description{
background-color:var(--interface-color);
margin-top:8px;
white-space: pre-wrap;
min-width: 0;
word-wrap: break-word;
grid-column: 1 / span 2;
grid-row: 7;
padding: 5px;
}
.music-list{
grid-row:8;
grid-column: 1 / span 2;
background-color: var(--interface-color);
padding-bottom: 7px;
}
.music-list table,th,td{
border: 1px solid;
}
.music-list th,td{
padding-left:4px;
padding-right:5px;
}
.music-list caption{
text-align:left;
font-weight:bold;
margin-bottom:5px;
}
.more-info{
grid-row: 9;
grid-column: 1 / span 2;
background-color: var(--interface-color);
}
.more-info > summary{
font-weight: normal;
border-width: 1px 0px;
border-style: solid;
}
.more-info-content{
padding: 5px;
}
.more-info-content p{
margin: 8px 0px;
}
.comments-area-outer{
grid-column: 2;
grid-row: 3;
margin-top:10px;
}
.comments-disabled{
background-color: var(--interface-color);
padding: 5px;
font-weight: bold;
}
.comments-area-inner{
padding-top: 10px;
}
.comment{
width:640px;
}
.side-videos{
grid-column: 4;
max-width: 640px;
}
#transcript-details{
margin-bottom: 10px;
}
table#transcript-table {
border-collapse: collapse;
width: 100%;
}
table#transcript-table td, th {
border: 1px solid #dddddd;
}
div#transcript-div {
background-color: var(--interface-color);
padding: 5px;
}
.playlist{
border-style: solid;
border-width: 2px;
border-color: lightgray;
margin-bottom: 10px;
}
.playlist-header{
background-color: var(--interface-color);
padding: 3px;
border-bottom-style: solid;
border-bottom-width: 2px;
border-bottom-color: lightgray;
}
.playlist-header h3{
margin: 2px;
}
.playlist-metadata{
list-style: none;
padding: 0px;
margin: 0px;
}
.playlist-metadata li{
display: inline;
margin: 2px;
}
.playlist-videos{
height: 300px;
overflow-y: scroll;
display: grid;
grid-auto-rows: 90px;
grid-row-gap: 10px;
padding-top: 10px;
}
.related-videos-inner{
padding-top: 10px;
display: grid;
grid-auto-rows: 90px;
grid-row-gap: 10px;
}
.thumbnail-box{ /* overides rule in shared.css */
height: 90px !important;
width: 120px !important;
}
/* Put related vids below videos when window is too small */
/* 1100px instead of 1080 because W3C is full of idiots who include scrollbar width */
@media (max-width:1100px){
main{
grid-template-columns: 1fr 640px 40px 1fr;
}
.side-videos{
margin-top: 10px;
grid-column: 2;
grid-row: 3;
width: initial;
}
.comments-area-outer{
grid-row: 4;
}
}
.download-dropdown-content{
background-color: var(--interface-color);
padding: 10px;
list-style: none;
margin: 0px;
}
li.download-format{
margin-bottom: 7px;
}
.format-attributes{
list-style: none;
padding: 0px;
margin: 0px;
display: flex;
flex-direction: row;
}
.format-attributes li{
white-space: nowrap;
max-height: 1.2em;
}
.format-ext{
width: 60px;
}
.format-video-quality{
width: 140px;
}
.format-audio-quality{
width: 120px;
}
.format-file-size{
width: 80px;
}
.format-codecs{
width: 120px;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
{% if playability_error %} {% if playability_error %}
<div class="playability-error"> <div class="playability-error">
<span>{{ 'Error: ' + playability_error }} <span>{{ 'Error: ' + playability_error }}
{% if invidious_reload_button %} {% if invidious_reload_button %}
<a href="{{ video_url }}&use_invidious=0"><br> <a href="{{ video_url }}&use_invidious=0"><br>
Reload without invidious (for usage of new identity button).</a> Reload without invidious (for usage of new identity button).</a>
{% endif %} {% endif %}
</span> </span>
</div> </div>
{% elif (video_sources.__len__() == 0 or live) and hls_formats.__len__() != 0 %} {% elif (video_sources.__len__() == 0 or live) and hls_formats.__len__() != 0 %}
@ -387,143 +26,137 @@ Reload without invidious (for usage of new identity button).</a>
</ol> </ol>
</div> </div>
{% else %} {% else %}
<div id="video-container"> <figure class="sc-video">
<div id="video-container-inner"> <video id="js-video-player" playsinline controls>
<video controls autofocus class="video" height="{{ video_height }}px"> {% for video_source in video_sources %}
{% for video_source in video_sources %} <source src="{{ video_source['src'] }}" type="{{ video_source['type'] }}">
<source src="{{ video_source['src'] }}" type="{{ video_source['type'] }}"> {% endfor %}
{% endfor %}
{% for source in subtitle_sources %} {% for source in subtitle_sources %}
{% if source['on'] %} {% if source['on'] %}
<track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default> <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default>
{% else %} {% else %}
<track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}"> <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}">
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</video>
</figure>
</video>
</div>
</div>
{% if time_start != 0 %} {% if time_start != 0 %}
<script> <script>
document.querySelector('video').currentTime = {{ time_start|tojson }}; document.querySelector('js-video-player').currentTime = {{ time_start|tojson }};
</script> </script>
{% endif %} {% endif %}
{% endif %} {% endif %}
<div class="video-info"> <div class="sc-info">
<h2 class="title">{{ title }}</h2> <div class="video-info">
<ul class="labels"> <h1 class="v-title">{{ title }}</h1>
{%- if unlisted -%}
<li class="is-unlisted">Unlisted</li>
{%- endif -%}
{%- if age_restricted -%}
<li class="age-restricted">Age-restricted</li>
{%- endif -%}
{%- if limited_state -%}
<li>Limited state</li>
{%- endif -%}
{%- if live -%}
<li>Live</li>
{%- endif -%}
</ul>
<address>Uploaded by <a href="{{ uploader_channel_url }}">{{ uploader }}</a></address>
<span class="views">{{ view_count }} views</span>
<ul class="labels">
<time datetime="$upload_date">Published on {{ time_published }}</time> {%- if unlisted -%}
<span class="likes-dislikes">{{ like_count }} likes {{ dislike_count }} dislikes</span> <li class="is-unlisted">Unlisted</li>
{%- endif -%}
<div class="external-player-controls"> {%- if age_restricted -%}
<input id="speed-control" type="text"> <li class="age-restricted">Age-restricted</li>
<script> {%- endif -%}
var video = document.querySelector('video'); {%- if limited_state -%}
var speedInput = document.querySelector('#speed-control'); <li>Limited state</li>
speedInput.addEventListener('keyup', (event) => { {%- endif -%}
if (event.key === 'Enter') { {%- if live -%}
var speed = parseFloat(speedInput.value); <li>Live</li>
if(!isNaN(speed)){ {%- endif -%}
video.playbackRate = speed;
}
}
});
</script>
</div>
<input class="checkbox" name="video_info_list" value="{{ video_info }}" form="playlist-edit" type="checkbox">
<details class="download-dropdown">
<summary class="download-dropdown-label">Download</summary>
<ul class="download-dropdown-content">
{% for format in download_formats %}
<li class="download-format">
<a class="download-link" href="{{ format['url'] }}">
<ol class="format-attributes">
<li class="format-ext">{{ format['ext'] }}</li>
<li class="format-video-quality">{{ format['video_quality'] }}</li>
<li class="format-audio-quality">{{ format['audio_quality'] }}</li>
<li class="format-file-size">{{ format['file_size'] }}</li>
<li class="format-codecs">{{ format['codecs'] }}</li>
</ol>
</a>
</li>
{% endfor %}
{% for download in other_downloads %}
<li class="download-format">
<a href="{{ download['url'] }}">
<ol class="format-attributes">
<li class="format-ext">{{ download['ext'] }}</li>
<li class="format-label">{{ download['label'] }}</li>
</ol>
</a>
</li>
{% endfor %}
</ul> </ul>
</details>
<address class="v-uploaded">Uploaded by <a href="{{ uploader_channel_url }}">{{ uploader }}</a></address>
<span class="v-views">{{ view_count }} views</span>
<time class="v-published" datetime="$upload_date">Published on {{ time_published }}</time>
<span class="v-likes-dislikes">{{ like_count }} likes {{ dislike_count }} dislikes</span>
<span class="description">{{ common_elements.text_runs(description)|escape|urlize|timestamps|safe }}</span> <div class="external-player-controls">
<div class="music-list"> <input class="speed" id="speed-control" type="text">
{% if music_list.__len__() != 0 %} <script>
<hr> (function main() {
<table> 'use strict';
<caption>Music</caption> const video = document.getElementById('js-video-player');
<tr> const speedInput = document.getElementById('speed-control');
{% for attribute in music_attributes %} speedInput.addEventListener('keyup', (event) => {
<th>{{ attribute }}</th> if (event.key === 'Enter') {
{% endfor %} let speed = parseFloat(speedInput.value);
</tr> if(!isNaN(speed)){
{% for track in music_list %} video.playbackRate = speed;
}
}
});
}());
</script>
</div>
<input class="v-checkbox" name="video_info_list" value="{{ video_info }}" form="playlist-edit" type="checkbox">
<details class="v-download">
<summary class="download-dropdown-label">Download</summary>
<ul class="download-dropdown-content">
{% for format in download_formats %}
<li class="download-format">
<a class="download-link" href="{{ format['url'] }}" download="{{ title }}.{{ format['ext'] }}">
{{ format['ext'] }} {{ format['video_quality'] }} {{ format['audio_quality'] }} {{ format['file_size'] }} {{ format['codecs'] }}
</a>
</li>
{% endfor %}
{% for download in other_downloads %}
<li class="download-format">
<a href="{{ download['url'] }}" download>
{{ download['ext'] }} {{ download['label'] }}
</a>
</li>
{% endfor %}
</ul>
</details>
<span class="v-description">{{ common_elements.text_runs(description)|escape|urlize|timestamps|safe }}</span>
<div class="v-music-list">
{% if music_list.__len__() != 0 %}
<hr>
<table>
<caption>Music</caption>
<tr> <tr>
{% for attribute in music_attributes %} {% for attribute in music_attributes %}
<td>{{ track.get(attribute.lower(), '') }}</td> <th>{{ attribute }}</th>
{% endfor %} {% endfor %}
</tr> </tr>
{% endfor %} {% for track in music_list %}
</table> <tr>
{% endif %} {% for attribute in music_attributes %}
</div> <td>{{ track.get(attribute.lower(), '') }}</td>
<details class="more-info"> {% endfor %}
<summary>More info</summary> </tr>
<div class="more-info-content"> {% endfor %}
<p>Tor exit node: {{ ip_address }}</p> </table>
{% if invidious_used %}
<p>Used Invidious as fallback.</p>
{% endif %}
<p class="allowed-countries">Allowed countries: {{ allowed_countries|join(', ') }}</p>
{% if settings.use_sponsorblock_js %}
<ul class="more-actions">
<li><label><input type=checkbox id=skip_sponsors checked>skip sponsors</label> <span id=skip_n></span>
</ul>
{% endif %} {% endif %}
</div> </div>
</details> <details class="v-more-info">
</div> <summary>More info</summary>
<div class="more-info-content">
<p>Tor exit node: {{ ip_address }}</p>
{% if invidious_used %}
<p>Used Invidious as fallback.</p>
{% endif %}
<p class="allowed-countries">Allowed countries: {{ allowed_countries|join(', ') }}</p>
{% if settings.use_sponsorblock_js %}
<ul class="more-actions">
<li><label><input type=checkbox id=skip_sponsors checked>skip sponsors</label> <span id=skip_n></span>
</ul>
{% endif %}
</div>
</details>
</div>
<div class="side-videos"> <div class="side-videos">
{% if playlist %}
<div class="playlist"> <!-- playlist -->
{% if playlist %}
<div class="site-playlist">
<div class="playlist-header"> <div class="playlist-header">
<a href="{{ playlist['url'] }}" title="{{ playlist['title'] }}"><h3>{{ playlist['title'] }}</h3></a> <a href="{{ playlist['url'] }}" title="{{ playlist['title'] }}"><h3>{{ playlist['title'] }}</h3></a>
<ul class="playlist-metadata"> <ul class="playlist-metadata">
@ -550,168 +183,172 @@ Reload without invidious (for usage of new identity button).</a>
</nav> </nav>
{% if playlist['current_index'] is not none %} {% if playlist['current_index'] is not none %}
<script> <script>
// from https://stackoverflow.com/a/6969486 (function main() {
function escapeRegExp(string) { // from https://stackoverflow.com/a/6969486
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string function escapeRegExp(string) {
} return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
var playability_error = {{ 'true' if playability_error else 'false' }}; }
var playlist_id = {{ playlist['id']|tojson }}; let playability_error = {{ 'true' if playability_error else 'false' }};
playlist_id = escapeRegExp(playlist_id); let playlist_id = {{ playlist['id']|tojson }};
playlist_id = escapeRegExp(playlist_id);
// read cookies on whether to autoplay thru playlist // read cookies on whether to autoplay thru playlist
// pain in the ass: // pain in the ass:
// https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie // https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
var cookieValue = document.cookie.replace(new RegExp( let cookieValue = document.cookie.replace(new RegExp(
'(?:(?:^|.*;\\s*)autoplay_' + playlist_id + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1'); '(?:(?:^|.*;\\s*)autoplay_' + playlist_id + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1');
var autoplayEnabled = 0; let autoplayEnabled = 0;
if(cookieValue.length === 0){ if(cookieValue.length === 0){
autoplayEnabled = 0; autoplayEnabled = 0;
} else { } else {
autoplayEnabled = Number(cookieValue); autoplayEnabled = Number(cookieValue);
} }
// check the checkbox if autoplay is on // check the checkbox if autoplay is on
var checkbox = document.querySelector('#autoplay-toggle'); let checkbox = document.querySelector('#autoplay-toggle');
if(autoplayEnabled){ if(autoplayEnabled){
checkbox.checked = true; checkbox.checked = true;
} }
// listen for checkbox to turn autoplay on and off // listen for checkbox to turn autoplay on and off
checkbox.addEventListener( 'change', function() { checkbox.addEventListener( 'change', function() {
if(this.checked) { if(this.checked) {
autoplayEnabled = 1; autoplayEnabled = 1;
document.cookie = 'autoplay_' + playlist_id + '=1'; document.cookie = 'autoplay_' + playlist_id + '=1';
} else { } else {
autoplayEnabled = 0; autoplayEnabled = 0;
document.cookie = 'autoplay_' + playlist_id + '=0'; document.cookie = 'autoplay_' + playlist_id + '=0';
} }
}); });
if(!playability_error){ const vid = document.getElementById('js-video-player');
// play the video if autoplay is on if(!playability_error){
var vid = document.querySelector('video'); if(autoplayEnabled){
if(autoplayEnabled){ vid.play();
vid.play(); }
} }
}
var currentIndex = {{ playlist['current_index']|tojson }}; let currentIndex = {{ playlist['current_index']|tojson }};
{% if playlist['current_index']+1 == playlist['items']|length %} {% if playlist['current_index']+1 == playlist['items']|length %}
var nextVideoUrl = null; let nextVideoUrl = null;
{% else %} {% else %}
var nextVideoUrl = {{ (playlist['items'][playlist['current_index']+1]['url'])|tojson }}; let nextVideoUrl = {{ (playlist['items'][playlist['current_index']+1]['url'])|tojson }};
{% endif %} {% endif %}
var nextVideoDelay = 1000; let nextVideoDelay = 1000;
// scroll playlist to proper position // scroll playlist to proper position
var pl = document.querySelector('.playlist-videos'); let pl = document.querySelector('.playlist-videos');
// item height + gap == 100 // item height + gap == 100
pl.scrollTop = 100*currentIndex; pl.scrollTop = 100*currentIndex;
// go to next video when video ends // go to next video when video ends
// https://stackoverflow.com/a/2880950 // https://stackoverflow.com/a/2880950
if(nextVideoUrl){ if(nextVideoUrl){
if(playability_error){ if(playability_error){
videoEnded(); videoEnded();
} else { } else {
vid.addEventListener('ended', videoEnded, false); vid.addEventListener('ended', videoEnded, false);
} }
function nextVideo(){ function nextVideo(){
if(autoplayEnabled){ if(autoplayEnabled){
window.location.href = nextVideoUrl; window.location.href = nextVideoUrl;
} }
} }
function videoEnded(e) { function videoEnded(e) {
window.setTimeout(nextVideo, nextVideoDelay); window.setTimeout(nextVideo, nextVideoDelay);
} }
} }
}());
</script> </script>
{% endif %} {% endif %}
{% if playlist['id'] is not none %} {% if playlist['id'] is not none %}
<script> <script>
// lazy load playlist images (function main() {
// copied almost verbatim from // lazy load playlist images
// https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/ // copied almost verbatim from
// IntersectionObserver isn't supported in pre-quantum // https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/
// firefox versions, but the alternative of making it // IntersectionObserver isn't supported in pre-quantum
// manually is a performance drain, so oh well // firefox versions, but the alternative of making it
var observer = new IntersectionObserver(lazyLoad, { // manually is a performance drain, so oh well
let observer = new IntersectionObserver(lazyLoad, {
// where in relation to the edge of the viewport, we are observing
rootMargin: "100px",
// how much of the element needs to have intersected
// in order to fire our loading function
threshold: 1.0
});
// where in relation to the edge of the viewport, we are observing function lazyLoad(elements) {
rootMargin: "100px", elements.forEach(item => {
if (item.intersectionRatio > 0) {
// set the src attribute to trigger a load
item.target.src = item.target.dataset.src;
// stop observing this element. Our work here is done!
observer.unobserve(item.target);
};
});
};
// how much of the element needs to have intersected // Tell our observer to observe all img elements with a "lazy" class
// in order to fire our loading function let lazyImages = document.querySelectorAll('img.lazy');
threshold: 1.0 lazyImages.forEach(img => {
observer.observe(img);
}); });
}());
function lazyLoad(elements) {
elements.forEach(item => {
if (item.intersectionRatio > 0) {
// set the src attribute to trigger a load
item.target.src = item.target.dataset.src;
// stop observing this element. Our work here is done!
observer.unobserve(item.target);
};
});
};
// Tell our observer to observe all img elements with a "lazy" class
var lazyImages = document.querySelectorAll('img.lazy');
lazyImages.forEach(img => {
observer.observe(img);
});
</script> </script>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
<!-- /playlist -->
{% if subtitle_sources %} {% if subtitle_sources %}
<details id="transcript-details"> <details id="transcript-details">
<summary>Transcript</summary> <summary>Transcript</summary>
<div id="transcript-div"> <div id="transcript-div">
<select id="select-tt"> <select id="select-tt">
{% for source in subtitle_sources %} {% for source in subtitle_sources %}
<option>{{ source['label'] }}</option> <option>{{ source['label'] }}</option>
{% endfor %}
</select>
<label for="transcript-use-table">Table view</label>
<input id="transcript-use-table" type="checkbox">
<table id="transcript-table"></table>
</div>
</details>
{% endif %}
{% if settings.related_videos_mode != 0 %}
<details class="related-videos-outer" {{'open' if settings.related_videos_mode == 1 else ''}}>
<summary>Related Videos</summary>
<nav class="related-videos-inner">
{% for info in related %}
{{ common_elements.item(info, include_badges=false) }}
{% endfor %} {% endfor %}
</select> </nav>
<label for="transcript-use-table">Table view</label> </details>
<input type="checkbox" id="transcript-use-table"> {% endif %}
<table id="transcript-table"></table>
</div> </div>
</details>
<!-- comments -->
{% if settings.comments_mode != 0 %}
{% if comments_disabled %}
<div class="comments-area-outer comments-disabled">Comments disabled</div>
{% else %}
<details class="comments-area-outer" {{'open' if settings.comments_mode == 1 else ''}}>
<summary>{{ comment_count|commatize }} comment{{'s' if comment_count != 1 else ''}}</summary>
<div class="comments-area-inner comments-area">
{% if comments_info %}
{{ comments.video_comments(comments_info) }}
{% endif %}
</div>
</details>
{% endif %}
{% endif %} {% endif %}
{% if settings.related_videos_mode != 0 %}
<details class="related-videos-outer" {{'open' if settings.related_videos_mode == 1 else ''}}>
<summary>Related Videos</summary>
<nav class="related-videos-inner">
{% for info in related %}
{{ common_elements.item(info, include_badges=false) }}
{% endfor %}
</nav>
</details>
{% endif %}
</div> </div>
{% if settings.comments_mode != 0 %}
{% if comments_disabled %}
<div class="comments-area-outer comments-disabled">Comments disabled</div>
{% else %}
<details class="comments-area-outer" {{'open' if settings.comments_mode == 1 else ''}}>
<summary>{{ comment_count|commatize }} comment{{'s' if comment_count != 1 else ''}}</summary>
<section class="comments-area-inner comments-area">
{% if comments_info %}
{{ comments.video_comments(comments_info) }}
{% endif %}
</section>
</details>
{% endif %}
{% endif %}
<script> data = {{ js_data|tojson }} </script> <script> data = {{ js_data|tojson }} </script>
<script src="/youtube.com/static/js/common.js"></script> <script src="/youtube.com/static/js/common.js"></script>
<script src="/youtube.com/static/js/transcript-table.js"></script> <script src="/youtube.com/static/js/transcript-table.js"></script>