From 18e89f613220f2ebe9b3fa8dbc26eab9ccb573c3 Mon Sep 17 00:00:00 2001 From: Daniel Jacobs Date: Sat, 26 Aug 2023 15:51:16 -0400 Subject: [PATCH] web: Remove all occurences of innerHTML (except in test files) (#12937) * web: Remove most occurences of innerHTML * web: Use helper methods for shadow template element creation * web: Refactor createErrorFooter function * web: Shadow template code cleanup * web: Add helper function to add CSS rules to shadow template --------- Co-authored-by: nosamu <71368227+n0samu@users.noreply.github.com> --- web/packages/core/src/i18n.ts | 4 +- web/packages/core/src/ruffle-player.ts | 292 ++++++----- web/packages/core/src/shadow-template.ts | 637 +++++++++++++++++------ 3 files changed, 653 insertions(+), 280 deletions(-) diff --git a/web/packages/core/src/i18n.ts b/web/packages/core/src/i18n.ts index 5addaca5a..65e96a2c2 100644 --- a/web/packages/core/src/i18n.ts +++ b/web/packages/core/src/i18n.ts @@ -108,7 +108,7 @@ export function text( export function textAsParagraphs( id: string, args?: Record | null, -): string { +): HTMLDivElement { const result = document.createElement("div"); text(id, args) .split("\n") @@ -117,5 +117,5 @@ export function textAsParagraphs( p.innerText = line; result.appendChild(p); }); - return result.innerHTML; + return result; } diff --git a/web/packages/core/src/ruffle-player.ts b/web/packages/core/src/ruffle-player.ts index 373615818..b8b531843 100644 --- a/web/packages/core/src/ruffle-player.ts +++ b/web/packages/core/src/ruffle-player.ts @@ -1,6 +1,6 @@ import type { Ruffle } from "../dist/ruffle_web"; import { loadRuffle } from "./load-ruffle"; -import { ruffleShadowTemplate } from "./shadow-template"; +import { applyStaticStyles, ruffleShadowTemplate } from "./shadow-template"; import { lookupElement } from "./register-element"; import { DEFAULT_CONFIG } from "./config"; import type { DataLoadOptions, URLLoadOptions } from "./load-options"; @@ -119,6 +119,13 @@ class Point { } } +class PanicLinkInfo { + constructor( + public url: string = "#", + public label: string = text("view-error-details"), + ) {} +} + /** * The ruffle player element that should be inserted onto the page. * @@ -127,6 +134,7 @@ class Point { export class RufflePlayer extends HTMLElement { private readonly shadow: ShadowRoot; private readonly dynamicStyles: HTMLStyleElement; + private readonly staticStyles: HTMLStyleElement; private readonly container: HTMLElement; private readonly playButton: HTMLElement; private readonly unmuteOverlay: HTMLElement; @@ -226,13 +234,16 @@ export class RufflePlayer extends HTMLElement { this.shadow.appendChild(ruffleShadowTemplate.content.cloneNode(true)); this.dynamicStyles = ( - this.shadow.getElementById("dynamic_styles") + this.shadow.getElementById("dynamic-styles") + ); + this.staticStyles = ( + this.shadow.getElementById("static-styles") ); this.container = this.shadow.getElementById("container")!; - this.playButton = this.shadow.getElementById("play_button")!; + this.playButton = this.shadow.getElementById("play-button")!; this.playButton.addEventListener("click", () => this.play()); - this.unmuteOverlay = this.shadow.getElementById("unmute_overlay")!; + this.unmuteOverlay = this.shadow.getElementById("unmute-overlay")!; this.splashScreen = this.shadow.getElementById("splash-screen")!; this.virtualKeyboard = ( this.shadow.getElementById("virtual-keyboard")! @@ -258,11 +269,11 @@ export class RufflePlayer extends HTMLElement { } const unmuteSvg = ( - this.unmuteOverlay.querySelector("#unmute_overlay_svg") + this.unmuteOverlay.querySelector("#unmute-overlay-svg") ); if (unmuteSvg) { const unmuteText = ( - unmuteSvg.querySelector("#unmute_text") + unmuteSvg.querySelector("#unmute-text") ); unmuteText.textContent = text("click-to-unmute"); } @@ -376,6 +387,7 @@ export class RufflePlayer extends HTMLElement { */ connectedCallback(): void { this.updateStyles(); + applyStaticStyles(this.staticStyles); } /** @@ -1403,14 +1415,14 @@ export class RufflePlayer extends HTMLElement { for (const item of this.contextMenuItems()) { if (item === null) { const menuSeparator = document.createElement("li"); - menuSeparator.className = "menu_separator"; + menuSeparator.className = "menu-separator"; const hr = document.createElement("hr"); menuSeparator.appendChild(hr); this.contextMenuElement.appendChild(menuSeparator); } else { const { text, onClick, enabled } = item; const menuItem = document.createElement("li"); - menuItem.className = "menu_item"; + menuItem.className = "menu-item"; menuItem.textContent = text; this.contextMenuElement.appendChild(menuItem); @@ -1676,6 +1688,31 @@ export class RufflePlayer extends HTMLElement { return result; } + /** + * @param footerInfo An array of PanicLinkInfo objects. + * + * @returns The
    element to be used as the error footer + */ + private createErrorFooter( + footerInfo: Array, + ): HTMLUListElement { + const errorFooter = document.createElement("ul"); + for (const linkInfo of footerInfo) { + const footerItem = document.createElement("li"); + const footerLink = document.createElement("a"); + footerLink.href = linkInfo.url; + footerLink.textContent = linkInfo.label; + if (linkInfo.url === "#") { + footerLink.id = "panic-view-details"; + } else { + footerLink.target = "_top"; + } + footerItem.appendChild(footerLink); + errorFooter.appendChild(footerItem); + } + return errorFooter; + } + /** * Panics this specific player, forcefully destroying all resources and displays an error message to the user. * @@ -1752,7 +1789,7 @@ export class RufflePlayer extends HTMLElement { // Create a link to GitHub with all of the error data, if the build is not outdated. // Otherwise, create a link to the downloads section on the Ruffle website. - let actionTag; + let actionLink: PanicLinkInfo; if (!isBuildOutdated) { let url; if (document.location.protocol.includes("extension")) { @@ -1782,13 +1819,12 @@ export class RufflePlayer extends HTMLElement { issueBody = encodeURIComponent(errorArray.join("")); } issueLink += issueBody; - actionTag = `${text( - "report-bug", - )}`; + actionLink = new PanicLinkInfo(issueLink, text("report-bug")); } else { - actionTag = `${text( - "update-ruffle", - )}`; + actionLink = new PanicLinkInfo( + RUFFLE_ORIGIN + "#downloads", + text("update-ruffle"), + ); } // Clears out any existing content (ie play button or canvas) and replaces it with the error screen @@ -1797,144 +1833,129 @@ export class RufflePlayer extends HTMLElement { case PanicError.FileProtocol: // General error: Running on the `file:` protocol errorBody = textAsParagraphs("error-file-protocol"); - errorFooter = ` -
  • ${text( - "ruffle-demo", - )}
  • -
  • ${text( - "ruffle-desktop", - )}
  • - `; + errorFooter = this.createErrorFooter([ + new PanicLinkInfo( + RUFFLE_ORIGIN + "/demo", + text("ruffle-demo"), + ), + new PanicLinkInfo( + RUFFLE_ORIGIN + "#downloads", + text("ruffle-desktop"), + ), + ]); break; case PanicError.JavascriptConfiguration: // General error: Incorrect JavaScript configuration errorBody = textAsParagraphs("error-javascript-config"); - errorFooter = ` -
  • ${text( - "ruffle-wiki", - )}
  • -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([ + new PanicLinkInfo( + "https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#javascript-api", + text("ruffle-wiki"), + ), + new PanicLinkInfo(), + ]); break; case PanicError.WasmNotFound: // Self hosted: Cannot load `.wasm` file - file not found errorBody = textAsParagraphs("error-wasm-not-found"); - errorFooter = ` -
  • ${text( - "ruffle-wiki", - )}
  • -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([ + new PanicLinkInfo( + "https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configuration-options", + text("ruffle-wiki"), + ), + new PanicLinkInfo(), + ]); break; case PanicError.WasmMimeType: // Self hosted: Cannot load `.wasm` file - incorrect MIME type errorBody = textAsParagraphs("error-wasm-mime-type"); - errorFooter = ` -
  • ${text( - "ruffle-wiki", - )}
  • -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([ + new PanicLinkInfo( + "https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configure-webassembly-mime-type", + text("ruffle-wiki"), + ), + new PanicLinkInfo(), + ]); break; case PanicError.SwfFetchError: errorBody = textAsParagraphs("error-swf-fetch"); - errorFooter = ` -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([new PanicLinkInfo()]); break; case PanicError.SwfCors: // Self hosted: Cannot load SWF file - CORS issues errorBody = textAsParagraphs("error-swf-cors"); - errorFooter = ` -
  • ${text( - "ruffle-wiki", - )}
  • -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([ + new PanicLinkInfo( + "https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configure-cors-header", + text("ruffle-wiki"), + ), + new PanicLinkInfo(), + ]); break; case PanicError.WasmCors: // Self hosted: Cannot load `.wasm` file - CORS issues errorBody = textAsParagraphs("error-wasm-cors"); - errorFooter = ` -
  • ${text( - "ruffle-wiki", - )}
  • -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([ + new PanicLinkInfo( + "https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configure-cors-header", + text("ruffle-wiki"), + ), + new PanicLinkInfo(), + ]); break; case PanicError.InvalidWasm: // Self hosted: Cannot load `.wasm` file - incorrect configuration or missing files errorBody = textAsParagraphs("error-wasm-invalid"); - errorFooter = ` -
  • ${text( - "ruffle-wiki", - )}
  • -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([ + new PanicLinkInfo( + "https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#addressing-a-compileerror", + text("ruffle-wiki"), + ), + new PanicLinkInfo(), + ]); break; case PanicError.WasmDownload: // Usually a transient network error or botched deployment errorBody = textAsParagraphs("error-wasm-download"); - errorFooter = ` -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([new PanicLinkInfo()]); break; case PanicError.WasmDisabledMicrosoftEdge: // Self hosted: User has disabled WebAssembly in Microsoft Edge through the // "Enhance your Security on the web" setting. errorBody = textAsParagraphs("error-wasm-disabled-on-edge"); - errorFooter = ` -
  • ${text( - "more-info", - )}
  • -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([ + new PanicLinkInfo( + "https://github.com/ruffle-rs/ruffle/wiki/Frequently-Asked-Questions-For-Users#edge-webassembly-error", + text("more-info"), + ), + new PanicLinkInfo(), + ]); break; case PanicError.JavascriptConflict: // Self hosted: Cannot load `.wasm` file - a native object / function is overriden errorBody = textAsParagraphs("error-javascript-conflict"); if (isBuildOutdated) { - errorBody += textAsParagraphs( - "error-javascript-conflict-outdated", - { buildDate: buildInfo.buildDate }, + errorBody.appendChild( + textAsParagraphs("error-javascript-conflict-outdated", { + buildDate: buildInfo.buildDate, + }), ); } - errorFooter = ` -
  • ${actionTag}
  • -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([ + actionLink, + new PanicLinkInfo(), + ]); break; case PanicError.CSPConflict: // General error: Cannot load `.wasm` file - a native object / function is overriden errorBody = textAsParagraphs("error-csp-conflict"); - errorFooter = ` -
  • ${text( - "ruffle-wiki", - )}
  • -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([ + new PanicLinkInfo( + "https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configure-wasm-csp", + text("ruffle-wiki"), + ), + new PanicLinkInfo(), + ]); break; default: // Unknown error @@ -1942,23 +1963,28 @@ export class RufflePlayer extends HTMLElement { buildDate: buildInfo.buildDate, outdated: String(isBuildOutdated), }); - errorFooter = ` -
  • ${actionTag}
  • -
  • ${text( - "view-error-details", - )}
  • - `; + errorFooter = this.createErrorFooter([ + actionLink, + new PanicLinkInfo(), + ]); break; } - this.container.innerHTML = ` -
    -
    ${text("panic-title")}
    -
    ${errorBody}
    - -
    - `; + const panicDiv = document.createElement("div"); + panicDiv.id = "panic"; + const panicTitle = document.createElement("div"); + panicTitle.id = "panic-title"; + panicTitle.textContent = text("panic-title"); + panicDiv.appendChild(panicTitle); + const panicBody = document.createElement("div"); + panicBody.id = "panic-body"; + panicBody.appendChild(errorBody); + panicDiv.appendChild(panicBody); + const panicFooter = document.createElement("div"); + panicFooter.id = "panic-footer"; + panicFooter.appendChild(errorFooter); + panicDiv.appendChild(panicFooter); + this.container.textContent = ""; + this.container.appendChild(panicDiv); const viewDetails = ( this.container.querySelector("#panic-view-details") ); @@ -1996,10 +2022,10 @@ export class RufflePlayer extends HTMLElement { this.hideSplashScreen(); const div = document.createElement("div"); - div.id = "message_overlay"; + div.id = "message-overlay"; const innerDiv = document.createElement("div"); innerDiv.className = "message"; - innerDiv.innerHTML = textAsParagraphs("message-cant-embed"); + innerDiv.appendChild(textAsParagraphs("message-cant-embed")); const buttonDiv = document.createElement("div"); const link = document.createElement("a"); @@ -2035,13 +2061,19 @@ export class RufflePlayer extends HTMLElement { */ protected displayMessage(message: string): void { const div = document.createElement("div"); - div.id = "message_overlay"; - div.innerHTML = `
    -

    ${message}

    -
    - -
    -
    `; + div.id = "message-overlay"; + const messageDiv = document.createElement("div"); + messageDiv.className = "message"; + const messageP = document.createElement("p"); + messageP.textContent = message; + messageDiv.appendChild(messageP); + const buttonDiv = document.createElement("div"); + const continueButton = document.createElement("button"); + continueButton.id = "continue-btn"; + continueButton.textContent = text("continue"); + buttonDiv.appendChild(continueButton); + messageDiv.appendChild(buttonDiv); + div.appendChild(messageDiv); this.container.prepend(div); (( this.container.querySelector("#continue-btn") diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index e7d5469eb..167fc5901 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -1,11 +1,32 @@ /** - * The shadow template which is used to fill the actual Ruffle player element - * on the page. + * Insert all rules from array in the style sheet. + * + * @param sheet The style sheet to which to apply the rules. + * @param rules An array of rules to be applied. */ -export const ruffleShadowTemplate = document.createElement("template"); -ruffleShadowTemplate.innerHTML = ` - - + } + return element; +} -
    -
    -
    - -
    +/** + * + * @param parentElement The node to which to append a child element. + * @param childElement The node to be appended to the parent element. + */ +function appendElement(parentElement: Node, childElement: Node) { + parentElement.appendChild(childElement); +} - +/** + * The shadow template which is used to fill the actual Ruffle player element + * on the page. + * + */ +export const ruffleShadowTemplate = document.createElement("template"); +const svgns = "http://www.w3.org/2000/svg"; +const staticStyles = createElement("style", "static-styles"); +const dynamicStyles = createElement("style", "dynamic-styles"); +const container = createElement("div", "container"); - +// Play button elements +const playButton = createElement("div", "play-button"); +const playIcon = createElement("div", undefined, "icon"); +const playSvg = createElement( + "svg", + undefined, + undefined, + { + xmlns: svgns, + "xmlns:xlink": "http://www.w3.org/1999/xlink", + preserveAspectRatio: "xMidYMid", + viewBox: "0 0 250 250", + width: "100%", + height: "100%", + }, + svgns, +); +const playDefs = createElement("defs", undefined, undefined, undefined, svgns); +const playLinearGradient = createElement( + "linearGradient", + "a", + undefined, + { + gradientUnits: "userSpaceOnUse", + x1: "125", + y1: "0", + x2: "125", + y2: "250", + spreadMethod: "pad", + }, + svgns, +); +const playStop0 = createElement( + "stop", + undefined, + undefined, + { + offset: "0%", + "stop-color": "#FDA138", + }, + svgns, +); +const playStop100 = createElement( + "stop", + undefined, + undefined, + { + offset: "100%", + "stop-color": "#FD3A40", + }, + svgns, +); +const playG = createElement("g", "b", undefined, undefined, svgns); +const playPath1 = createElement( + "path", + undefined, + undefined, + { + fill: "url(#a)", + d: "M250 125q0-52-37-88-36-37-88-37T37 37Q0 73 0 125t37 88q36 37 88 37t88-37q37-36 37-88M87 195V55l100 70-100 70z", + }, + svgns, +); +const playPath2 = createElement( + "path", + undefined, + undefined, + { + fill: "#FFF", + d: "M87 55v140l100-70L87 55z", + }, + svgns, +); +const playUse = document.createElementNS(svgns, "use"); +playUse.href.baseVal = "#b"; - +// Unmute overlay elements +const unmuteOverlay = createElement("div", "unmute-overlay"); +const background = createElement("div", undefined, "background"); +const unmuteIcon = createElement("div", undefined, "icon"); +const unmuteSvg = createElement( + "svg", + "unmute-overlay-svg", + undefined, + { + xmlns: svgns, + "xmlns:xlink": "http://www.w3.org/1999/xlink", + preserveAspectRatio: "xMidYMid", + viewBox: "0 0 512 584", + width: "100%", + height: "100%", + scale: "0.8", + }, + svgns, +); +const unmutePath1 = createElement( + "path", + undefined, + undefined, + { + fill: "#FFF", + stroke: "#FFF", + d: "m457.941 256 47.029-47.029c9.372-9.373 9.372-24.568 0-33.941-9.373-9.373-24.568-9.373-33.941 0l-47.029 47.029-47.029-47.029c-9.373-9.373-24.568-9.373-33.941 0-9.372 9.373-9.372 24.568 0 33.941l47.029 47.029-47.029 47.029c-9.372 9.373-9.372 24.568 0 33.941 4.686 4.687 10.827 7.03 16.97 7.03s12.284-2.343 16.971-7.029l47.029-47.03 47.029 47.029c4.687 4.687 10.828 7.03 16.971 7.03s12.284-2.343 16.971-7.029c9.372-9.373 9.372-24.568 0-33.941z", + }, + svgns, +); +const unmutePath2 = createElement( + "path", + undefined, + undefined, + { + fill: "#FFF", + stroke: "#FFF", + d: "m99 160h-55c-24.301 0-44 19.699-44 44v104c0 24.301 19.699 44 44 44h55c2.761 0 5-2.239 5-5v-182c0-2.761-2.239-5-5-5z", + }, + svgns, +); +const unmutePath3 = createElement( + "path", + undefined, + undefined, + { + fill: "#FFF", + stroke: "#FFF", + d: "m280 56h-24c-5.269 0-10.392 1.734-14.578 4.935l-103.459 79.116c-1.237.946-1.963 2.414-1.963 3.972v223.955c0 1.557.726 3.026 1.963 3.972l103.459 79.115c4.186 3.201 9.309 4.936 14.579 4.936h23.999c13.255 0 24-10.745 24-24v-352.001c0-13.255-10.745-24-24-24z", + }, + svgns, +); +const unmuteText = createElement( + "text", + "unmute-text", + undefined, + { + x: "256", + y: "560", + "text-anchor": "middle", + "font-size": "60px", + fill: "#FFF", + stroke: "#FFF", + }, + svgns, +); - -`; +// Virtual keyboard element +const virtualKeyboard = createElement("input", "virtual-keyboard", undefined, { + type: "text", + autocapitalize: "off", + autocomplete: "off", + autocorrect: "off", +}); + +// Splash screen elements +const splashScreen = createElement("div", "splash-screen", "hidden"); +const splashScreenSvg = createElement( + "svg", + undefined, + "logo", + { + xmlns: svgns, + "xmlns:xlink": "http://www.w3.org/1999/xlink", + preserveAspectRatio: "xMidYMid", + viewBox: "0 0 380 150", + }, + svgns, +); +const splashScreenG = createElement( + "g", + undefined, + undefined, + undefined, + svgns, +); +const splashScreenPath1 = createElement( + "path", + undefined, + undefined, + { + fill: "#966214", + d: "M58.75 85.6q.75-.1 1.5-.35.85-.25 1.65-.75.55-.35 1.05-.8.5-.45.95-1 .5-.5.75-1.2-.05.05-.15.1-.1.15-.25.25l-.1.2q-.15.05-.25.1-.4 0-.8.05-.5-.25-.9-.5-.3-.1-.55-.3l-.6-.6-4.25-6.45-1.5 11.25h3.45m83.15-.2h3.45q.75-.1 1.5-.35.25-.05.45-.15.35-.15.65-.3l.5-.3q.25-.15.5-.35.45-.35.9-.75.45-.35.75-.85l.1-.1q.1-.2.2-.35.2-.3.35-.6l-.3.4-.15.15q-.5.15-1.1.1-.25 0-.4-.05-.5-.15-.8-.4-.15-.1-.25-.25-.3-.3-.55-.6l-.05-.05v-.05l-4.25-6.4-1.5 11.25m-21.15-3.95q-.3-.3-.55-.6l-.05-.05v-.05l-4.25-6.4-1.5 11.25h3.45q.75-.1 1.5-.35.85-.25 1.6-.75.75-.5 1.4-1.1.45-.35.75-.85.35-.5.65-1.05l-.45.55q-.5.15-1.1.1-.9 0-1.45-.7m59.15.3q-.75-.5-1.4-1-3.15-2.55-3.5-6.4l-1.5 11.25h21q-3.1-.25-5.7-.75-5.6-1.05-8.9-3.1m94.2 3.85h3.45q.6-.1 1.2-.3.4-.1.75-.2.35-.15.65-.3.7-.35 1.35-.8.75-.55 1.3-1.25.1-.15.25-.3-2.55-.25-3.25-1.8l-4.2-6.3-1.5 11.25m-45.3-4.85q-.5-.4-.9-.8-2.3-2.35-2.6-5.6l-1.5 11.25h21q-11.25-.95-16-4.85m97.7 4.85q-.3-.05-.6-.05-10.8-1-15.4-4.8-3.15-2.55-3.5-6.35l-1.5 11.2h21Z", + }, + svgns, +); +const splashScreenPath2 = createElement( + "path", + undefined, + undefined, + { + fill: "var(--ruffle-orange)", + d: "M92.6 54.8q-1.95-1.4-4.5-1.4H60.35q-1.35 0-2.6.45-1.65.55-3.15 1.8-2.75 2.25-3.25 5.25l-1.65 12h.05v.3l5.85 1.15h-9.5q-.5.05-1 .15-.5.15-1 .35-.5.2-.95.45-.5.3-.95.7-.45.35-.85.8-.35.4-.65.85-.3.45-.5.9-.15.45-.3.95l-5.85 41.6H50.3l5-35.5 1.5-11.25 4.25 6.45.6.6q.25.2.55.3.4.25.9.5.4-.05.8-.05.1-.05.25-.1l.1-.2q.15-.1.25-.25.1-.05.15-.1l.3-1.05 1.75-12.3h11.15L75.8 82.6h16.5l2.3-16.25h-.05l.8-5.7q.4-2.45-1-4.2-.35-.4-.75-.8-.25-.25-.55-.5-.2-.2-.45-.35m16.2 18.1h.05l-.05.3 5.85 1.15H105.2q-.5.05-1 .15-.5.15-1 .35-.5.2-.95.45-.5.3-1 .65-.4.4-.8.85-.25.3-.55.65-.05.1-.15.2-.25.45-.4.9-.2.45-.3.95-.1.65-.2 1.25-.2 1.15-.4 2.25l-4.3 30.6q-.25 3 1.75 5.25 1.6 1.8 4 2.15.6.1 1.25.1h27.35q3.25 0 6-2.25.35-.35.7-.55l.3-.2q2-2 2.25-4.5l1.65-11.6q.05-.05.1-.05l1.65-11.35h.05l.7-5.2 1.5-11.25 4.25 6.4v.05l.05.05q.25.3.55.6.1.15.25.25.3.25.8.4.15.05.4.05.6.05 1.1-.1l.15-.15.3-.4.3-1.05 1.3-9.05h-.05l.7-5.05h-.05l.15-1.25h-.05l1.65-11.7h-16.25l-2.65 19.5h.05v.2l-.05.1h.05l5.8 1.15H132.7q-.5.05-1 .15-.5.15-1 .35-.15.05-.3.15-.3.1-.55.25-.05 0-.1.05-.5.3-1 .65-.4.35-.7.7-.55.7-.95 1.45-.35.65-.55 1.4-.15.7-.25 1.4v.05q-.15 1.05-.35 2.05l-1.2 8.75v.1l-2.1 14.7H111.4l2.25-15.55h.05l.7-5.2 1.5-11.25 4.25 6.4v.05l.05.05q.25.3.55.6.55.7 1.45.7.6.05 1.1-.1l.45-.55.3-1.05 1.3-9.05h-.05l.7-5.05h-.05l.15-1.25h-.05l1.65-11.7h-16.25l-2.65 19.5m106.5-41.75q-2.25-2.25-5.5-2.25h-27.75q-3 0-5.75 2.25-1.3.95-2.05 2.1-.45.6-.7 1.2-.2.5-.35 1-.1.45-.15.95l-4.15 29.95h-.05l-.7 5.2h-.05l-.2 1.35h.05l-.05.3 5.85 1.15h-9.45q-2.1.05-3.95 1.6-1.9 1.55-2.25 3.55l-.5 3.5h-.05l-5.3 38.1h16.25l5-35.5 1.5-11.25q.35 3.85 3.5 6.4.65.5 1.4 1 3.3 2.05 8.9 3.1 2.6.5 5.7.75l1.75-11.25h-12.2l.4-2.95h-.05l.7-5.05h-.05q.1-.9.3-1.9.1-.75.2-1.6.85-5.9 2.15-14.9 0-.15.05-.25l.1-.9q.2-1.55.45-3.15h11.25l-3.1 20.8h16.5l4.1-28.05q.15-1.7-.4-3.15-.5-1.1-1.35-2.1m46.65 44.15q-.5.3-1 .65-.4.4-.8.85-.35.4-.7.85-.25.45-.45.9-.15.45-.3.95l-5.85 41.6h16.25l5-35.5 1.5-11.25 4.2 6.3q.7 1.55 3.25 1.8l.05-.1q.25-.4.35-.85l.3-1.05 1.8-14.05v-.05l5.35-37.45h-16.25l-6.15 44.3 5.85 1.15h-9.45q-.5.05-1 .15-.5.15-1 .35-.5.2-.95.45m5.4-38.9q.15-1.7-.4-3.15-.5-1.1-1.35-2.1-2.25-2.25-5.5-2.25h-27.75q-2.3 0-4.45 1.35-.65.35-1.3.9-1.3.95-2.05 2.1-.45.6-.7 1.2-.4.9-.5 1.95l-4.15 29.95h-.05l-.7 5.2h-.05l-.2 1.35h.05l-.05.3 5.85 1.15h-9.45q-2.1.05-3.95 1.6-1.9 1.55-2.25 3.55l-.5 3.5h-.05l-1.2 8.75v.1l-4.1 29.25h16.25l5-35.5 1.5-11.25q.3 3.25 2.6 5.6.4.4.9.8 4.75 3.9 16 4.85l1.75-11.25h-12.2l.4-2.95h-.05l.7-5.05h-.05q.15-.9.3-1.9.1-.75.25-1.6.15-1.25.35-2.65v-.05q.95-6.7 2.35-16.5h11.25l-3.1 20.8h16.5l4.1-28.05M345 66.35h-.05l1.15-8.2q.5-3-1.75-5.25-1.25-1.25-3-1.75-1-.5-2.25-.5h-27.95q-.65 0-1.3.1-2.5.35-4.7 2.15-2.75 2.25-3.25 5.25l-1.95 14.7v.05l-.05.3 5.85 1.15h-9.45q-1.9.05-3.6 1.35-.2.1-.35.25-1.9 1.55-2.25 3.55l-4.85 34.1q-.25 3 1.75 5.25 1.25 1.4 3 1.95 1.05.3 2.25.3H320q3.25 0 6-2.25 2.75-2 3.25-5l2.75-18.5h-16.5l-1.75 11H302.5l2.1-14.75h.05l.85-6 1.5-11.2q.35 3.8 3.5 6.35 4.6 3.8 15.4 4.8.3 0 .6.05h15.75L345 66.35m-16.4-.95-1.25 8.95h-11.3l.4-2.95h-.05l.7-5.05h-.1l.15-.95h11.45Z", + }, + svgns, +); +const loadingAnimation = createElement( + "svg", + undefined, + "loading-animation", + { + xmlns: svgns, + viewBox: "0 0 66 66", + }, + svgns, +); +const spinner = createElement( + "circle", + undefined, + "spinner", + { + fill: "none", + "stroke-width": "6", + "stroke-linecap": "round", + cx: "33", + cy: "33", + r: "30", + }, + svgns, +); +const loadbar = createElement("div", undefined, "loadbar"); +const loadbarInner = createElement("div", undefined, "loadbar-inner"); + +// Save manager elements +const saveManager = createElement("div", "save-manager", "modal hidden"); +const saveModalArea = createElement("div", "modal-area", "modal-area"); +const saveModalClose = createElement("span", undefined, "close-modal"); +saveModalClose.textContent = "\u00D7"; +const generalSaveOptions = createElement( + "div", + undefined, + "general-save-options", +); +const backupSaves = createElement("span", "backup-saves", "save-option"); +const localSaves = createElement("table", "local-saves"); + +// Video modal elements +const videoModal = createElement("div", "video-modal", "modal hidden"); +const videoModalArea = createElement("div", undefined, "modal-area"); +const videoModalClose = createElement("span", undefined, "close-modal"); +videoModalClose.textContent = "\u00D7"; +const videoHolder = createElement("div", "video-holder"); + +// Context menu overlay elements +const contextMenuOverlay = createElement( + "div", + "context-menu-overlay", + "hidden", +); +const contextMenu = createElement("ul", "context-menu"); + +appendElement(ruffleShadowTemplate.content, staticStyles); +appendElement(ruffleShadowTemplate.content, dynamicStyles); +appendElement(ruffleShadowTemplate.content, container); +// Play button append +appendElement(container, playButton); +appendElement(playButton, playIcon); +appendElement(playIcon, playSvg); +appendElement(playSvg, playDefs); +appendElement(playDefs, playLinearGradient); +appendElement(playLinearGradient, playStop0); +appendElement(playLinearGradient, playStop100); +appendElement(playDefs, playG); +appendElement(playG, playPath1); +appendElement(playG, playPath2); +appendElement(playSvg, playUse); +// Unmute overlay append +appendElement(container, unmuteOverlay); +appendElement(unmuteOverlay, background); +appendElement(unmuteOverlay, unmuteIcon); +appendElement(unmuteIcon, unmuteSvg); +appendElement(unmuteSvg, unmutePath1); +appendElement(unmuteSvg, unmutePath2); +appendElement(unmuteSvg, unmutePath3); +appendElement(unmuteSvg, unmuteText); +// Virtual keyboard append +appendElement(container, virtualKeyboard); +// Splash screen append +appendElement(ruffleShadowTemplate.content, splashScreen); +appendElement(splashScreen, splashScreenSvg); +appendElement(splashScreenSvg, splashScreenG); +appendElement(splashScreenG, splashScreenPath1); +appendElement(splashScreenG, splashScreenPath2); +appendElement(splashScreen, loadingAnimation); +appendElement(loadingAnimation, spinner); +appendElement(splashScreen, loadbar); +appendElement(loadbar, loadbarInner); +// Save manager append +appendElement(ruffleShadowTemplate.content, saveManager); +appendElement(saveManager, saveModalArea); +appendElement(saveModalArea, saveModalClose); +appendElement(saveModalArea, generalSaveOptions); +appendElement(generalSaveOptions, backupSaves); +appendElement(saveModalArea, localSaves); +// Video modal append +appendElement(ruffleShadowTemplate.content, videoModal); +appendElement(videoModal, videoModalArea); +appendElement(videoModalArea, videoModalClose); +appendElement(videoModalArea, videoHolder); +// Context menu overlay append +appendElement(ruffleShadowTemplate.content, contextMenuOverlay); +appendElement(contextMenuOverlay, contextMenu);