diff --git a/.csslintrc b/.csslintrc new file mode 100644 index 0000000000..3dd9dbe367 --- /dev/null +++ b/.csslintrc @@ -0,0 +1,13 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + "font-family-no-missing-generic-family-keyword": null, + "declaration-block-single-line-max-declarations": null, + "max-line-length": [ 80, { + ignorePattern: "/^@import\\s+/", + }], + "selector-list-comma-newline-after": "never-multi-line", + "selector-pseudo-element-colon-notation": "single", + "unit-whitelist": ["em", "%", "px", "s", "deg", "vmin", "ms", "vh"] + } +} \ No newline at end of file diff --git a/build/check.py b/build/check.py index 24992993d1..cd5ab8cb48 100755 --- a/build/check.py +++ b/build/check.py @@ -63,6 +63,22 @@ def check_js_lint(args): return linter.lint(fix=args.fix, force=args.force) +def check_css_lint(args): + """Runs the CSS linter.""" + logging.info('Linting CSS...') + + match = re.compile(r'.*\.(less|css)$') + base = shakaBuildHelpers.get_source_base() + def get(*path_components): + return shakaBuildHelpers.get_all_files( + os.path.join(base, *path_components), match) + files = (get('ui') + get('demo')); + config_path = os.path.join(base, '.csslintrc') + + linter = compiler.CssLinter(files, config_path) + return linter.lint(fix=args.fix, force=args.force) + + def check_html_lint(args): """Runs the HTML linter.""" logging.info('Linting HTML...') @@ -291,6 +307,7 @@ def main(args): steps = [ check_js_lint, check_html_lint, + check_css_lint, check_complete, check_spelling, check_eslint_disable, diff --git a/build/compiler.py b/build/compiler.py index d38200c706..59809bedc8 100644 --- a/build/compiler.py +++ b/build/compiler.py @@ -306,6 +306,46 @@ def lint(self, fix=False, force=False): return True +class CssLinter(object): + def __init__(self, source_files, config_path): + self.source_files = _canonicalize_source_files(source_files) + self.config_path = config_path + self.output = _get_source_path('dist/.csslintstamp') + + def lint(self, fix=False, force=False): + """Run CSS linter checks on the files in |self.source_files|. + + Args: + fix: If True, ask the linter to fix what errors it can automatically. + force: Run linter checks even if the inputs have not changed. + + Returns: + True on success; False on failure. + """ + deps = self.source_files + [self.config_path] + if not force and not _must_build(self.output, deps): + return True + + stylelint = shakaBuildHelpers.get_node_binary('stylelint') + cmd_line = stylelint + ['--config', self.config_path] + self.source_files + # Disables globbing, since that messes up our nightly tests, and we don't + # use it anyway. + # This is currently a flag added in a fork we maintain, but there is a pull + # request in progress for this. + # See: https://github.com/stylelint/stylelint/issues/4193 + cmd_line += ['--disable-globbing']; + + if fix: + cmd_line += ['--fix'] + + if shakaBuildHelpers.execute_get_code(cmd_line) != 0: + return False + + # Update the timestamp of the file that tracks when we last updated. + _update_timestamp(self.output) + return True + + class HtmlLinter(object): def __init__(self, source_files, config_path): self.source_files = _canonicalize_source_files(source_files) diff --git a/demo/cast_receiver/receiver_app.css b/demo/cast_receiver/receiver_app.css index e7a9c3beb3..17eea73b04 100644 --- a/demo/cast_receiver/receiver_app.css +++ b/demo/cast_receiver/receiver_app.css @@ -72,7 +72,6 @@ body { * they will be visible on top until we decide to hide them and show the * player. */ z-index: 99; - padding-top: 60px; padding-left: 0; background-color: black; @@ -105,18 +104,25 @@ body:after { } @keyframes bg-change { - 0% { background-image: url('idle1.jpg'); padding-left: 0; } - 32% { background-image: url('idle1.jpg'); padding-left: 0; } - - 34% { background-image: url('idle2.jpg'); padding-left: 0; } - 49% { background-image: url('idle2.jpg'); padding-left: 0; } - 50% { background-image: url('idle2.jpg'); padding-left: 400px; } - 65% { background-image: url('idle2.jpg'); padding-left: 400px; } - - 67% { background-image: url('idle3.jpg'); padding-left: 400px; } - 87% { background-image: url('idle3.jpg'); padding-left: 400px; } - 88% { background-image: url('idle3.jpg'); padding-left: 0; } - 98% { background-image: url('idle3.jpg'); padding-left: 0; } + 0% { background-image: url('idle1.jpg'); padding-left: 0; } + + 32% { background-image: url('idle1.jpg'); padding-left: 0; } + + 34% { background-image: url('idle2.jpg'); padding-left: 0; } + + 49% { background-image: url('idle2.jpg'); padding-left: 0; } + + 50% { background-image: url('idle2.jpg'); padding-left: 400px; } + + 65% { background-image: url('idle2.jpg'); padding-left: 400px; } + + 67% { background-image: url('idle3.jpg'); padding-left: 400px; } + + 87% { background-image: url('idle3.jpg'); padding-left: 400px; } + + 88% { background-image: url('idle3.jpg'); padding-left: 0; } + + 98% { background-image: url('idle3.jpg'); padding-left: 0; } 100% { background-image: url('idle1.jpg'); padding-left: 0; } } diff --git a/demo/demo.less b/demo/demo.less index 2cf4b27c27..2623b122db 100644 --- a/demo/demo.less +++ b/demo/demo.less @@ -51,6 +51,7 @@ position: absolute; top: 10px; right: 10px; + /* Give the button a round background, meant to look like the play button. */ border-radius: 50%; width: 32px; @@ -65,11 +66,16 @@ html, body { /* Give the FAB a drop shadow, that expands a little bit when moused over. */ .mdl-button--fab { - filter: drop-shadow(0 2px 5px #333333); + filter: drop-shadow(0 2px 5px #333); transition: 0.2s ease-in-out; } + .mdl-button--fab:hover { - filter: drop-shadow(0 2px 8px #333333); + filter: drop-shadow(0 2px 8px #333); +} + +.input-container-label { + padding-right: 1em; } /* Remove vertical padding on MDL text fields, but only while they are in @@ -79,22 +85,27 @@ html, body { // The default width of 300px is a bit too wide for us. width: 200px; } + .hamburger-menu .mdl-textfield__label { top: 4px; } + .hamburger-menu .mdl-textfield__label:after { bottom: 0; } + .hamburger-menu .mdl-layout-title { /* The line-height style in mdl-layout-title looks weird on narrow displays, * so remove it in the hamburger menu. */ line-height: unset; } + .hamburger-menu .input-container-label { /* Give the labels for input rows a left margin. This keeps them from directly * touching the left side of the screen, for improved readability. */ margin-left: 1em; } + .hamburger-menu .mdl-button--raised { /* Left-align the text content of the section header buttons. */ text-align: left; @@ -125,7 +136,7 @@ html, body { color: white; position: relative; padding: 0 0; - top: 0em; + top: 0; right: 1em; float: right; font-size: 24px; @@ -139,6 +150,7 @@ html, body { display: inline-block; margin: 1em; } + .asset-card-unsupported { display: inline-block; margin: 1em; @@ -152,41 +164,53 @@ html, body { background-size: contain; background-repeat: no-repeat; display: inline-block; + /* features */ &[icon="high_definition"] { background-image: data-uri('icons/custom_high_definition.svg'); } + &[icon="ultra_high_definition"] { background-image: data-uri('icons/custom_ultra_high_definition.svg'); } + &[icon="subtitles"] { background-image: data-uri('icons/baseline-subtitles-24px.svg'); } + &[icon="closed_caption"] { background-image: data-uri('icons/baseline-closed_caption-24px.svg'); } + &[icon="live"] { background-image: data-uri('icons/baseline-live_tv-24px.svg'); } + &[icon="trick_mode"] { background-image: data-uri('icons/baseline-fast_forward-24px.svg'); } + &[icon="surround_sound"] { background-image: data-uri('icons/baseline-surround_sound-24px.svg'); } + &[icon="multiple_languages"] { background-image: data-uri('icons/baseline-language-24px.svg'); } + &[icon="audio_only"] { background-image: data-uri('icons/baseline-audiotrack-24px.svg'); } + /* key systems */ &[icon="widevine"] { background-image: data-uri('icons/custom_widevine.svg'); } + &[icon="clear_key"] { background-image: data-uri('icons/custom_clear_key.svg'); } + &[icon="playready"] { background-image: data-uri('icons/custom_playready.svg'); } @@ -209,10 +233,11 @@ html, body { .progress-circle { position: absolute; - top: 0px; + top: 0; right: 14px; width: @progress-circle-size; height: @progress-circle-size; + /* This should be unclickable, so that you can click the button beneath. */ pointer-events: none; } @@ -237,12 +262,14 @@ html, body { .progress-circle-bar { .progress-circle-circle(); + stroke: @mdl-button-color; stroke-linecap: round; } .progress-circle-back { .progress-circle-circle(); + stroke: #eee; } @@ -261,9 +288,9 @@ html, body { .asset-card img { /* Icons are 300px by 210px (10:7 aspect ratio). */ width: 300px; + /* Constrain to space if necessary. */ max-width: 100%; - display: block; margin-left: auto; margin-right: auto; @@ -292,6 +319,7 @@ html, body { height: auto; flex-wrap: wrap; } + /* The spacer should be hidden in this mode, so that the version number is no * longer being forced to the right. */ .app-header .mdl-layout-spacer { @@ -334,6 +362,7 @@ footer .mdl-mega-footer__link-list { /* The default color was low-contrast on this dark background. */ color: lighten(@mdl-footer-link-color, 20%); } + a[disabled] { /* Making the disabled version even darker would be even worse in terms of * contrast, so use a strikethrough style instead. */ @@ -403,6 +432,7 @@ footer .mdl-mega-footer__link-list { /* The video bar fills the horizontal space, but its height depends on the * contents. */ width: 100%; + /* Add a little bit of padding on top, to make the video not look cropped. */ padding-top: 1em; } @@ -419,6 +449,7 @@ footer .mdl-mega-footer__link-list { * mobile screens. */ max-width: 100%; } + #video-bar.no-input-sized { /* Add some additional padding so that TV overscan doesn't cut off the version * number or other information we might need. */ @@ -431,6 +462,7 @@ footer .mdl-mega-footer__link-list { height: 100%; height: calc(100% - 5em); } + .video-container.no-input-sized { width: 100%; height: 100%; @@ -453,26 +485,28 @@ footer .mdl-mega-footer__link-list { .input-container-row { display: inline-block; } + .input-container-style-flex { display: flex; flex-wrap: wrap; } + .input-container-style-vertical { text-align: left; } + .input-container-style-accordion { text-align: left; opacity: 0; - max-height: 0px; + max-height: 0; transition: 0.3s ease-in-out; } + .input-container-style-accordion.show { opacity: 1; + /* If the max-height is too high (e.g. set to 100%), the "sliding out" * animation is too fast to make out with the eye. * So give it a fixed maximum instead. */ max-height: 1000px; } -.input-container-label { - padding-right: 1em; -} diff --git a/package.json b/package.json index 2b06fd779b..645b941cce 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "mux.js": "^5.1.3", "rimraf": "^2.6.3", "sprintf-js": "^1.1.2", + "stylelint": "github:theodab/stylelint", + "stylelint-config-standard": "^18.3.0", "tippy.js": "^4.3.1", "which": "^1.3.1" }, diff --git a/ui/controls.less b/ui/controls.less index 095a335787..c08837a76d 100644 --- a/ui/controls.less +++ b/ui/controls.less @@ -22,7 +22,6 @@ @import "less/spinner.less"; @import "less/other_elements.less"; @import "less/overflow_menu.less"; - @import (css, inline) "https://fonts.googleapis.com/css?family=Roboto"; @import (css, inline) "https://fonts.googleapis.com/icon?family=Material+Icons"; @@ -35,6 +34,7 @@ font-size: 150%; padding: 5px; + /* dimensions */ bottom: 50px; left: 0; right: 0; @@ -53,12 +53,10 @@ to make them override display setting previously set in .overflowMenu button. See https://goo.gl/egXAJY for explanation of how CSS cascade rules work. */ -button.shaka-hidden, -.shaka-hidden { +button.shaka-hidden, .shaka-hidden { .hidden(); } -button.shaka-displayed, -.shaka-displayed { +button.shaka-displayed, .shaka-displayed { display: flex; } diff --git a/ui/less/buttons.less b/ui/less/buttons.less index f5d300b046..2b065899d6 100644 --- a/ui/less/buttons.less +++ b/ui/less/buttons.less @@ -41,7 +41,7 @@ border-radius: 50%; /* A small drop shadow below the button. */ - box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 20px 0px; + box-shadow: rgba(0, 0, 0, 0.1) 0 0 20px 0; /* No border. */ border: none; @@ -52,8 +52,9 @@ background-size: 50%; background-repeat: no-repeat; background-position: center center; + /* A background color behind the play arrow. */ - background-color: rgba(255, 255, 255, .9); + background-color: rgba(255, 255, 255, 0.9); /* Actual icon images for the two states this could be in. * These will be inlined as data URIs when compiled, and so do not need to be @@ -62,6 +63,7 @@ &[icon="play"] { background-image: data-uri('images/play_arrow.svg'); } + &[icon="pause"] { background-image: data-uri('images/pause.svg'); } @@ -91,11 +93,11 @@ /* Use a consistent outline focus style across browsers. */ .shaka-controls-container { - button:focus, - input:focus { + button:focus, input:focus { /* Most browsers will fall back to "Highlight" (system setting) color for * the focus outline. */ outline: 1px solid Highlight; + /* WebKit-based and Blink-based browsers have this as their default outline * color. */ outline: 1px solid -webkit-focus-ring-color; @@ -103,8 +105,7 @@ /* Disable this Mozilla-specific focus ring, since we have an outline defined * for focus. */ - button::-moz-focus-inner, - input::-moz-focus-outer { + button:-moz-focus-inner, input:-moz-focus-outer { outline: none; border: 0; } @@ -114,8 +115,7 @@ * it doesn't look great. This removes the outline for * mouse users while leaving it for keyboard users. */ .shaka-controls-container:not(.shaka-keyboard-navigation) { - button:focus, - input:focus { + button:focus, input:focus { outline: none; border: 0; } diff --git a/ui/less/containers.less b/ui/less/containers.less index 40fb4a8feb..48a28e2050 100644 --- a/ui/less/containers.less +++ b/ui/less/containers.less @@ -17,6 +17,8 @@ /* All of the top-level containers into which various visible features go. */ +@transparent: rgba(0, 0, 0, 0); + /* A container for the entire video + controls combo. This is the auto-setup * div which we populate. */ .shaka-video-container { @@ -31,6 +33,7 @@ .material-icons { font-family: 'Material Icons'; } + * { font-family: Roboto-Regular, Roboto, sans-serif; } @@ -44,6 +47,7 @@ * pseudo-class they don't support. */ .fullscreen-container() { .fill-container(); + background-color: black; .shaka-text-container { @@ -78,18 +82,18 @@ /* A flex container, to make layout of children easier to reason about. */ display: flex; + /* Defines in which direction the children should flow. */ flex-direction: column; + /* Pushes the children toward the bottom of the container. */ justify-content: flex-end; + /* Centers children horizontally. */ align-items: center; /* A black gradient at the bottom, behind the controls, but only so high. */ - background: linear-gradient( - to top, - rgba(0, 0, 0, 1) 0, - rgba(0, 0, 0, 0) 92px); + background: linear-gradient(to top, rgba(0, 0, 0, 1) 0, @transparent 92px); /* By default, do not allow any of our controls to shrink. * Specific controls can use .shrinkable() to override. */ @@ -99,7 +103,7 @@ opacity: 0; /* When we show/hide this, do it gradually using cubic-bezier timing. */ - transition: opacity cubic-bezier(0.4, 0.0, 0.6, 1) 600ms; + transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms; /* Position the controls container in front of the text container, so that * the text container doesn't interfere with the control buttons. */ @@ -120,7 +124,6 @@ } } - /* Container for controls positioned at the bottom of the video container: * controls button panel and the seek bar. */ .shaka-bottom-controls { @@ -149,8 +152,10 @@ /* This is itself a flex container, with children layed out horizontally. */ display: flex; flex-direction: row; + /* Push children to the right. */ justify-content: flex-end; + /* Center children vertically. */ align-items: center; @@ -203,6 +208,7 @@ justify-content: center; align-items: center; position: absolute; + /* Specify left and top for IE's sake. Otherwise, the container ends up below * and to the right of where it should be. */ top: 0; @@ -215,6 +221,8 @@ left: 0; width: 100%; min-width: 48px; + text-align: center; + position: absolute; /* Set the captions in the middle horizontally by default. */ display: flex; @@ -228,9 +236,6 @@ * overflow content. */ overflow: hidden; - text-align: center; - position: absolute; - /* These are defaults which are overridden by JS or cue styles. */ font-size: 20px; line-height: 1.4; // relative to font size. @@ -243,10 +248,10 @@ } } - /* The buffering spinner. */ .shaka-spinner-container { position: absolute; + .fill-container(); flex-shrink: 1; @@ -263,8 +268,9 @@ for the detailed explanation. */ /* For the padding thing to work, spinner div needs to be an - overlay-parent and spinner svg - an overlay child.*/ + overlay-parent and spinner svg - an overlay child. */ .overlay-parent(); + margin: 0; box-sizing: border-box; padding: @spinner-size-percentage / 2; diff --git a/ui/less/other_elements.less b/ui/less/other_elements.less index e1633f026a..6b60ee5ede 100644 --- a/ui/less/other_elements.less +++ b/ui/less/other_elements.less @@ -29,6 +29,7 @@ /* Make the element grow to take up the remaining space. */ flex-grow: 1; - /* Margins don't shrink. Remove margins in order to be more flexible when shrinking. */ + /* Margins don't shrink. Remove margins in order to be more flexible when + * shrinking. */ margin: 0; } diff --git a/ui/less/overflow_menu.less b/ui/less/overflow_menu.less index ef53656aaf..7e9a2b775e 100644 --- a/ui/less/overflow_menu.less +++ b/ui/less/overflow_menu.less @@ -29,7 +29,7 @@ /* Styles for the menu itself. */ background: white; - box-shadow: 0 1px 9px 0 rgba(0,0,0,0.40); + box-shadow: 0 1px 9px 0 rgba(0, 0, 0, 0.4); border-radius: 2px; max-height: 250px; min-width: 180px; @@ -64,20 +64,21 @@ &:hover { background: rgb(224, 224, 224); } + .shaka-keyboard-navigation &:focus { background: rgb(224, 224, 224); } } - /* These are the elements which contain the material design icons. */ - /* TODO: Pull MD icon details out of JS. */ + /* These are the elements which contain the material design icons. + * TODO: Pull MD icon details out of JS. */ i { /* TODO(b/116651454): eliminate hard-coded offsets */ padding-left: 10px; } - /* If the seekbar is missing, this is positioned lower. */ - /* TODO: Solve with flex layout instead? */ + /* If the seekbar is missing, this is positioned lower. + * TODO: Solve with flex layout instead? */ &.shaka-low-position { /* TODO(b/116651454): eliminate hard-coded offsets */ bottom: 15px; @@ -93,6 +94,7 @@ .shaka-overflow-menu span { text-align: left; position: relative; + /* TODO(b/116651454): eliminate hard-coded offsets */ left: 13px; } @@ -101,6 +103,7 @@ * right of MD icons. */ .shaka-overflow-button-label { position: relative; + /* This is a flex container, whose children flow vertically. */ display: flex; flex-direction: column; @@ -114,12 +117,10 @@ color: rgba(0, 0, 0, 0.54); } -/* These three submenus have somewhat different margins inside them. */ -/* TODO: This is all submenus, but not the top-level menu. Is there a better +/* These three submenus have somewhat different margins inside them. + * TODO: This is all submenus, but not the top-level menu. Is there a better * way to express this? */ -.shaka-resolutions, -.shaka-audio-languages, -.shaka-text-languages { +.shaka-resolutions, .shaka-audio-languages, .shaka-text-languages { span { /* TODO(b/116651454): eliminate hard-coded offsets */ margin-left: 54px; diff --git a/ui/less/range_elements.less b/ui/less/range_elements.less index 64b823e179..27635df64c 100644 --- a/ui/less/range_elements.less +++ b/ui/less/range_elements.less @@ -51,8 +51,10 @@ /* Vertical margins to occupy the same space as the thumb. */ margin: (@thumb-size - @track-height)/2 6px; + /* Smaller height to contain the background for the virtual track. */ height: @track-height; + /* Rounded ends on the virtual track. */ border-radius: @track-height; @@ -123,6 +125,7 @@ &::-webkit-slider-runnable-track { .track(); } + &::-webkit-slider-thumb { .thumb(); } @@ -131,6 +134,7 @@ &::-moz-range-track { .track(); } + &::-moz-range-thumb { .thumb(); } @@ -139,6 +143,7 @@ &::-ms-track { .track(); } + &::-ms-thumb { .thumb(); } @@ -152,6 +157,7 @@ &::-ms-fill-lower { .hidden(); } + &::-ms-fill-upper { .hidden(); } diff --git a/ui/less/spinner.less b/ui/less/spinner.less index 5081c0d5b1..56bd836d81 100644 --- a/ui/less/spinner.less +++ b/ui/less/spinner.less @@ -71,10 +71,12 @@ stroke-dasharray: 1, 200; stroke-dashoffset: 0; } + 50% { stroke-dasharray: 89, 200; stroke-dashoffset: -35px; } + 100% { stroke-dasharray: 89, 200; stroke-dashoffset: -124px;