diff --git a/src/javascript/modernizr.js b/src/javascript/modernizr.js new file mode 100644 index 0000000..d2831b9 --- /dev/null +++ b/src/javascript/modernizr.js @@ -0,0 +1,804 @@ +/*! + * modernizr v3.6.0 + * Build https://modernizr.com/download?-backdropfilter-testallprops-dontmin + * + * Copyright (c) + * Faruk Ates + * Paul Irish + * Alex Sexton + * Ryan Seddon + * Patrick Kettner + * Stu Cox + * Richard Herrera + + * MIT License + */ + +/* + * Modernizr tests which native CSS3 and HTML5 features are available in the + * current UA and makes the results available to you in two ways: as properties on + * a global `Modernizr` object, and as classes on the `` element. This + * information allows you to progressively enhance your pages with a granular level + * of control over the experience. +*/ + +;(function(window, document, undefined){ + var tests = []; + + + /** + * + * ModernizrProto is the constructor for Modernizr + * + * @class + * @access public + */ + + var ModernizrProto = { + // The current version, dummy + _version: '3.6.0', + + // Any settings that don't work as separate modules + // can go in here as configuration. + _config: { + 'classPrefix': '', + 'enableClasses': true, + 'enableJSClass': true, + 'usePrefixes': true + }, + + // Queue of tests + _q: [], + + // Stub these for people who are listening + on: function(test, cb) { + // I don't really think people should do this, but we can + // safe guard it a bit. + // -- NOTE:: this gets WAY overridden in src/addTest for actual async tests. + // This is in case people listen to synchronous tests. I would leave it out, + // but the code to *disallow* sync tests in the real version of this + // function is actually larger than this. + var self = this; + setTimeout(function() { + cb(self[test]); + }, 0); + }, + + addTest: function(name, fn, options) { + tests.push({name: name, fn: fn, options: options}); + }, + + addAsyncTest: function(fn) { + tests.push({name: null, fn: fn}); + } + }; + + + + // Fake some of Object.create so we can force non test results to be non "own" properties. + var Modernizr = function() {}; + Modernizr.prototype = ModernizrProto; + + // Leak modernizr globally when you `require` it rather than force it here. + // Overwrite name so constructor name is nicer :D + Modernizr = new Modernizr(); + + + + /** + * is returns a boolean if the typeof an obj is exactly type. + * + * @access private + * @function is + * @param {*} obj - A thing we want to check the type of + * @param {string} type - A string to compare the typeof against + * @returns {boolean} + */ + + function is(obj, type) { + return typeof obj === type; + } + ; + + var classes = []; + + + /** + * Run through all tests and detect their support in the current UA. + * + * @access private + */ + + function testRunner() { + var featureNames; + var feature; + var aliasIdx; + var result; + var nameIdx; + var featureName; + var featureNameSplit; + + for (var featureIdx in tests) { + if (tests.hasOwnProperty(featureIdx)) { + featureNames = []; + feature = tests[featureIdx]; + // run the test, throw the return value into the Modernizr, + // then based on that boolean, define an appropriate className + // and push it into an array of classes we'll join later. + // + // If there is no name, it's an 'async' test that is run, + // but not directly added to the object. That should + // be done with a post-run addTest call. + if (feature.name) { + featureNames.push(feature.name.toLowerCase()); + + if (feature.options && feature.options.aliases && feature.options.aliases.length) { + // Add all the aliases into the names list + for (aliasIdx = 0; aliasIdx < feature.options.aliases.length; aliasIdx++) { + featureNames.push(feature.options.aliases[aliasIdx].toLowerCase()); + } + } + } + + // Run the test, or use the raw value if it's not a function + result = is(feature.fn, 'function') ? feature.fn() : feature.fn; + + + // Set each of the names on the Modernizr object + for (nameIdx = 0; nameIdx < featureNames.length; nameIdx++) { + featureName = featureNames[nameIdx]; + // Support dot properties as sub tests. We don't do checking to make sure + // that the implied parent tests have been added. You must call them in + // order (either in the test, or make the parent test a dependency). + // + // Cap it to TWO to make the logic simple and because who needs that kind of subtesting + // hashtag famous last words + featureNameSplit = featureName.split('.'); + + if (featureNameSplit.length === 1) { + Modernizr[featureNameSplit[0]] = result; + } else { + // cast to a Boolean, if not one already + if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) { + Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]); + } + + Modernizr[featureNameSplit[0]][featureNameSplit[1]] = result; + } + + classes.push((result ? '' : 'no-') + featureNameSplit.join('-')); + } + } + } + } + ; + + /** + * If the browsers follow the spec, then they would expose vendor-specific styles as: + * elem.style.WebkitBorderRadius + * instead of something like the following (which is technically incorrect): + * elem.style.webkitBorderRadius + + * WebKit ghosts their properties in lowercase but Opera & Moz do not. + * Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+ + * erik.eae.net/archives/2008/03/10/21.48.10/ + + * More here: github.com/Modernizr/Modernizr/issues/issue/21 + * + * @access private + * @returns {string} The string representing the vendor-specific style properties + */ + + var omPrefixes = 'Moz O ms Webkit'; + + + var cssomPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.split(' ') : []); + ModernizrProto._cssomPrefixes = cssomPrefixes; + + + /** + * List of JavaScript DOM values used for tests + * + * @memberof Modernizr + * @name Modernizr._domPrefixes + * @optionName Modernizr._domPrefixes + * @optionProp domPrefixes + * @access public + * @example + * + * Modernizr._domPrefixes is exactly the same as [_prefixes](#modernizr-_prefixes), but rather + * than kebab-case properties, all properties are their Capitalized variant + * + * ```js + * Modernizr._domPrefixes === [ "Moz", "O", "ms", "Webkit" ]; + * ``` + */ + + var domPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.toLowerCase().split(' ') : []); + ModernizrProto._domPrefixes = domPrefixes; + + + /** + * fnBind is a super small [bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) polyfill. + * + * @access private + * @function fnBind + * @param {function} fn - a function you want to change `this` reference to + * @param {object} that - the `this` you want to call the function with + * @returns {function} The wrapped version of the supplied function + */ + + function fnBind(fn, that) { + return function() { + return fn.apply(that, arguments); + }; + } + + ; + + /** + * testDOMProps is a generic DOM property test; if a browser supports + * a certain property, it won't return undefined for it. + * + * @access private + * @function testDOMProps + * @param {array.} props - An array of properties to test for + * @param {object} obj - An object or Element you want to use to test the parameters again + * @param {boolean|object} elem - An Element to bind the property lookup again. Use `false` to prevent the check + * @returns {false|*} returns false if the prop is unsupported, otherwise the value that is supported + */ + function testDOMProps(props, obj, elem) { + var item; + + for (var i in props) { + if (props[i] in obj) { + + // return the property name as a string + if (elem === false) { + return props[i]; + } + + item = obj[props[i]]; + + // let's bind a function + if (is(item, 'function')) { + // bind to obj unless overriden + return fnBind(item, elem || obj); + } + + // return the unbound function or obj or value + return item; + } + } + return false; + } + + ; + + + /** + * contains checks to see if a string contains another string + * + * @access private + * @function contains + * @param {string} str - The string we want to check for substrings + * @param {string} substr - The substring we want to search the first string for + * @returns {boolean} + */ + + function contains(str, substr) { + return !!~('' + str).indexOf(substr); + } + + ; + + /** + * cssToDOM takes a kebab-case string and converts it to camelCase + * e.g. box-sizing -> boxSizing + * + * @access private + * @function cssToDOM + * @param {string} name - String name of kebab-case prop we want to convert + * @returns {string} The camelCase version of the supplied name + */ + + function cssToDOM(name) { + return name.replace(/([a-z])-([a-z])/g, function(str, m1, m2) { + return m1 + m2.toUpperCase(); + }).replace(/^-/, ''); + } + ; + + /** + * domToCSS takes a camelCase string and converts it to kebab-case + * e.g. boxSizing -> box-sizing + * + * @access private + * @function domToCSS + * @param {string} name - String name of camelCase prop we want to convert + * @returns {string} The kebab-case version of the supplied name + */ + + function domToCSS(name) { + return name.replace(/([A-Z])/g, function(str, m1) { + return '-' + m1.toLowerCase(); + }).replace(/^ms-/, '-ms-'); + } + ; + + + /** + * wrapper around getComputedStyle, to fix issues with Firefox returning null when + * called inside of a hidden iframe + * + * @access private + * @function computedStyle + * @param {HTMLElement|SVGElement} - The element we want to find the computed styles of + * @param {string|null} [pseudoSelector]- An optional pseudo element selector (e.g. :before), of null if none + * @returns {CSSStyleDeclaration} + */ + + function computedStyle(elem, pseudo, prop) { + var result; + + if ('getComputedStyle' in window) { + result = getComputedStyle.call(window, elem, pseudo); + var console = window.console; + + if (result !== null) { + if (prop) { + result = result.getPropertyValue(prop); + } + } else { + if (console) { + var method = console.error ? 'error' : 'log'; + console[method].call(console, 'getComputedStyle returning null, its possible modernizr test results are inaccurate'); + } + } + } else { + result = !pseudo && elem.currentStyle && elem.currentStyle[prop]; + } + + return result; + } + + ; + + /** + * docElement is a convenience wrapper to grab the root element of the document + * + * @access private + * @returns {HTMLElement|SVGElement} The root element of the document + */ + + var docElement = document.documentElement; + + + /** + * A convenience helper to check if the document we are running in is an SVG document + * + * @access private + * @returns {boolean} + */ + + var isSVG = docElement.nodeName.toLowerCase() === 'svg'; + + + /** + * createElement is a convenience wrapper around document.createElement. Since we + * use createElement all over the place, this allows for (slightly) smaller code + * as well as abstracting away issues with creating elements in contexts other than + * HTML documents (e.g. SVG documents). + * + * @access private + * @function createElement + * @returns {HTMLElement|SVGElement} An HTML or SVG element + */ + + function createElement() { + if (typeof document.createElement !== 'function') { + // This is the case in IE7, where the type of createElement is "object". + // For this reason, we cannot call apply() as Object is not a Function. + return document.createElement(arguments[0]); + } else if (isSVG) { + return document.createElementNS.call(document, 'http://www.w3.org/2000/svg', arguments[0]); + } else { + return document.createElement.apply(document, arguments); + } + } + + ; + + /** + * Create our "modernizr" element that we do most feature tests on. + * + * @access private + */ + + var modElem = { + elem: createElement('modernizr') + }; + + // Clean up this element + Modernizr._q.push(function() { + delete modElem.elem; + }); + + + + var mStyle = { + style: modElem.elem.style + }; + + // kill ref for gc, must happen before mod.elem is removed, so we unshift on to + // the front of the queue. + Modernizr._q.unshift(function() { + delete mStyle.style; + }); + + + + /** + * getBody returns the body of a document, or an element that can stand in for + * the body if a real body does not exist + * + * @access private + * @function getBody + * @returns {HTMLElement|SVGElement} Returns the real body of a document, or an + * artificially created element that stands in for the body + */ + + function getBody() { + // After page load injecting a fake body doesn't work so check if body exists + var body = document.body; + + if (!body) { + // Can't use the real body create a fake one. + body = createElement(isSVG ? 'svg' : 'body'); + body.fake = true; + } + + return body; + } + + ; + + /** + * injectElementWithStyles injects an element with style element and some CSS rules + * + * @access private + * @function injectElementWithStyles + * @param {string} rule - String representing a css rule + * @param {function} callback - A function that is used to test the injected element + * @param {number} [nodes] - An integer representing the number of additional nodes you want injected + * @param {string[]} [testnames] - An array of strings that are used as ids for the additional nodes + * @returns {boolean} + */ + + function injectElementWithStyles(rule, callback, nodes, testnames) { + var mod = 'modernizr'; + var style; + var ret; + var node; + var docOverflow; + var div = createElement('div'); + var body = getBody(); + + if (parseInt(nodes, 10)) { + // In order not to give false positives we create a node for each test + // This also allows the method to scale for unspecified uses + while (nodes--) { + node = createElement('div'); + node.id = testnames ? testnames[nodes] : mod + (nodes + 1); + div.appendChild(node); + } + } + + style = createElement('style'); + style.type = 'text/css'; + style.id = 's' + mod; + + // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody. + // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270 + (!body.fake ? div : body).appendChild(style); + body.appendChild(div); + + if (style.styleSheet) { + style.styleSheet.cssText = rule; + } else { + style.appendChild(document.createTextNode(rule)); + } + div.id = mod; + + if (body.fake) { + //avoid crashing IE8, if background image is used + body.style.background = ''; + //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible + body.style.overflow = 'hidden'; + docOverflow = docElement.style.overflow; + docElement.style.overflow = 'hidden'; + docElement.appendChild(body); + } + + ret = callback(div, rule); + // If this is done after page load we don't want to remove the body so check if body exists + if (body.fake) { + body.parentNode.removeChild(body); + docElement.style.overflow = docOverflow; + // Trigger layout so kinetic scrolling isn't disabled in iOS6+ + // eslint-disable-next-line + docElement.offsetHeight; + } else { + div.parentNode.removeChild(div); + } + + return !!ret; + + } + + ; + + /** + * nativeTestProps allows for us to use native feature detection functionality if available. + * some prefixed form, or false, in the case of an unsupported rule + * + * @access private + * @function nativeTestProps + * @param {array} props - An array of property names + * @param {string} value - A string representing the value we want to check via @supports + * @returns {boolean|undefined} A boolean when @supports exists, undefined otherwise + */ + + // Accepts a list of property names and a single value + // Returns `undefined` if native detection not available + function nativeTestProps(props, value) { + var i = props.length; + // Start with the JS API: http://www.w3.org/TR/css3-conditional/#the-css-interface + if ('CSS' in window && 'supports' in window.CSS) { + // Try every prefixed variant of the property + while (i--) { + if (window.CSS.supports(domToCSS(props[i]), value)) { + return true; + } + } + return false; + } + // Otherwise fall back to at-rule (for Opera 12.x) + else if ('CSSSupportsRule' in window) { + // Build a condition string for every prefixed variant + var conditionText = []; + while (i--) { + conditionText.push('(' + domToCSS(props[i]) + ':' + value + ')'); + } + conditionText = conditionText.join(' or '); + return injectElementWithStyles('@supports (' + conditionText + ') { #modernizr { position: absolute; } }', function(node) { + return computedStyle(node, null, 'position') == 'absolute'; + }); + } + return undefined; + } + ; + + // testProps is a generic CSS / DOM property test. + + // In testing support for a given CSS property, it's legit to test: + // `elem.style[styleName] !== undefined` + // If the property is supported it will return an empty string, + // if unsupported it will return undefined. + + // We'll take advantage of this quick test and skip setting a style + // on our modernizr element, but instead just testing undefined vs + // empty string. + + // Property names can be provided in either camelCase or kebab-case. + + function testProps(props, prefixed, value, skipValueTest) { + skipValueTest = is(skipValueTest, 'undefined') ? false : skipValueTest; + + // Try native detect first + if (!is(value, 'undefined')) { + var result = nativeTestProps(props, value); + if (!is(result, 'undefined')) { + return result; + } + } + + // Otherwise do it properly + var afterInit, i, propsLength, prop, before; + + // If we don't have a style element, that means we're running async or after + // the core tests, so we'll need to create our own elements to use + + // inside of an SVG element, in certain browsers, the `style` element is only + // defined for valid tags. Therefore, if `modernizr` does not have one, we + // fall back to a less used element and hope for the best. + // for strict XHTML browsers the hardly used samp element is used + var elems = ['modernizr', 'tspan', 'samp']; + while (!mStyle.style && elems.length) { + afterInit = true; + mStyle.modElem = createElement(elems.shift()); + mStyle.style = mStyle.modElem.style; + } + + // Delete the objects if we created them. + function cleanElems() { + if (afterInit) { + delete mStyle.style; + delete mStyle.modElem; + } + } + + propsLength = props.length; + for (i = 0; i < propsLength; i++) { + prop = props[i]; + before = mStyle.style[prop]; + + if (contains(prop, '-')) { + prop = cssToDOM(prop); + } + + if (mStyle.style[prop] !== undefined) { + + // If value to test has been passed in, do a set-and-check test. + // 0 (integer) is a valid property value, so check that `value` isn't + // undefined, rather than just checking it's truthy. + if (!skipValueTest && !is(value, 'undefined')) { + + // Needs a try catch block because of old IE. This is slow, but will + // be avoided in most cases because `skipValueTest` will be used. + try { + mStyle.style[prop] = value; + } catch (e) {} + + // If the property value has changed, we assume the value used is + // supported. If `value` is empty string, it'll fail here (because + // it hasn't changed), which matches how browsers have implemented + // CSS.supports() + if (mStyle.style[prop] != before) { + cleanElems(); + return prefixed == 'pfx' ? prop : true; + } + } + // Otherwise just return true, or the property name if this is a + // `prefixed()` call + else { + cleanElems(); + return prefixed == 'pfx' ? prop : true; + } + } + } + cleanElems(); + return false; + } + + ; + + /** + * testPropsAll tests a list of DOM properties we want to check against. + * We specify literally ALL possible (known and/or likely) properties on + * the element including the non-vendor prefixed one, for forward- + * compatibility. + * + * @access private + * @function testPropsAll + * @param {string} prop - A string of the property to test for + * @param {string|object} [prefixed] - An object to check the prefixed properties on. Use a string to skip + * @param {HTMLElement|SVGElement} [elem] - An element used to test the property and value against + * @param {string} [value] - A string of a css value + * @param {boolean} [skipValueTest] - An boolean representing if you want to test if value sticks when set + * @returns {false|string} returns the string version of the property, or false if it is unsupported + */ + function testPropsAll(prop, prefixed, elem, value, skipValueTest) { + + var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), + props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); + + // did they call .prefixed('boxSizing') or are we just testing a prop? + if (is(prefixed, 'string') || is(prefixed, 'undefined')) { + return testProps(props, prefixed, value, skipValueTest); + + // otherwise, they called .prefixed('requestAnimationFrame', window[, elem]) + } else { + props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); + return testDOMProps(props, prefixed, elem); + } + } + + // Modernizr.testAllProps() investigates whether a given style property, + // or any of its vendor-prefixed variants, is recognized + // + // Note that the property names must be provided in the camelCase variant. + // Modernizr.testAllProps('boxSizing') + ModernizrProto.testAllProps = testPropsAll; + + + + /** + * testAllProps determines whether a given CSS property is supported in the browser + * + * @memberof Modernizr + * @name Modernizr.testAllProps + * @optionName Modernizr.testAllProps() + * @optionProp testAllProps + * @access public + * @function testAllProps + * @param {string} prop - String naming the property to test (either camelCase or kebab-case) + * @param {string} [value] - String of the value to test + * @param {boolean} [skipValueTest=false] - Whether to skip testing that the value is supported when using non-native detection + * @example + * + * testAllProps determines whether a given CSS property, in some prefixed form, + * is supported by the browser. + * + * ```js + * testAllProps('boxSizing') // true + * ``` + * + * It can optionally be given a CSS value in string form to test if a property + * value is valid + * + * ```js + * testAllProps('display', 'block') // true + * testAllProps('display', 'penguin') // false + * ``` + * + * A boolean can be passed as a third parameter to skip the value check when + * native detection (@supports) isn't available. + * + * ```js + * testAllProps('shapeOutside', 'content-box', true); + * ``` + */ + + function testAllProps(prop, value, skipValueTest) { + return testPropsAll(prop, undefined, undefined, value, skipValueTest); + } + ModernizrProto.testAllProps = testAllProps; + + /*! + { + "name": "Backdrop Filter", + "property": "backdropfilter", + "authors": ["Brian Seward"], + "tags": ["css"], + "notes": [ + { + "name": "W3C Editor’s Draft specification", + "href": "https://drafts.fxtf.org/filters-2/#BackdropFilterProperty" + }, + { + "name": "Caniuse for CSS Backdrop Filter", + "href": "http://caniuse.com/#feat=css-backdrop-filter" + }, + { + "name": "WebKit Blog introduction + Demo", + "href": "https://www.webkit.org/blog/3632/introducing-backdrop-filters/" + } + ] + } + !*/ + /* DOC + Detects support for CSS Backdrop Filters, allowing for background blur effects like those introduced in iOS 7. Support for this was added to iOS Safari/WebKit in iOS 9. + */ + + Modernizr.addTest('backdropfilter', testAllProps('backdropFilter')); + + + // Run each test + testRunner(); + + delete ModernizrProto.addTest; + delete ModernizrProto.addAsyncTest; + + // Run the things that are supposed to run after the tests + for (var i = 0; i < Modernizr._q.length; i++) { + Modernizr._q[i](); + } + + // Leak Modernizr namespace + module.exports = Modernizr; + + + ; + + })(window, document); \ No newline at end of file diff --git a/src/sass/ie.sass b/src/sass/ie.sass index e1ad1b4..bf9f2b6 100644 --- a/src/sass/ie.sass +++ b/src/sass/ie.sass @@ -1,2 +1,9 @@ -@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) - // Styles for Internet Explorer should be in here \ No newline at end of file +.penguin-player__player.penguin-player__player-no-css-blur + background: none!important + .penguin-player__player--background + position: absolute + top: 0 + left: 0 + right: 0 + bottom: 0 + z-index: -1 \ No newline at end of file diff --git a/src/sass/player.sass b/src/sass/player.sass index 7548e31..5826442 100644 --- a/src/sass/player.sass +++ b/src/sass/player.sass @@ -261,7 +261,7 @@ min-width: 300px width: #{variables.$expand-width} &+.penguin-player__lyric - margin-left: calc(20px + min(calc(100vw - 20px), max(#{variables.$expand-width}, 300px))) + padding-left: calc(20px + min(calc(100vw - 20px), max(#{variables.$expand-width}, 300px))) &.penguin-player__player-full border-radius: variables.$thumbnail-size / 2 height: variables.$thumbnail-size + 8px + 92px @@ -282,13 +282,13 @@ position: fixed left: 0 right: 0 - bottom: 10px + bottom: 0 + padding: 10px pointer-events: none opacity: 0 - transition: opacity .1s linear, margin-left .2s ease-in-out, bottom .2s ease-in-out + transition: opacity .1s linear, padding-left .2s ease-in-out, bottom .2s ease-in-out margin-left: 0 - z-index: 4 - > * + > *:not(.penguin-player__lyric--background) overflow: hidden white-space: nowrap text-overflow: ellipsis @@ -300,12 +300,25 @@ font-size: 22px .penguin-player__lyric--sub font-size: 16px + .penguin-player__lyric--background + position: fixed + left: 0 + right: 0 + bottom: -10px + height: 60px + transition: bottom .2s ease-in-out + background: linear-gradient(to top, black, transparent) + z-index: -1 + &.penguin-player__lyric-hover .penguin-player__lyric--background + bottom: 0 @media only screen and (max-width: 700px) .penguin-player__player &.penguin-player__player-full, &.penguin-player__player-playlist, &:hover width: calc(100vw - 20px)!important &+.penguin-player__lyric - margin-left: 0!important + padding-left: 0!important + .penguin-player__lyric--background + bottom: -60px!important &:hover &+.penguin-player__lyric bottom: variables.$thumbnail-size + 8px + 20px @@ -314,4 +327,7 @@ bottom: variables.$thumbnail-size + 8px + 92px + 20px &.penguin-player__player-playlist &+.penguin-player__lyric - bottom: calc(#{variables.$thumbnail-size + 8px + 92px + 20px} + #{variables.$playlist-height}) \ No newline at end of file + bottom: calc(#{variables.$thumbnail-size + 8px + 92px + 20px} + #{variables.$playlist-height}) + .penguin-player__lyric + > * + text-align: right!important \ No newline at end of file diff --git a/src/template.pug b/src/template.pug index 738cec5..2dd6f64 100644 --- a/src/template.pug +++ b/src/template.pug @@ -42,4 +42,6 @@ audio.penguin-player__audio .penguin-player__player--playlist(role="list") .penguin-player__lyric h1.penguin-player__lyric--main - h2.penguin-player__lyric--sub \ No newline at end of file + h2.penguin-player__lyric--sub + .penguin-player__lyric--background + //-.penguin-player__lyric--expand-button(role="button") \ No newline at end of file diff --git a/src/typescript/controller.ts b/src/typescript/controller.ts index 21175a1..c180d91 100644 --- a/src/typescript/controller.ts +++ b/src/typescript/controller.ts @@ -6,7 +6,7 @@ import { progressSlider, resetRotate, setThemeColor, volumeSlider } from "./ui"; import { dispatchEvent } from "./modules/event"; import ajax from "./modules/ajax"; -export let songs: Array = []; +export let songs: Song[] = []; export let currentSong: number; let errorAmount = 0, currentUrlReq: AjaxPromise; diff --git a/src/typescript/global.d.ts b/src/typescript/global.d.ts index 3a42fb3..cbc5956 100644 --- a/src/typescript/global.d.ts +++ b/src/typescript/global.d.ts @@ -24,7 +24,7 @@ interface MediaMetadataOptions { title: string artist: string album: string - artwork: Array + artwork: Artwork[] } interface TrialInfo { start: number diff --git a/src/typescript/helper.ts b/src/typescript/helper.ts index f5ceefe..be1e21c 100644 --- a/src/typescript/helper.ts +++ b/src/typescript/helper.ts @@ -1,3 +1,7 @@ +/// #if IE_SUPPORT +const Modernizr = require("../javascript/modernizr"); +/// #endif + export function getOffsetLeft(element: HTMLElement): number { let left=0; while(element) { @@ -23,4 +27,10 @@ export function deepEventHandler(element: HTMLElement, ...args: any[]) { [].forEach.call(element.childNodes, (child: ChildNode) => { deepEventHandler(child, ...args); }); -} \ No newline at end of file +} + +/// #if IE_SUPPORT +export function isBlurSupported() { + return Modernizr.testAllProps("backdropFilter", "blur(5px)"); +} +/// #endif \ No newline at end of file diff --git a/src/typescript/lyric.ts b/src/typescript/lyric.ts index 1ce6d43..ad06a8e 100644 --- a/src/typescript/lyric.ts +++ b/src/typescript/lyric.ts @@ -16,9 +16,9 @@ window.addEventListener("penguininitialized", () => { let lyricReq: AjaxPromise, retryTimeout: any; -let lrc: Array, tLrc: Array, lrcOffset = 0, tLrcOffset = 0, lastMain: string, lastSub: string, lrcTimeout: any, subLrcTimeout: any; +let lrc: LyricLine[], tLrc: LyricLine[], lrcOffset = 0, tLrcOffset = 0, lastMain: string, lastSub: string, lrcTimeout: any, subLrcTimeout: any; -function findLrcPos(lrc: Array, time: number, offset = 0): number { +function findLrcPos(lrc: LyricLine[], time: number, offset = 0): number { if (!lrc) {return;} for (let i = offset;i < lrc.length;i++) { if (lrc[i + 1] == null || lrc[i + 1].time > time * 1000) { diff --git a/src/typescript/modules/color.ts b/src/typescript/modules/color.ts index db270ce..6b6d449 100644 --- a/src/typescript/modules/color.ts +++ b/src/typescript/modules/color.ts @@ -21,7 +21,7 @@ export function isBright(color: Color): boolean { return ((color[0] * 299) + (color[1] * 587) + (color[2] * 114)) / 1000 >= 125; } -export function findHighContrastColor(background: Color, colors: Array): Color { +export function findHighContrastColor(background: Color, colors: Color[]): Color { let contrasts = []; for (let color of colors) { contrasts.push(contrast(background, color)); diff --git a/src/typescript/modules/cookie.ts b/src/typescript/modules/cookie.ts index 2ad6983..fc62e22 100644 --- a/src/typescript/modules/cookie.ts +++ b/src/typescript/modules/cookie.ts @@ -29,7 +29,7 @@ export default { hasItem: function (sKey: string): boolean { return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=")).test(document.cookie); }, - keys: function (): Array { + keys: function (): string[] { var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/); for (var nIdx = 0; nIdx < aKeys.length; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); } return aKeys; diff --git a/src/typescript/player.ts b/src/typescript/player.ts index 1e72a68..5346543 100644 --- a/src/typescript/player.ts +++ b/src/typescript/player.ts @@ -133,7 +133,7 @@ function initialize(list: any) { lazyLoad = new LazyLoad({ container: playlist, elements_selector: ".penguin-player--lazy", - callback_loaded: onPlaylistSongLoaded + callback_loaded: onPlaylistSongLoaded // TODO: Fix performance problem when lazy load is not working }); document.body.appendChild(el); dispatchEvent("penguininitialized"); @@ -180,7 +180,7 @@ function updatePlayPauseButton() { get song(): Song { return songs[currentSong]; }, - get playlist(): Array { + get playlist(): Song[] { return songs; } } diff --git a/src/typescript/ui.ts b/src/typescript/ui.ts index 973985b..fa1817a 100644 --- a/src/typescript/ui.ts +++ b/src/typescript/ui.ts @@ -1,7 +1,4 @@ import Scrollbar from "smooth-scrollbar"; -/// #if IE_SUPPORT -const StackBlur = require("stackblur-canvas"); -/// #endif import { findHighContrastColor } from "./modules/color"; import cookie from "./modules/cookie"; @@ -9,15 +6,13 @@ import { dispatchEvent } from "./modules/event"; import { container as el } from "./player"; import Slider from "./modules/slider"; import { currentSong, getRealDuration, songs, trialInfo } from "./controller"; +import { isBlurSupported } from "./helper"; export let volumeSlider: Slider; export let progressSlider: Slider; window.addEventListener("penguininitialized", () => { let audio: HTMLAudioElement = el.querySelector(".penguin-player__audio"); - /// #if IE_SUPPORT - - /// #endif // Progress bar setup let playerOldState: boolean; progressSlider = new Slider({ @@ -51,6 +46,14 @@ window.addEventListener("penguininitialized", () => { audio.volume = value; cookie.setItem("penguin_volume", value, Infinity); }); + // Lyric overlay setup + window.addEventListener("mousemove", (e) => { + if (e.pageY >= window.innerHeight - 60) { + el.querySelector(".penguin-player__lyric").classList.add("penguin-player__lyric-hover"); + } else { + el.querySelector(".penguin-player__lyric").classList.remove("penguin-player__lyric-hover"); + } + }); Scrollbar.init(el.querySelector(".penguin-player__player--playlist"), { damping: 0.15 }); }); @@ -69,8 +72,11 @@ export function setCircleProgress(progress: number) { } } -export function setThemeColor(color: Color, palette: Array) { +export function setThemeColor(color: Color, palette: Color[]) { let backgroundRgba = `rgba(${color.join(", ")}, 0.5)`; + /// #if IE_SUPPORT + backgroundRgba = `rgba(${color.join(", ")}, ${isBlurSupported() ? 0.5 : 0.8})`; // Increase opacity if blur is not supported + /// #endif let foregroundRgb = `rgb(${findHighContrastColor(color, palette).join(", ")})`; let player: HTMLDivElement = el.querySelector(".penguin-player__player"); player.style.backgroundColor = backgroundRgba; diff --git a/webpack.config.js b/webpack.config.js index e6d7ec4..2533f01 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -64,7 +64,7 @@ module.exports = env => { filename: "[name].js" }, externals: { - "./polyfills": "undefined" + "./polyfills": "''" }, plugins, optimization: mode === "production" ? optimization : undefined,