diff --git a/web/packages/demo/fonts/S6uyw4BMUTPHjx4wXg.woff2 b/web/packages/demo/fonts/S6uyw4BMUTPHjx4wXg.woff2 new file mode 100644 index 000000000..ff60934dd Binary files /dev/null and b/web/packages/demo/fonts/S6uyw4BMUTPHjx4wXg.woff2 differ diff --git a/web/packages/demo/fonts/S6uyw4BMUTPHjxAwXjeu.woff2 b/web/packages/demo/fonts/S6uyw4BMUTPHjxAwXjeu.woff2 new file mode 100644 index 000000000..edb9fa6f4 Binary files /dev/null and b/web/packages/demo/fonts/S6uyw4BMUTPHjxAwXjeu.woff2 differ diff --git a/web/packages/demo/src/common.css b/web/packages/demo/src/common.css new file mode 100644 index 000000000..9a0bfb652 --- /dev/null +++ b/web/packages/demo/src/common.css @@ -0,0 +1,163 @@ +:root { + --ruffle-light-blue: #546da3; + --ruffle-blue: #37528c; + --ruffle-dark-blue: #184778; + --ruffle-orange: #ffad33; + --ruffle-dark-orange: #966214; +} + +.notransition, +.notransition *, +.notransition::before, +.notransition::after { + transition: none !important; +} + +body { + background: var(--ruffle-blue); + font: + 14px Arial, + sans-serif; + margin: auto; + color: white; +} + +.header { + position: relative; + background: var(--ruffle-dark-blue); + text-align: center; + padding: 16px 16px 6px; + box-shadow: 0 0 12px #0008; +} + +.header a { + display: inline-block; + line-height: 0; +} + +#version-text { + text-align: center; + font-size: smaller; + opacity: 0.6; +} + +.logo { + width: 100%; + transition: transform 0.2s; +} + +.logo:hover { + transform: scale(104%); +} + +/* Controls */ + +input, +select { + background: var(--ruffle-dark-blue); + color: white; + padding: 6px; + border: none; + border-radius: 4px; +} + +input::placeholder { + opacity: 0.5; + color: white; +} + +button { + padding: 6px 8px; + border: 2px solid var(--ruffle-blue); + border-radius: 8px; + cursor: pointer; + text-align: center; + background: var(--ruffle-light-blue); + color: white; + + /* This gives the text a little more weight without outright bolding it */ + text-shadow: 0 0 0.1px white; +} + +.options { + display: flex; + flex-flow: column; + gap: 20px; +} + +.option { + position: relative; + display: flex; + align-items: center; +} + +.option input, +.option select { + padding: 4px; + position: absolute; + right: 0; +} + +.option label { + display: inline-block; + padding-right: 60px; +} + +/* Checkbox (Based on "Pure CSS Slider Checkboxes": https://codepen.io/Qvcool/pen/bdzVYW) */ + +.option.checkbox input { + width: 40px; + height: 20px; + margin: auto; + cursor: pointer; + z-index: 1; + opacity: 0; +} + +.option.checkbox label::before, +.option.checkbox label::after { + content: ""; + position: absolute; + border-radius: 10px; + top: 0; + bottom: 0; + margin: auto; + transition: + background 0.2s, + right 0.2s; +} + +.option.checkbox label::before { + height: 20px; + width: 40px; + right: 0; + background: gray; +} + +.option.checkbox label::after { + height: 18px; + width: 18px; + right: 21px; + background: silver; +} + +.option.checkbox input:checked + label::before { + background: var(--ruffle-dark-orange); +} + +.option.checkbox input:checked + label::after { + background: var(--ruffle-orange); + right: 1px; +} + +/* Number input */ + +.option.number-input input { + width: 60px; + height: 20px; + margin: auto; +} + +.hidden { + display: none !important; +} diff --git a/web/packages/demo/src/index.css b/web/packages/demo/src/index.css index dd9bf673d..e11da61a0 100644 --- a/web/packages/demo/src/index.css +++ b/web/packages/demo/src/index.css @@ -1,35 +1,100 @@ -:root { - --ruffle-blue: #37528c; - --ruffle-orange: #ffad33; - --splash-screen-background: #31497d; -} - body { position: absolute; inset: 0; padding: 0; margin: 0; font-family: Lato, sans-serif; + font-size: 100%; display: flex; - flex-flow: column; + flex-direction: column; background: black; } +#nav { + width: 100%; + background: var(--ruffle-blue); + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + color: white; + padding: 8px 16px; + box-sizing: border-box; +} + +#nav > * { + display: flex; + gap: 20px; +} + +.logo { + height: 32px; + flex: 0 0 content; +} + +.select-container > div { + display: flex; + align-items: center; + gap: 4px; +} + +#web-url { + width: min(40vw, 500px); +} + +#local-file { + display: none; +} + +#local-file-name, +#sample-swfs-label, +#local-file-static-label { + font-size: smaller; +} + +#local-file-static-label { + display: none; +} + +#toggle-info, +#reload-swf { + cursor: pointer; +} + +#author { + color: var(--ruffle-orange); +} + #main { position: relative; flex: 1; + display: flex; + flex-direction: row; +} + +#author-container { + font-size: smaller; +} + +#player-container { + overflow-y: hidden; + flex-grow: 1; +} + +#player-container > * { + position: relative; + width: 100%; + height: 100%; } #overlay { - position: absolute; - inset: 0; - z-index: 1; pointer-events: none; border: 8px dashed var(--ruffle-orange); border-radius: 30px; opacity: 0; transition: opacity 0.3s ease-in; - margin: 10px 5px; + box-sizing: border-box; + z-index: 1; } #overlay.drag { @@ -37,163 +102,47 @@ body { transition-timing-function: ease-out; } -#player { - position: absolute; - inset: 0; - width: auto; - height: auto; - margin: 10px 0; +#overlay:not([hidden]) ~ #player { + bottom: 100%; } -#nav { - width: 100%; - background: var(--ruffle-blue); - box-shadow: 0 3px 6px 5px var(--ruffle-blue); - display: flex; - align-items: center; - justify-content: space-around; - color: white; - padding: 10px 0 5px; - margin-bottom: 5px; -} - -#title { - transition: opacity 0.5s; -} - -#title:hover { - opacity: 0.5; -} - -#title img { - height: 32px; -} - -#file-picker select, -#file-picker input, -#author { - margin-left: 5px; -} - -#local-file-container, -#sample-swfs-container { - display: inline-block; - vertical-align: middle; -} - -#local-file { - width: 0; - opacity: 0; - position: absolute; -} - -#local-file-label { - color: var(--ruffle-blue); - padding: 3px 10px; - margin: 5px 2px; - cursor: pointer; - border-radius: 50px; - background-color: white; -} - -#local-file-name { - min-width: 150px; - display: inline-block; - font-size: smaller; -} - -#sample-swfs { - background-color: white; - color: var(--ruffle-blue); - border: 1px solid white; - border-radius: 5px; -} - -#author-container { - font-size: small; -} - -#author { - color: var(--ruffle-orange); -} - -.hidden { - display: none !important; -} - -.modal { - display: none; - position: fixed; - z-index: 1; - left: 0; - top: 0; - width: 100%; - height: 100%; -} - -.modal-content { - background-color: var(--ruffle-blue); - margin: 15vh auto; - padding: 20px; - border: 2px solid white; +#info-container { width: 300px; - height: 270px; - overflow: auto; + background-color: var(--ruffle-blue); + padding: 4px 16px; + flex-direction: column; + gap: 8px; + box-sizing: border-box; } -.close { - color: #aaa; +#info-container span:first-child { + text-shadow: 0 0 1px white; +} + +#info-container span:first-child::after { + content: ":"; +} + +#info-container span:last-child { float: right; - font-size: 28px; - font-weight: bold; - cursor: pointer; } -#open-modal, -#reload-swf { - vertical-align: middle; - cursor: pointer; +/* TODO: Make metadata element IDs kebab-case, and convert back and forth + between that and the camelCase of metadata JS object keys. */ +/* stylelint-disable-next-line selector-id-pattern */ +#backgroundColor { + width: 1em; + height: 1em; + border: 2px solid var(--ruffle-dark-blue); + background-color: white; } -#metadata { - margin: 0 auto; -} - -#metadata td { - padding: 2px 1px; - border: 1px solid #ddd; - color: var(--ruffle-orange); -} - -#metadata tr td:nth-child(1) { - font-weight: bold; - padding: 0 10px; -} - -@media only screen and (width <= 800px) { - #local-file-container, - #sample-swfs-container { +@media only screen and (width <= 1120px) { + #local-file-static-label { display: block; } - #local-file-container { - margin-bottom: 10px; - } -} - -@media only screen and (width <= 600px) { - #local-file-static-label, - #sample-swfs-label { - display: block; - margin-bottom: 5px; - } - - #author-container { - font-size: 12px; - text-align: center; - } - - #nav { + .select-container { flex-flow: column; } } diff --git a/web/packages/demo/src/index.ts b/web/packages/demo/src/index.ts index e5fe64c07..248503359 100644 --- a/web/packages/demo/src/index.ts +++ b/web/packages/demo/src/index.ts @@ -1,3 +1,5 @@ +import "./lato.css"; +import "./common.css"; import "./index.css"; declare global { @@ -25,7 +27,7 @@ const ruffle = (window.RufflePlayer as PublicAPI).newest()!; let player: RufflePlayer | null; -const main = document.getElementById("main")!; +const main = document.getElementById("player-container")!; const overlay = document.getElementById("overlay")!; const authorContainer = document.getElementById("author-container")!; const author = document.getElementById("author"); @@ -37,14 +39,13 @@ const sampleFileInput = ( document.getElementById("sample-swfs") ); const localFileName = document.getElementById("local-file-name")!; -const closeModal = document.getElementById("close-modal")!; -const openModal = document.getElementById("open-modal")!; +const toggleInfo = document.getElementById("toggle-info")!; const reloadSwf = document.getElementById("reload-swf")!; -const metadataModal = document.getElementById("metadata-modal")!; +const infoContainer = document.getElementById("info-container")!; // prettier-ignore const optionGroups = { - "Animation": document.getElementById("anim-optgroup")!, - "Game": document.getElementById("games-optgroup")!, + "Animation": document.getElementById("anim-optgroup")!, + "Game": document.getElementById("games-optgroup")!, }; // This is the base config used by the demo player (except for specific SWF files @@ -57,7 +58,7 @@ const baseDemoConfig = { forceAlign: true, }; -const swfToFlashVersion: Record = { +const swfToFlashVersion: { [key: number]: string } = { 1: "1", 2: "2", 3: "3", @@ -122,37 +123,36 @@ function unload() { document.querySelectorAll("span.metadata").forEach((el) => { el.textContent = "Loading"; }); - (document.getElementById("backgroundColor")).value = - "#FFFFFF"; + document.getElementById("backgroundColor")!.style.backgroundColor = + "white"; } } -function load(options: DataLoadOptions | URLLoadOptions) { +function load(options: string | DataLoadOptions | URLLoadOptions) { unload(); player = ruffle.createPlayer(); player.id = "player"; main.append(player); player.load(options, false); - player.addEventListener("loadedmetadata", function () { + player.addEventListener("loadedmetadata", () => { if (player?.metadata) { for (const [key, value] of Object.entries(player.metadata)) { const metadataElement = document.getElementById(key); if (metadataElement) { switch (key) { case "backgroundColor": - (metadataElement).value = - value ?? "#FFFFFF"; + metadataElement.style.backgroundColor = + value ?? "white"; break; case "uncompressedLength": metadataElement.textContent = `${value >> 10}Kb`; break; + // @ts-expect-error This intentionally falls through to the default case case "swfVersion": document.getElementById( "flashVersion", - )!.textContent = - swfToFlashVersion[value] ?? "Unknown"; - metadataElement.textContent = value; - break; + )!.textContent = swfToFlashVersion[value] ?? null; + // falls through and executes the default case as well default: metadataElement.textContent = value; break; @@ -177,7 +177,7 @@ function hideSample() { author.href = ""; } -async function loadFile(file: File) { +async function loadFile(file: File | undefined) { if (!file) { return; } @@ -204,9 +204,14 @@ function loadSample() { } } -localFileInput.addEventListener("change", (_event) => { - if (localFileInput.files && localFileInput.files[0]) { - loadFile(localFileInput.files[0]); +localFileInput.addEventListener("change", (event) => { + const eventTarget = event.target as HTMLInputElement; + if ( + eventTarget?.files && + eventTarget?.files.length > 0 && + eventTarget.files[0] + ) { + loadFile(eventTarget.files[0]); } }); @@ -230,8 +235,8 @@ main.addEventListener("drop", (event) => { event.stopPropagation(); event.preventDefault(); overlay.classList.remove("drag"); - localFileInput.files = event.dataTransfer?.files ?? null; - if (event.dataTransfer?.files[0]) { + if (event.dataTransfer) { + localFileInput.files = event.dataTransfer.files; loadFile(event.dataTransfer.files[0]); } }); @@ -249,18 +254,18 @@ localFileInput.addEventListener("drop", (event) => { event.stopPropagation(); event.preventDefault(); overlay.classList.remove("drag"); - localFileInput.files = event.dataTransfer?.files ?? null; - if (event.dataTransfer?.files[0]) { + if (event.dataTransfer) { + localFileInput.files = event.dataTransfer.files; loadFile(event.dataTransfer.files[0]); } }); -closeModal.addEventListener("click", () => { - metadataModal.style.display = "none"; -}); - -openModal.addEventListener("click", () => { - metadataModal.style.display = "block"; +toggleInfo.addEventListener("click", () => { + if (infoContainer.style.display === "none") { + infoContainer.style.display = "flex"; + } else { + infoContainer.style.display = "none"; + } }); reloadSwf.addEventListener("click", () => { @@ -281,15 +286,9 @@ window.addEventListener("load", () => { ) { localFileInput.removeAttribute("accept"); } - overlay.classList.remove("hidden"); + overlay.removeAttribute("hidden"); }); -window.onclick = (event) => { - if (event.target === metadataModal) { - metadataModal.style.display = "none"; - } -}; - (async () => { const response = await fetch("swfs.json"); diff --git a/web/packages/demo/src/lato.css b/web/packages/demo/src/lato.css new file mode 100644 index 000000000..3e38a760e --- /dev/null +++ b/web/packages/demo/src/lato.css @@ -0,0 +1,20 @@ +/* latin-ext */ +@font-face { + font-family: Lato; + font-style: normal; + font-weight: 400; + src: url("../fonts/S6uyw4BMUTPHjxAwXjeu.woff2") format("woff2"); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, + U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: Lato; + font-style: normal; + font-weight: 400; + src: url("../fonts/S6uyw4BMUTPHjx4wXg.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, + U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} diff --git a/web/packages/demo/webpack.config.js b/web/packages/demo/webpack.config.js index db4c0dddb..e6ff1ed43 100644 --- a/web/packages/demo/webpack.config.js +++ b/web/packages/demo/webpack.config.js @@ -41,6 +41,10 @@ module.exports = (_env, _argv) => { patterns: [ { from: path.resolve(__dirname, "www/index.html") }, { from: path.resolve(__dirname, "www/logo-anim.swf") }, + { from: path.resolve(__dirname, "www/icon32.png") }, + { from: path.resolve(__dirname, "www/icon48.png") }, + { from: path.resolve(__dirname, "www/icon180.png") }, + { from: path.resolve(__dirname, "www/logo.svg") }, { from: "swfs.json", noErrorOnMissing: true }, { from: "LICENSE*" }, { from: "README.md" }, diff --git a/web/packages/demo/www/icon180.png b/web/packages/demo/www/icon180.png new file mode 100644 index 000000000..698fa8aa5 Binary files /dev/null and b/web/packages/demo/www/icon180.png differ diff --git a/web/packages/demo/www/icon32.png b/web/packages/demo/www/icon32.png new file mode 100644 index 000000000..91a936109 Binary files /dev/null and b/web/packages/demo/www/icon32.png differ diff --git a/web/packages/demo/www/icon48.png b/web/packages/demo/www/icon48.png new file mode 100644 index 000000000..4264a2d2b Binary files /dev/null and b/web/packages/demo/www/icon48.png differ diff --git a/web/packages/demo/www/index.html b/web/packages/demo/www/index.html index a95915a58..1ebf131e6 100644 --- a/web/packages/demo/www/index.html +++ b/web/packages/demo/www/index.html @@ -1,95 +1,89 @@ - - - - - Ruffle Web Demo - - - - - - -