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>
This commit is contained in:
Daniel Jacobs 2023-08-26 15:51:16 -04:00 committed by GitHub
parent 5a10ccb22a
commit 18e89f6132
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 653 additions and 280 deletions

View File

@ -108,7 +108,7 @@ export function text(
export function textAsParagraphs(
id: string,
args?: Record<string, FluentVariable> | 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;
}

View File

@ -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 = <HTMLStyleElement>(
this.shadow.getElementById("dynamic_styles")
this.shadow.getElementById("dynamic-styles")
);
this.staticStyles = <HTMLStyleElement>(
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 = <HTMLInputElement>(
this.shadow.getElementById("virtual-keyboard")!
@ -258,11 +269,11 @@ export class RufflePlayer extends HTMLElement {
}
const unmuteSvg = <SVGElement>(
this.unmuteOverlay.querySelector("#unmute_overlay_svg")
this.unmuteOverlay.querySelector("#unmute-overlay-svg")
);
if (unmuteSvg) {
const unmuteText = <SVGTextElement>(
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 <ul> element to be used as the error footer
*/
private createErrorFooter(
footerInfo: Array<PanicLinkInfo>,
): 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 = `<a target="_top" href="${issueLink}">${text(
"report-bug",
)}</a>`;
actionLink = new PanicLinkInfo(issueLink, text("report-bug"));
} else {
actionTag = `<a target="_top" href="${RUFFLE_ORIGIN}#downloads">${text(
"update-ruffle",
)}</a>`;
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 = `
<li><a target="_top" href="${RUFFLE_ORIGIN}/demo">${text(
"ruffle-demo",
)}</a></li>
<li><a target="_top" href="${RUFFLE_ORIGIN}#downloads">${text(
"ruffle-desktop",
)}</a></li>
`;
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 = `
<li><a target="_top" href="https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#javascript-api">${text(
"ruffle-wiki",
)}</a></li>
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
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 = `
<li><a target="_top" href="https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configuration-options">${text(
"ruffle-wiki",
)}</a></li>
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
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 = `
<li><a target="_top" href="https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configure-webassembly-mime-type">${text(
"ruffle-wiki",
)}</a></li>
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
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 = `
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
errorFooter = this.createErrorFooter([new PanicLinkInfo()]);
break;
case PanicError.SwfCors:
// Self hosted: Cannot load SWF file - CORS issues
errorBody = textAsParagraphs("error-swf-cors");
errorFooter = `
<li><a target="_top" href="https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configure-cors-header">${text(
"ruffle-wiki",
)}</a></li>
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
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 = `
<li><a target="_top" href="https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configure-cors-header">${text(
"ruffle-wiki",
)}</a></li>
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
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 = `
<li><a target="_top" href="https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#addressing-a-compileerror">${text(
"ruffle-wiki",
)}</a></li>
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
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 = `
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
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 = `
<li><a target="_top" href="https://github.com/ruffle-rs/ruffle/wiki/Frequently-Asked-Questions-For-Users#edge-webassembly-error">${text(
"more-info",
)}</a></li>
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
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 = `
<li>${actionTag}</li>
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
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 = `
<li><a target="_top" href="https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configure-wasm-csp">${text(
"ruffle-wiki",
)}</a></li>
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
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 = `
<li>${actionTag}</li>
<li><a href="#" id="panic-view-details">${text(
"view-error-details",
)}</a></li>
`;
errorFooter = this.createErrorFooter([
actionLink,
new PanicLinkInfo(),
]);
break;
}
this.container.innerHTML = `
<div id="panic">
<div id="panic-title">${text("panic-title")}</div>
<div id="panic-body">${errorBody}</div>
<div id="panic-footer">
<ul>${errorFooter}</ul>
</div>
</div>
`;
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 = <HTMLLinkElement>(
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 = `<div class="message">
<p>${message}</p>
<div>
<button id="continue-btn">${text("continue")}</button>
</div>
</div>`;
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);
(<HTMLButtonElement>(
this.container.querySelector("#continue-btn")

View File

@ -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 = `
<style>
:host {
function insertRules(sheet: CSSStyleSheet, rules: Array<string>) {
for (const rule of rules) {
try {
sheet.insertRule(rule);
} catch (err) {
// Ignore unsupported rules
}
}
}
/**
* The default styles to apply to the shadow template.
* This function must be run after the shadow template is added to the page.
*
* @param styleElement The static style element to which to add the rules
*/
export function applyStaticStyles(styleElement: HTMLStyleElement) {
if (!styleElement.sheet) {
return;
}
const rules = [
`:host {
all: initial;
--ruffle-blue: #37528c;
@ -22,57 +43,57 @@ ruffleShadowTemplate.innerHTML = `
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
}
}`,
/* Ruffle's width/height CSS interferes with Safari's fullscreen CSS. */
/* Ensure that Safari's fullscreen mode actually fills the screen. */
:host(:-webkit-full-screen) {
`:host(:-webkit-full-screen) {
display: block;
width: 100% !important;
height: 100% !important;
}
}`,
.hidden {
`.hidden {
display: none !important;
}
}`,
/* All of these use the dimensions specified by the embed. */
#container,
#play_button,
#unmute_overlay,
#unmute_overlay .background,
`#container,
#play-button,
#unmute-overlay,
#unmute-overlay .background,
#panic,
#splash-screen,
#message_overlay {
#message-overlay {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
}`,
#container {
`#container {
overflow: hidden;
}
}`,
#container canvas {
`#container canvas {
width: 100%;
height: 100%;
}
}`,
#play_button,
#unmute_overlay {
`#play-button,
#unmute-overlay {
cursor: pointer;
display: none;
}
}`,
#unmute_overlay .background {
`#unmute-overlay .background {
background: black;
opacity: 0.7;
}
}`,
#play_button .icon,
#unmute_overlay .icon {
`#play-button .icon,
#unmute-overlay .icon {
position: absolute;
top: 50%;
left: 50%;
@ -82,53 +103,53 @@ ruffleShadowTemplate.innerHTML = `
max-height: 384px;
transform: translate(-50%, -50%);
opacity: 0.8;
}
}`,
#play_button:hover .icon,
#unmute_overlay:hover .icon {
`#play-button:hover .icon,
#unmute-overlay:hover .icon {
opacity: 1;
}
}`,
#panic {
/* Includes inverted colors from play button! */
`#panic {
font-size: 20px;
text-align: center;
/* Inverted colors from play button! */
background: linear-gradient(180deg, #fd3a40 0%, #fda138 100%);
color: white;
display: flex;
flex-flow: column;
justify-content: space-around;
}
}`,
#panic a {
`#panic a {
color: var(--ruffle-blue);
font-weight: bold;
}
}`,
#panic-title {
`#panic-title {
font-size: xxx-large;
font-weight: bold;
}
}`,
#panic-body.details {
`#panic-body.details {
flex: 0.9;
margin: 0 10px;
}
}`,
#panic-body textarea {
`#panic-body textarea {
width: 100%;
height: 100%;
resize: none;
}
}`,
#panic ul {
`#panic ul {
padding: 0;
display: flex;
list-style-type: none;
justify-content: space-evenly;
}
}`,
#message_overlay {
`#message-overlay {
position: absolute;
background: var(--ruffle-blue);
color: var(--ruffle-orange);
@ -138,28 +159,28 @@ ruffleShadowTemplate.innerHTML = `
align-items: center;
justify-content: center;
overflow: auto;
}
}`,
#message_overlay .message {
`#message-overlay .message {
text-align: center;
max-height: 100%;
max-width: 100%;
padding: 5%;
font-size: 20px;
}
}`,
#message_overlay p {
`#message-overlay p {
margin: 0.5em 0;
}
}`,
#message_overlay .message div {
`#message-overlay .message div {
display: flex;
justify-content: center;
flex-wrap: wrap;
column-gap: 1em;
}
}`,
#message_overlay a, #message_overlay button {
`#message-overlay a, #message-overlay button {
cursor: pointer;
background: var(--ruffle-blue);
color: var(--ruffle-orange);
@ -170,13 +191,13 @@ ruffleShadowTemplate.innerHTML = `
padding: 10px;
text-decoration: none;
margin: 2% 0;
}
}`,
#message_overlay a:hover, #message_overlay button:hover {
`#message-overlay a:hover, #message-overlay button:hover {
background: #ffffff4c;
}
}`,
#continue-btn {
`#continue-btn {
cursor: pointer;
background: var(--ruffle-blue);
color: var(--ruffle-orange);
@ -185,20 +206,20 @@ ruffleShadowTemplate.innerHTML = `
font-size: 20px;
border-radius: 20px;
padding: 10px;
}
}`,
#continue-btn:hover {
`#continue-btn:hover {
background: #ffffff4c;
}
}`,
#context-menu-overlay {
`#context-menu-overlay {
width: 100%;
height: 100%;
z-index: 1;
position: absolute;
}
}`,
#context-menu {
`#context-menu {
color: black;
background: #fafafa;
border: 1px solid gray;
@ -209,117 +230,117 @@ ruffleShadowTemplate.innerHTML = `
list-style: none;
padding: 0;
margin: 0;
}
}`,
#context-menu .menu_item {
`#context-menu .menu-item {
padding: 5px 10px;
cursor: pointer;
color: black;
}
}`,
#context-menu .menu_item.disabled {
`#context-menu .menu-item.disabled {
cursor: default;
color: gray;
}
}`,
#context-menu .menu_item:not(.disabled):hover {
`#context-menu .menu-item:not(.disabled):hover {
background: lightgray;
}
}`,
#context-menu .menu_separator hr {
`#context-menu .menu-separator hr {
border: none;
border-bottom: 1px solid lightgray;
margin: 2px;
}
}`,
#splash-screen {
`#splash-screen {
display: flex;
flex-direction: column;
background: var(--splash-screen-background, var(--preloader-background, var(--ruffle-blue)));
align-items: center;
justify-content: center;
}
}`,
.loadbar {
`.loadbar {
width: 100%;
max-width: 316px;
max-height: 10px;
height: 20%;
background: #253559;
}
}`,
.loadbar-inner {
`.loadbar-inner {
width: 0px;
max-width: 100%;
height: 100%;
background: var(--ruffle-orange);
}
.logo {
}`,
`.logo {
display: var(--logo-display, block);
max-width: 380px;
max-height: 150px;
}
}`,
.loading-animation {
`.loading-animation {
max-width: 28px;
max-height: 28px;
margin-bottom: 2%;
width: 10%;
aspect-ratio: 1;
}
}`,
.spinner {
`.spinner {
stroke-dasharray: 180;
stroke-dashoffset: 135;
stroke: var(--ruffle-orange);
transform-origin: 50% 50%;
animation: rotate 1.5s linear infinite;
}
}`,
@keyframes rotate {
`@keyframes rotate {
to {
transform: rotate(360deg);
}
}
}`,
#virtual-keyboard {
`#virtual-keyboard {
position: absolute;
opacity: 0;
top: -100px;
width: 1px;
height: 1px;
}
}`,
.modal {
`.modal {
height: inherit;
user-select: text;
}
}`,
.modal-area {
`.modal-area {
position: sticky;
background: white;
width: fit-content;
padding: 16px;
border: 3px solid black;
margin: auto;
}
}`,
#modal-area {
`#modal-area {
height: 500px;
max-height: calc(100% - 38px);
min-height: 80px;
}
}`,
#restore-save {
`#restore-save {
display: none;
}
}`,
.replace-save {
`.replace-save {
display: none;
}
}`,
.save-option {
`.save-option {
display: inline-block;
padding: 3px 10px;
margin: 5px 2px;
@ -327,83 +348,403 @@ ruffleShadowTemplate.innerHTML = `
border-radius: 50px;
background-color: var(--ruffle-blue);
color: white;
}
}`,
.close-modal {
`.close-modal {
position: absolute;
top: 5px;
right: 10px;
cursor: pointer;
font-size: x-large;
}
}`,
.general-save-options {
`.general-save-options {
text-align: center;
padding-bottom: 8px;
border-bottom: 2px solid #888;
}
}`,
#local-saves {
`#local-saves {
border-collapse: collapse;
overflow-y: auto;
display: block;
padding-right: 16px;
height: calc(100% - 45px);
min-height: 30px;
}
}`,
#local-saves td {
`#local-saves td {
border-bottom: 1px solid #bbb;
height: 30px;
}
}`,
#local-saves tr td:nth-child(1) {
`#local-saves tr td:nth-child(1) {
padding-right: 1em;
word-break: break-all;
}
}`,
#local-saves tr:nth-child(even) {
`#local-saves tr:nth-child(even) {
background-color: #f2f2f2;
}
}`,
#video-holder {
`#video-holder {
padding-top: 20px;
}`,
];
insertRules(styleElement.sheet, rules);
}
/**
*
* @param tag The HTML tag name of the new element.
* @param id The id of the new element.
* @param className The class name of the new element.
* @param attributes A hash of attributes for the new element.
* @param ns The namespace of the new element.
*
* @returns The newly created Element
*/
function createElement(
tag: string,
id?: string,
className?: string,
attributes?: Record<string, string>,
ns?: string,
): Element {
const element = ns
? document.createElementNS(ns, tag)
: document.createElement(tag);
if (id) {
element.id = id;
}
if (className && ns) {
element.classList.add(className);
} else if (className) {
element.className = className;
}
if (attributes) {
for (const [key, attr] of Object.entries(attributes)) {
element.setAttribute(key, attr);
}
</style>
<style id="dynamic_styles"></style>
}
return element;
}
<div id="container">
<div id="play_button"><div class="icon"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid" viewBox="0 0 250 250" width="100%" height="100%"><defs><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="125" y1="0" x2="125" y2="250" spreadMethod="pad"><stop offset="0%" stop-color="#FDA138"/><stop offset="100%" stop-color="#FD3A40"/></linearGradient><g id="b"><path 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"/><path fill="#FFF" d="M87 55v140l100-70L87 55z"/></g></defs><use xlink:href="#b"/></svg></div></div>
<div id="unmute_overlay"><div class="background"></div><div class="icon"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid" viewBox="0 0 512 584" width="100%" height="100%" scale="0.8" id="unmute_overlay_svg"><path 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"/><path 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"/><path 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"/><text x="256" y="560" text-anchor="middle" font-size="60px" fill="#FFF" stroke="#FFF" id="unmute_text"></text></svg></div></div>
<input id="virtual-keyboard" type="text" autocapitalize="off" autocomplete="off" autocorrect="off">
</div>
/**
*
* @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);
}
<div class="hidden" id="splash-screen">
<svg class="logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid" viewBox="0 0 380 150"><g><path 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"/><path 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"/></g></svg>
<svg class="loading-animation" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 66 66">
<circle class="spinner" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
</svg>
<div class="loadbar"><div class="loadbar-inner"></div></div>
</div>
/**
* 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");
<div id="save-manager" class="modal hidden">
<div id="modal-area" class="modal-area">
<span class="close-modal">&times;</span>
<div class="general-save-options">
<span class="save-option" id="backup-saves"></span>
</div>
<table id="local-saves"></table>
</div>
</div>
// 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";
<div id="video-modal" class="modal hidden">
<div class="modal-area">
<span class="close-modal">&times;</span>
<div id="video-holder"></div>
</div>
</div>
// 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,
);
<div id="context-menu-overlay" class="hidden">
<ul id="context-menu"></ul>
</div>
`;
// 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);