Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
:root {
--player-bg-color: #000;
--player-control-icon-fill: #fff;
--player-control-item-half-width: clamp(calc(16px / 2), calc(10vmax / 2), calc(32px / 2));
--player-control-item-height: clamp(16px, 10vmax, 32px);
--close-btn-fill-color: #000;
--controls-bottom-distance: 15px;
--controls-bottom-upper-height: 30px;
--scrubber-vertical-margin: 7px;
--resize-margin: 5px;
background-color: var(--player-bg-color);
overflow: hidden;
}
button::-moz-focus-inner {
border: 0;
}
body {
margin: 0;
}
body:fullscreen {
-moz-window-dragging: no-drag;
}
.player-holder {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
.seethrough-mode {
background: transparent;
.player-holder {
will-change: opacity, filter;
transition: opacity 160ms linear, filter 160ms linear;
body:hover:not(:fullscreen) & {
opacity: 0.35;
filter: blur(8px);
}
}
}
browser {
flex: 1;
}
#controls {
height: calc(100% - 2 * var(--resize-margin));
left: 0;
position: absolute;
top: 0;
width: calc(100% - 2 * var(--resize-margin));
margin: var(--resize-margin);
-moz-window-dragging: drag;
}
#controls button {
appearance: none;
border: 0;
z-index: 1;
}
#controls button:focus-visible,
#controls input:focus-visible,
.switch > input:focus-visible + .slider {
outline: 3px solid #0060DF;
box-shadow: 1px 2px 5px #000;
}
/* Styling for background gradient.
* Opacity changes are handled separately under .control-item.
*/
#controls-bottom-gradient {
background: linear-gradient(0deg, #000000 -13.24%, rgba(0, 0, 0, 0) 90.44%);
position: absolute;
height: calc(var(--controls-bottom-distance) + 2 * var(--resize-margin) + var(--player-control-item-height) + var(--controls-bottom-upper-height) + var(--scrubber-vertical-margin));
bottom: 0;
width: 100vw;
margin: 0 calc(-1 * var(--resize-margin)) calc(-1 * var(--resize-margin)) calc(-1 * var(--resize-margin));
pointer-events: none;
-moz-window-dragging: drag;
}
#controls-bottom {
position: absolute;
bottom: var(--controls-bottom-distance);
width: 100%;
}
.controls-bottom-lower {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin: 0 24px;
}
.start-controls {
display: grid;
justify-self: start;
}
.center-controls {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-areas: "seekbackward playpause seekforward";
justify-self: center;
gap: 8px;
}
.end-controls {
display: grid;
grid-template-columns: 1fr 2fr 1fr 1fr;
grid-template-areas: "audio audio-scrubber closedcaption fullscreen";
justify-self: end;
gap: 8px;
}
.control-item {
-moz-window-dragging: no-drag;
transition: opacity 160ms linear, fill-opacity 160ms linear;
opacity: 0;
cursor: pointer;
}
.control-button {
background-color: transparent;
border-radius: 4px;
/**
* Make the button dimensions a square proportional to one
* dimension of the window - in this case, the width dimension,
* since we suspect most videos are wider than they are tall.
*/
height: var(--player-control-item-height);
width: 10vmax;
max-width: 32px;
min-width: 16px;
-moz-context-properties: fill, fill-opacity;
fill: var(--player-control-icon-fill);
background-position: center;
background-size: 60%;
background-repeat: no-repeat;
}
.control-button:hover:enabled {
background-color: rgba(255, 255, 255, .25);
}
#controls:is([keying], [showing]) .control-button:disabled,
/* Only change opacity on hover events for non-fullscreen mode.
* Fullscreen mode uses the `showing` attribute instead. */
body:not(:fullscreen) #controls:hover .control-button:disabled {
/* Set `fill-opacity` to the desired opacity in addition to full `opacity`
* to allow having the button's tooltip in full opacity even if the button is disabled. */
fill-opacity: 0.4;
opacity: 1 !important;
}
.control-item:focus-visible::after,
.control-item:hover::after {
content: attr(tooltip);
display: inline-block;
width: max-content;
position: relative;
padding: .4em .5em;
background: #000000;
color: #ffffff;
border-radius: 4px;
pointer-events: none;
}
/* Since #controls is set to LTR, button tooltips would normally appear
* as LTR also for RTL locales. To fix this, set the .control-item's ::after
* to RTL based on the root directionality.
* Because of that, don't set logical properties for the next set of rules. */
:root:dir(rtl) .control-item::after {
direction: rtl;
}
/* Set the tooltip position for different playback controls */
.tooltip-under-controls:focus-visible::after,
.tooltip-under-controls:hover::after {
bottom: -3em;
}
#close:focus-visible::after,
#close:hover::after,
#unpip[mac="true"]:focus-visible::after,
#unpip[mac="true"]:hover::after {
float: right;
transform: translateX(1em);
}
#unpip:focus-visible::after,
#unpip:hover::after,
#close[mac="true"]:focus-visible::after,
#close[mac="true"]:hover::after {
float: left;
transform: translateX(-1em);
}
.tooltip-over-controls:focus-visible::after,
.tooltip-over-controls:hover::after {
bottom: 3em;
}
.inline-end-tooltip:focus-visible::after,
.inline-end-tooltip:hover::after {
float: right;
right: -1em;
}
.inline-start-tooltip:focus-visible::after,
.inline-start-tooltip:hover::after {
float: left;
left: -1em;
}
.center-tooltip:focus-visible::after,
.center-tooltip:hover::after {
right: 0.8em;
translate: calc(-50% + var(--player-control-item-half-width));
}
/* Since the unpip button icon is reversed for RTL locales,
* re-position the tooltip so that the tooltip remains in the original placement */
:root:dir(rtl) #unpip:focus-visible::after,
:root:dir(rtl) #unpip:hover::after {
float: right;
}
:root:dir(rtl) #unpip[mac="true"]:focus-visible::after,
:root:dir(rtl) #unpip[mac="true"]:hover::after {
float: left;
}
/* Since the unpip icon is reversed for RTL locales,
* flip its tooltip back */
:root:dir(rtl) #unpip:focus-visible::after,
:root:dir(rtl) #unpip:hover::after {
scale: -1 1;
}
/* Set opacity for buttons and scrubber when controls are visible on the pip window and are not hovered over.
* For fullscreen mode, only apply opacity if there is a showing attribute. */
body:not(:fullscreen) #controls:hover .control-item:not(:hover),
body:fullscreen #controls[showing]:hover .control-item:not(:hover),
#controls[donthide] .control-item {
opacity: 0.8;
}
#controls[keying] .control-item,
#controls[showing] .control-item,
.control-item:hover {
opacity: 1;
}
/* Background gradient is the only control item with a fixed opacity value. */
#controls[keying] #controls-bottom-gradient,
#controls[showing] #controls-bottom-gradient,
#controls-bottom-gradient:hover {
opacity: 0.8;
}
/* For readability, timestamp should maintain full opacity when visible */
body:not(:fullscreen) #controls:hover #timestamp,
body:fullscreen #controls[showing]:hover {
opacity: 1;
}
#close,
#unpip {
background-color: rgba(255, 255, 255, .8);
position: absolute;
fill: var(--close-btn-fill-color);
}
#close:is(:hover, :focus-visible),
#unpip:is(:hover, :focus-visible) {
background-color: rgba(255, 255, 255, .9);
}
#close {
background-image: url("chrome://global/skin/icons/close.svg");
right: 10px;
top: 10px;
}
#close[mac="true"] {
right: auto;
left: 10px;
}
#unpip {
background-image: url("chrome://global/skin/media/picture-in-picture-closed.svg");
left: 10px;
top: 10px;
}
#unpip[mac="true"] {
right: 10px;
left: auto;
}
#playpause {
grid-area: playpause;
}
#audio {
grid-area: audio;
}
#audio-scrubber {
grid-area: audio-scrubber;
align-self: center;
width: 64px;
background-color: transparent;
padding: 6px 2px;
margin: 0;
&::-moz-range-thumb {
border-radius: 8px;
background-color: #FFFFFF;
width: 8px;
height: 8px;
padding: 0;
}
&::-moz-range-track {
background-color: #969696;
}
&::-moz-range-progress {
background-color: #FFFFFF;
}
&,
&::-moz-range-track,
&::-moz-range-progress {
height: 2px;
border-radius: 10px;
}
}
#fullscreen {
grid-area: fullscreen;
}
#controls.playing #playpause {
background-image: url("chrome://global/skin/media/pause-fill.svg");
}
#controls:not(.playing) #playpause {
background-image: url("chrome://global/skin/media/play-fill.svg");
}
#controls.muted #audio {
background-image: url("chrome://global/skin/media/audio-muted.svg");
}
#controls:not(.muted) #audio {
background-image: url("chrome://global/skin/media/audio.svg");
}
body:fullscreen #fullscreen {
background-image: url("chrome://global/skin/media/picture-in-picture-exit-fullscreen-button.svg");
background-size: auto;
}
body:not(:fullscreen) #fullscreen {
background-image: url("chrome://global/skin/media/picture-in-picture-enter-fullscreen-button.svg");
background-size: auto;
}
#seekBackward {
background-image: url("chrome://global/skin/media/picture-in-picture-seekBackward-button.svg");
background-size: auto;
grid-area: seekbackward;
}
#seekForward {
background-image: url("chrome://global/skin/media/picture-in-picture-seekForward-button.svg");
background-size: auto;
grid-area: seekforward;
}
:root:dir(rtl) #unpip {
transform: scaleX(-1);
}
#closed-caption {
background-image: url("chrome://global/skin/media/closed-caption-settings-button.svg");
color: white;
grid-area: closedcaption;
}
.box {
-moz-window-dragging: no-drag;
background-color: #2B2A33;
text-align: start;
font-size: 1em;
width: 186px;
padding: 0 8px;
margin: 0;
border-radius: 8px;
}
:root:dir(rtl) .box {
direction: rtl;
}
.a11y-only {
visibility: hidden;
position: absolute;
}
.hide {
display: none;
}
.bold {
font-weight: 590;
}
.box > input[type="radio"] {
background-color: red;
fill: currentColor;
}
.box label:not(.switch) {
color: white;
font-family: sans-serif;
}
#subtitles-toggle-label {
width: fit-content;
padding: 8px;
}
.panel {
position: absolute;
bottom: 40px;
user-select: none;
right: 24px;
}
.panel-fieldset {
border: none;
margin-top: 8px;
padding-inline-start: 0;
}
.panel-legend {
font-family: sans-serif;
color: white;
margin-top: 8px;
padding-inline-start: 0;
}
.arrow {
border: 10px solid transparent;
border-top-color: #2B2A33;
width: 0;
height: 0;
inset-inline-start: 136px;
position: relative;
}
.grey-line {
width: 100%;
height: 1px;
background: #8F8F9D;
}
.font-size-selection {
margin-inline-start: 8px;
padding-inline-start: 8px;
}
.font-size-selection-radio {
display: flex;
width: fit-content;
cursor: pointer;
padding-block: 8px;
}
.font-size-selection-radio label {
cursor: pointer;
}
.font-size-selection-radio > input[type="radio"] {
appearance: none;
width: 16px;
height: 16px;
border: 1px solid #8f8f9d;
border-radius: 50%;
margin: 0;
margin-inline-end: 6px;
}
.font-size-selection-radio > input[type="radio"]:checked {
border: 4px solid #00ddff;
}
.subtitle-grid {
display: grid;
grid-template-rows: auto;
grid-template-columns: auto 46px;
padding: 8px;
}
.switch {
position: relative;
display: inline-block;
width: 32px;
height: 16px;
grid-column: 2;
margin: 8px;
cursor: pointer;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
inset: 0;
transition: transform 250ms;
border-radius: 13px;
background-color: #55545f;
}
.slider::before {
position: absolute;
content: '';
height: 12px;
width: 12px;
inset-inline-start: 2px;
bottom: 2px;
background-color: #2B2A33;
transition: transform 250ms;
border-radius: 50%;
}
input:checked + .slider {
background-color: #00ddff;
}
input:checked + .slider::before {
transform: translateX(16px);
}
:root:dir(rtl) input:checked + .slider::before {
transform: translateX(-16px);
}
.font-size-overlay {
opacity: 0.4;
pointer-events: none;
}
.controls-bottom-upper {
width: calc(100% - 38px);
height: var(--controls-bottom-upper-height);
margin: 0 19px;
display: grid;
}
.scrubber-no-drag {
-moz-window-dragging: no-drag;
height: 16px;
margin: var(--scrubber-vertical-margin) 0;
display: grid;
justify-items: center;
align-items: center;
width: 100%;
}
#scrubber {
width: 100%;
background-color: transparent;
padding: 6px 2px;
&::-moz-range-thumb {
border-radius: 14px;
background-color: #BFBFC9;
width: 8px;
height: 8px;
border: 3px solid #FFFFFF;
padding: 0;
}
&::-moz-range-track {
background-color: #969696;
}
&::-moz-range-progress {
background-color: #FFFFFF;
}
&,
&::-moz-range-track,
&::-moz-range-progress {
height: 4px;
border-radius: 10px;
}
}
#timestamp {
align-self: center;
color: #FFFFFF;
cursor: default;
font-family: system-ui;
font-size: 0.9em;
font-variant-numeric: tabular-nums;
user-select: none;
width: 16ch;
grid-area: timestamp;
}
#timestamp::after {
background: unset;
content: unset;
}
@media (width <= 630px) {
#audio-scrubber {
display: none;
}
.end-controls {
grid-template-columns: repeat(3, 1fr);
grid-template-areas: "audio closedcaption fullscreen";
}
}
@media (width <= 475px) {
#closed-caption {
display: none;
}
.end-controls {
grid-template-columns: repeat(2, 1fr);
grid-template-areas: "audio fullscreen";
}
}
@media (height <= 325px) and (width > 630px) {
#closed-caption {
display: none;
}
.end-controls {
grid-template-columns: 1fr 2fr 1fr;
grid-template-areas: "audio audio-scrubber fullscreen";
}
}
@media (height <= 325px) and (width <= 630px) {
#closed-caption,
#audio-scrubber {
display: none;
}
.end-controls {
grid-template-columns: repeat(2, 1fr);
grid-template-areas: "audio fullscreen";
}
}
@media (width <= 440px) {
#timestamp {
display: none;
}
}
@media (width <= 350px) {
#fullscreen {
display: none;
}
.end-controls {
grid-template-columns: repeat(1, 1fr);
grid-template-areas: "audio";
}
}
@media (height <= 200px) {
.scrubber-no-drag {
display: none;
}
}
@media (width <= 300px) {
.scrubber-no-drag,
#seekForward,
#seekBackward,
.start-controls {
display: none;
}
.controls-bottom-lower {
grid-template-columns: repeat(2, 1fr);
}
.center-controls {
grid-template-columns: repeat(1, 1fr);
grid-template-areas: "playpause";
}
.end-controls {
justify-self: center;
}
}