Change general design theme

This commit is contained in:
Jesús
2020-12-14 23:44:29 -05:00
parent 9d0be82e74
commit 7a765dc664
30 changed files with 5448 additions and 1526 deletions

View File

@@ -3,378 +3,17 @@
{% import "common_elements.html" as common_elements %}
{% import "comments.html" as comments with context %}
{% block style %}
details > summary{
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;
}
<link href="/youtube.com/static/watch.css" rel="stylesheet">
{% endblock style %}
{% block main %}
{% if playability_error %}
<div class="playability-error">
<span>{{ 'Error: ' + playability_error }}
{% if invidious_reload_button %}
<a href="{{ video_url }}&use_invidious=0"><br>
Reload without invidious (for usage of new identity button).</a>
{% endif %}
{% if invidious_reload_button %}
<a href="{{ video_url }}&use_invidious=0"><br>
Reload without invidious (for usage of new identity button).</a>
{% endif %}
</span>
</div>
{% 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>
</div>
{% else %}
<div id="video-container">
<div id="video-container-inner">
<video controls autofocus class="video" height="{{ video_height }}px">
{% for video_source in video_sources %}
<source src="{{ video_source['src'] }}" type="{{ video_source['type'] }}">
{% endfor %}
<figure class="sc-video">
<video id="js-video-player" playsinline controls>
{% for video_source in video_sources %}
<source src="{{ video_source['src'] }}" type="{{ video_source['type'] }}">
{% endfor %}
{% for source in subtitle_sources %}
{% if source['on'] %}
<track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default>
{% else %}
<track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}">
{% endif %}
{% endfor %}
{% for source in subtitle_sources %}
{% if source['on'] %}
<track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default>
{% else %}
<track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}">
{% endif %}
{% endfor %}
</video>
</figure>
</video>
</div>
</div>
{% if time_start != 0 %}
<script>
document.querySelector('video').currentTime = {{ time_start|tojson }};
document.querySelector('js-video-player').currentTime = {{ time_start|tojson }};
</script>
{% endif %}
{% endif %}
<div class="video-info">
<h2 class="title">{{ title }}</h2>
<ul class="labels">
{%- 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>
<div class="sc-info">
<div class="video-info">
<h1 class="v-title">{{ title }}</h1>
<time datetime="$upload_date">Published on {{ time_published }}</time>
<span class="likes-dislikes">{{ like_count }} likes {{ dislike_count }} dislikes</span>
<div class="external-player-controls">
<input id="speed-control" type="text">
<script>
var video = document.querySelector('video');
var speedInput = document.querySelector('#speed-control');
speedInput.addEventListener('keyup', (event) => {
if (event.key === 'Enter') {
var speed = parseFloat(speedInput.value);
if(!isNaN(speed)){
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 class="labels">
{%- 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>
</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="music-list">
{% if music_list.__len__() != 0 %}
<hr>
<table>
<caption>Music</caption>
<tr>
{% for attribute in music_attributes %}
<th>{{ attribute }}</th>
{% endfor %}
</tr>
{% for track in music_list %}
<div class="external-player-controls">
<input class="speed" id="speed-control" type="text">
<script>
(function main() {
'use strict';
const video = document.getElementById('js-video-player');
const speedInput = document.getElementById('speed-control');
speedInput.addEventListener('keyup', (event) => {
if (event.key === 'Enter') {
let speed = parseFloat(speedInput.value);
if(!isNaN(speed)){
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>
{% for attribute in music_attributes %}
<td>{{ track.get(attribute.lower(), '') }}</td>
<th>{{ attribute }}</th>
{% endfor %}
</tr>
{% endfor %}
</table>
{% endif %}
</div>
<details class="more-info">
<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>
{% for track in music_list %}
<tr>
{% for attribute in music_attributes %}
<td>{{ track.get(attribute.lower(), '') }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{% endif %}
</div>
</details>
</div>
<details class="v-more-info">
<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">
{% if playlist %}
<div class="playlist">
<div class="side-videos">
<!-- playlist -->
{% if playlist %}
<div class="site-playlist">
<div class="playlist-header">
<a href="{{ playlist['url'] }}" title="{{ playlist['title'] }}"><h3>{{ playlist['title'] }}</h3></a>
<ul class="playlist-metadata">
@@ -550,168 +183,172 @@ Reload without invidious (for usage of new identity button).</a>
</nav>
{% if playlist['current_index'] is not none %}
<script>
// from https://stackoverflow.com/a/6969486
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 }};
playlist_id = escapeRegExp(playlist_id);
(function main() {
// from https://stackoverflow.com/a/6969486
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
let playability_error = {{ 'true' if playability_error else 'false' }};
let playlist_id = {{ playlist['id']|tojson }};
playlist_id = escapeRegExp(playlist_id);
// read cookies on whether to autoplay thru playlist
// pain in the ass:
// https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
var cookieValue = document.cookie.replace(new RegExp(
'(?:(?:^|.*;\\s*)autoplay_' + playlist_id + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1');
var autoplayEnabled = 0;
if(cookieValue.length === 0){
autoplayEnabled = 0;
} else {
autoplayEnabled = Number(cookieValue);
}
// read cookies on whether to autoplay thru playlist
// pain in the ass:
// https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
let cookieValue = document.cookie.replace(new RegExp(
'(?:(?:^|.*;\\s*)autoplay_' + playlist_id + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1');
let autoplayEnabled = 0;
if(cookieValue.length === 0){
autoplayEnabled = 0;
} else {
autoplayEnabled = Number(cookieValue);
}
// check the checkbox if autoplay is on
var checkbox = document.querySelector('#autoplay-toggle');
if(autoplayEnabled){
checkbox.checked = true;
}
// check the checkbox if autoplay is on
let checkbox = document.querySelector('#autoplay-toggle');
if(autoplayEnabled){
checkbox.checked = true;
}
// listen for checkbox to turn autoplay on and off
checkbox.addEventListener( 'change', function() {
if(this.checked) {
autoplayEnabled = 1;
document.cookie = 'autoplay_' + playlist_id + '=1';
} else {
autoplayEnabled = 0;
document.cookie = 'autoplay_' + playlist_id + '=0';
}
});
// listen for checkbox to turn autoplay on and off
checkbox.addEventListener( 'change', function() {
if(this.checked) {
autoplayEnabled = 1;
document.cookie = 'autoplay_' + playlist_id + '=1';
} else {
autoplayEnabled = 0;
document.cookie = 'autoplay_' + playlist_id + '=0';
}
});
if(!playability_error){
// play the video if autoplay is on
var vid = document.querySelector('video');
if(autoplayEnabled){
vid.play();
}
}
const vid = document.getElementById('js-video-player');
if(!playability_error){
if(autoplayEnabled){
vid.play();
}
}
var currentIndex = {{ playlist['current_index']|tojson }};
{% if playlist['current_index']+1 == playlist['items']|length %}
var nextVideoUrl = null;
{% else %}
var nextVideoUrl = {{ (playlist['items'][playlist['current_index']+1]['url'])|tojson }};
{% endif %}
var nextVideoDelay = 1000;
let currentIndex = {{ playlist['current_index']|tojson }};
{% if playlist['current_index']+1 == playlist['items']|length %}
let nextVideoUrl = null;
{% else %}
let nextVideoUrl = {{ (playlist['items'][playlist['current_index']+1]['url'])|tojson }};
{% endif %}
let nextVideoDelay = 1000;
// scroll playlist to proper position
var pl = document.querySelector('.playlist-videos');
// item height + gap == 100
pl.scrollTop = 100*currentIndex;
// scroll playlist to proper position
let pl = document.querySelector('.playlist-videos');
// item height + gap == 100
pl.scrollTop = 100*currentIndex;
// go to next video when video ends
// https://stackoverflow.com/a/2880950
if(nextVideoUrl){
if(playability_error){
videoEnded();
} else {
vid.addEventListener('ended', videoEnded, false);
}
function nextVideo(){
if(autoplayEnabled){
window.location.href = nextVideoUrl;
}
}
function videoEnded(e) {
window.setTimeout(nextVideo, nextVideoDelay);
}
}
// go to next video when video ends
// https://stackoverflow.com/a/2880950
if(nextVideoUrl){
if(playability_error){
videoEnded();
} else {
vid.addEventListener('ended', videoEnded, false);
}
function nextVideo(){
if(autoplayEnabled){
window.location.href = nextVideoUrl;
}
}
function videoEnded(e) {
window.setTimeout(nextVideo, nextVideoDelay);
}
}
}());
</script>
{% endif %}
{% if playlist['id'] is not none %}
<script>
// lazy load playlist images
// copied almost verbatim from
// https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/
// IntersectionObserver isn't supported in pre-quantum
// firefox versions, but the alternative of making it
// manually is a performance drain, so oh well
var observer = new IntersectionObserver(lazyLoad, {
(function main() {
// lazy load playlist images
// copied almost verbatim from
// https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/
// IntersectionObserver isn't supported in pre-quantum
// firefox versions, but the alternative of making it
// 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
rootMargin: "100px",
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);
};
});
};
// how much of the element needs to have intersected
// in order to fire our loading function
threshold: 1.0
});
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);
});
// Tell our observer to observe all img elements with a "lazy" class
let lazyImages = document.querySelectorAll('img.lazy');
lazyImages.forEach(img => {
observer.observe(img);
});
}());
</script>
{% endif %}
</div>
{% endif %}
{% endif %}
<!-- /playlist -->
{% if subtitle_sources %}
<details id="transcript-details">
<summary>Transcript</summary>
<div id="transcript-div">
<select id="select-tt">
{% for source in subtitle_sources %}
<option>{{ source['label'] }}</option>
{% if subtitle_sources %}
<details id="transcript-details">
<summary>Transcript</summary>
<div id="transcript-div">
<select id="select-tt">
{% for source in subtitle_sources %}
<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 %}
</select>
<label for="transcript-use-table">Table view</label>
<input type="checkbox" id="transcript-use-table">
<table id="transcript-table"></table>
</div>
</details>
</nav>
</details>
{% endif %}
</div>
<!-- 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 %}
{% 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>
{% 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 src="/youtube.com/static/js/common.js"></script>
<script src="/youtube.com/static/js/transcript-table.js"></script>