From f78122b5c5dfa79b371d4c246b15cf5b8712a879 Mon Sep 17 00:00:00 2001
From: Robin <129759474+robinmiau@users.noreply.github.com>
Date: Tue, 21 Apr 2026 01:52:44 +0200
Subject: [PATCH 1/5] Update panic component for better error detail display
Refactor panic component to improve error detail handling.
---
web/packages/core/src/internal/ui/panic.tsx | 48 ++++++++++++++++-----
1 file changed, 38 insertions(+), 10 deletions(-)
diff --git a/web/packages/core/src/internal/ui/panic.tsx b/web/packages/core/src/internal/ui/panic.tsx
index 2127d05a5c54..eaadd1e09d96 100644
--- a/web/packages/core/src/internal/ui/panic.tsx
+++ b/web/packages/core/src/internal/ui/panic.tsx
@@ -49,9 +49,9 @@ function createPanicAction({
errorText: string;
}) {
if (action.type === "show_details") {
- const onClick = () => {
+ const onClick = (event: MouseEvent) => {
+ event.preventDefault();
showDetails();
- return false;
};
return (
@@ -396,21 +396,17 @@ export function showPanicScreen(
const errorText = errorArray.join("");
const { body, actions } = createPanicError(error);
- const panicBody = createRef();
+ const detailsModal = createRef();
+ const copyButton = createRef();
const showDetails = () => {
- panicBody.current!.classList.add("details");
- panicBody.current!.replaceChildren(
- ,
- );
+ detailsModal.current!.classList.remove("hidden");
};
container.textContent = "";
container.appendChild(
{text("panic-title")}
-
- {body}
-
+
{body}
+
+
+ {
+ if (!copyButton.current) {
+ return;
+ }
+ navigator.clipboard?.writeText(errorText);
+ copyButton.current.classList.add("copied");
+ setTimeout(() => {
+ copyButton.current?.classList.remove("copied");
+ }, 2000);
+ }}
+ >
+
+ detailsModal.current!.classList.add("hidden")
+ }
+ >
+
+
+
,
);
}
From e7f887cd758a338c75c5378c83d9170f93db5f76 Mon Sep 17 00:00:00 2001
From: Robin <129759474+robinmiau@users.noreply.github.com>
Date: Tue, 21 Apr 2026 02:05:53 +0200
Subject: [PATCH 2/5] Add checked property and improve context menu handling
---
.../core/src/internal/player/inner.tsx | 126 +++++++++++++++---
1 file changed, 105 insertions(+), 21 deletions(-)
diff --git a/web/packages/core/src/internal/player/inner.tsx b/web/packages/core/src/internal/player/inner.tsx
index a42de959096a..25291f25979b 100644
--- a/web/packages/core/src/internal/player/inner.tsx
+++ b/web/packages/core/src/internal/player/inner.tsx
@@ -93,6 +93,12 @@ interface ContextMenuItem {
* @default true
*/
enabled?: boolean;
+
+ /**
+ * Whether this item has a checkmark next to it.
+ * When defined, a checkmark column is shown for all items in the group.
+ */
+ checked?: boolean;
}
/**
@@ -178,6 +184,9 @@ export class InnerPlayer {
// Allows the user to permanently disable the context menu.
private contextMenuForceDisabled = false;
+ // The client position where the custom context menu was last opened.
+ private contextMenuOpenPosition: { x: number; y: number } | null = null;
+
// Whether the most recent pointer event was from a touch (or pen).
private isTouch = false;
// Whether this device sends contextmenu events.
@@ -187,6 +196,8 @@ export class InnerPlayer {
// When set to `true`, the next context menu event will
// not show the context menu.
private _suppressContextMenu = false;
+ private hideContextMenuOnWheel: ((event: WheelEvent) => void) | null =
+ null;
// The effective config loaded upon `.load()`.
public loadedConfig?: URLLoadOptions | DataLoadOptions;
@@ -382,7 +393,7 @@ export class InnerPlayer {
}
};
- modalElement.parentNode!.addEventListener("click", hideModal);
+ modalElement.addEventListener("click", hideModal);
const modalArea = modalElement.querySelector(".modal-area");
if (modalArea) {
modalArea.addEventListener("click", (event) =>
@@ -447,6 +458,10 @@ export class InnerPlayer {
volumeMuteCheckbox.checked = this.volumeSettings.isMuted;
volumeSlider.disabled = volumeMuteCheckbox.checked;
volumeSlider.valueAsNumber = this.volumeSettings.volume;
+ volumeSlider.style.setProperty(
+ "--volume-pct",
+ `${this.volumeSettings.volume}%`,
+ );
volumeSliderText.textContent = volumeSlider.value + "%";
setVolumeIcon();
@@ -459,6 +474,10 @@ export class InnerPlayer {
});
volumeSlider.addEventListener("input", () => {
volumeSliderText.textContent = volumeSlider.value + "%";
+ volumeSlider.style.setProperty(
+ "--volume-pct",
+ `${volumeSlider.value}%`,
+ );
this.volumeSettings.volume = volumeSlider.valueAsNumber;
this.instance?.set_volume(this.volumeSettings.get_volume());
setVolumeIcon();
@@ -750,7 +769,17 @@ export class InnerPlayer {
this.rendererDebugInfo = this.instance!.renderer_debug_info();
- if (this.rendererDebugInfo.includes("Adapter Device Type: Cpu")) {
+ // Show a hardware acceleration warning when software rendering is detected.
+ // The device type is unreliable through WebGL/ANGLE (always "Other"), so we
+ // also match known software renderer names (WARP, SwiftShader, Mesa llvmpipe).
+ const isSoftwareRenderer =
+ this.rendererDebugInfo.includes("Adapter Device Type: Cpu") ||
+ this.rendererDebugInfo.includes("Adapter Device Type: VirtualGpu") ||
+ this.rendererDebugInfo.includes("Microsoft Basic Render Driver") ||
+ this.rendererDebugInfo.includes("SwiftShader") ||
+ this.rendererDebugInfo.includes("llvmpipe") ||
+ this.rendererDebugInfo.includes("softpipe");
+ if (isSoftwareRenderer) {
this.container.addEventListener(
"mouseover",
this.openHardwareAccelerationModal.bind(this),
@@ -1503,7 +1532,6 @@ export class InnerPlayer {
}
private contextMenuItems(): Array {
- const CHECKMARK = String.fromCharCode(0x2713);
const items: Array = [];
const addSeparator = () => {
// Don't start with or duplicate separators.
@@ -1524,12 +1552,11 @@ export class InnerPlayer {
addSeparator();
}
items.push({
- // TODO: better checkboxes
- text:
- item.caption + (item.checked ? ` (${CHECKMARK})` : ``),
+ text: item.caption,
onClick: async () =>
this.instance?.run_context_menu_callback(index),
enabled: item.enabled,
+ checked: item.checked,
});
});
@@ -1669,6 +1696,22 @@ export class InnerPlayer {
return;
}
+ // If the custom menu is already open and the user right-clicks again
+ // at roughly the same position, hide our menu and let the browser show
+ // its native context menu. A 8px threshold accounts for slight mouse drift.
+ if (
+ event.type === "contextmenu" &&
+ !this.contextMenuOverlay.classList.contains("hidden") &&
+ this.contextMenuOpenPosition !== null
+ ) {
+ const dx = event.clientX - this.contextMenuOpenPosition.x;
+ const dy = event.clientY - this.contextMenuOpenPosition.y;
+ if (dx * dx + dy * dy <= 64) {
+ this.hideContextMenu();
+ return;
+ }
+ }
+
event.preventDefault();
if (this._suppressContextMenu) {
@@ -1700,6 +1743,20 @@ export class InnerPlayer {
);
event.stopPropagation();
}
+ this.hideContextMenuOnWheel = (event: WheelEvent) => {
+ const rect = this.contextMenuElement.getBoundingClientRect();
+ if (
+ event.clientX < rect.left ||
+ event.clientX > rect.right ||
+ event.clientY < rect.top ||
+ event.clientY > rect.bottom
+ ) {
+ this.hideContextMenu();
+ }
+ };
+ document.addEventListener("wheel", this.hideContextMenuOnWheel, {
+ capture: true,
+ });
if (
[false, ContextMenu.Off].includes(
@@ -1720,8 +1777,17 @@ export class InnerPlayer {
);
}
+ const items = this.contextMenuItems();
+ const hasCheckmarks = items.some(
+ (item) => item !== null && item.checked !== undefined,
+ );
+ this.contextMenuElement.classList.toggle(
+ "has-checkmarks",
+ hasCheckmarks,
+ );
+
// Populate context menu items.
- for (const item of this.contextMenuItems()) {
+ for (const item of items) {
if (item === null) {
this.contextMenuElement.appendChild(
,
);
} else {
- const { text, onClick, enabled } = item;
+ const { text, onClick, enabled, checked } = item;
const menuItem = (
@@ -1771,6 +1838,8 @@ export class InnerPlayer {
this.contextMenuOverlay.classList.remove("hidden");
+ this.contextMenuOpenPosition = { x: event.clientX, y: event.clientY };
+
const playerRect = this.element.getBoundingClientRect();
const contextMenuRect = this.contextMenuElement.getBoundingClientRect();
@@ -1786,19 +1855,25 @@ export class InnerPlayer {
// mode and get the body when it's null.
const viewportElement = document.scrollingElement || document.body;
- // Keep the entire context menu inside the viewport.
- const overflowX = Math.max(
- 0,
- event.clientX + contextMenuRect.width - viewportElement.clientWidth,
- );
- const overflowY = Math.max(
- 0,
- event.clientY +
- contextMenuRect.height -
- viewportElement.clientHeight,
- );
- const x = event.clientX - playerRect.x - overflowX;
- const y = event.clientY - playerRect.y - overflowY;
+ const menuWidth = contextMenuRect.width;
+ const menuHeight = contextMenuRect.height;
+ const vw = viewportElement.clientWidth;
+ const vh = viewportElement.clientHeight;
+
+ // Flip the menu above/left of the cursor (like native context menus)
+ // when it would overflow, falling back to clamping if there's no room.
+ let cx = event.clientX;
+ if (cx + menuWidth > vw) {
+ cx = event.clientX - menuWidth >= 0 ? event.clientX - menuWidth : vw - menuWidth;
+ }
+
+ let cy = event.clientY;
+ if (cy + menuHeight > vh) {
+ cy = event.clientY - menuHeight >= 0 ? event.clientY - menuHeight : vh - menuHeight;
+ }
+
+ const x = cx - playerRect.x;
+ const y = cy - playerRect.y;
const isRtl =
getComputedStyle(this.contextMenuElement).direction === "rtl";
@@ -1816,6 +1891,15 @@ export class InnerPlayer {
private hideContextMenu(): void {
this.instance?.clear_custom_menu_items();
this.contextMenuOverlay.classList.add("hidden");
+ this.contextMenuOpenPosition = null;
+ if (this.hideContextMenuOnWheel) {
+ document.removeEventListener(
+ "wheel",
+ this.hideContextMenuOnWheel,
+ { capture: true },
+ );
+ this.hideContextMenuOnWheel = null;
+ }
}
/**
From 0260b1ce0fa2fe8a298daee6d887f20858acb436 Mon Sep 17 00:00:00 2001
From: Robin <129759474+robinmiau@users.noreply.github.com>
Date: Tue, 21 Apr 2026 02:13:33 +0200
Subject: [PATCH 3/5] Enhance static styles with new variables and updates
Added new CSS variables for dark mode and modal styles. Updated styles for panic overlay and modal components.
---
.../core/src/internal/ui/static-styles.css | 344 ++++++++++++++----
1 file changed, 274 insertions(+), 70 deletions(-)
diff --git a/web/packages/core/src/internal/ui/static-styles.css b/web/packages/core/src/internal/ui/static-styles.css
index 445ecec63f9d..36e24d6ec4c6 100644
--- a/web/packages/core/src/internal/ui/static-styles.css
+++ b/web/packages/core/src/internal/ui/static-styles.css
@@ -3,7 +3,11 @@
pointer-events: inherit;
--ruffle-blue: #37528c;
+ --ruffle-blue-dark: #253559;
--ruffle-orange: #ffad33;
+ --modal-background: #fafafa;
+ --modal-foreground-rgb: 0, 0, 0;
+ --modal-foreground-filter: none;
display: inline-block;
position: relative;
@@ -86,50 +90,119 @@
scale: 0.8;
}
-/* Includes inverted colors from play button! */
#panic {
- font-size: 20px;
+ font-size: 15px;
text-align: center;
- background: linear-gradient(180deg, #fd3a40 0%, #fda138 100%);
+ background: var(--ruffle-blue);
color: white;
display: flex;
flex-flow: column;
- justify-content: space-around;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 16px;
overflow: auto;
a {
- color: var(--ruffle-blue);
- font-weight: bold;
+ color: white;
+ text-underline-offset: 2px;
}
}
#panic-title {
- font-size: xxx-large;
+ font-size: 28px;
font-weight: bold;
+ letter-spacing: -0.5px;
}
#panic-body {
- &.details {
- flex: 0.9;
- margin: 0 10px;
+ max-width: 480px;
+ width: 100%;
+}
+
+#panic-details-modal {
+ position: absolute;
+ inset: 0;
+ background: #0008;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 8px;
+ box-sizing: border-box;
+ z-index: 1;
+}
+
+#panic-details-content {
+ position: relative;
+ background-color: var(--modal-background);
+ color: rgb(var(--modal-foreground-rgb));
+ width: 100%;
+ max-width: 720px;
+ height: 80%;
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 2px 6px 0 #0008;
+ padding: 44px 12px 12px;
+ box-sizing: border-box;
+
+ .panic-copy-button {
+ position: absolute;
+ top: 14px;
+ right: 40px;
+ width: 16px;
+ height: 16px;
+ background-image: url("data:image/svg+xml,");
+ cursor: pointer;
+ filter: var(--modal-foreground-filter);
+ border-radius: 4px;
+ opacity: 0.6;
+ transition:
+ opacity 0.15s,
+ background-image 0s;
+
+ &:hover {
+ opacity: 1;
+ }
+
+ &.copied {
+ background-image: url("data:image/svg+xml,");
+ filter: none;
+ opacity: 1;
+ pointer-events: none;
+ cursor: default;
+ }
}
textarea {
width: 100%;
height: 100%;
+ font-family: monospace;
+ font-size: 12px;
resize: none;
+ background: rgb(var(--modal-foreground-rgb), 0.07);
+ border: none;
+ border-radius: 8px;
+ color: rgb(var(--modal-foreground-rgb));
+ padding: 10px;
+ box-sizing: border-box;
+ outline: none;
}
-}
-#panic ul {
- padding: 0;
- display: flex;
- list-style-type: none;
- justify-content: space-evenly;
+ textarea::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ textarea::-webkit-scrollbar-thumb {
+ background: rgb(var(--modal-foreground-rgb), 0.25);
+ border-radius: 3px;
+ }
+
+ textarea::-webkit-scrollbar-track {
+ background: transparent;
+ }
}
#message-overlay {
- position: absolute;
background: var(--ruffle-blue);
color: var(--ruffle-orange);
opacity: 1;
@@ -165,34 +238,49 @@
color: var(--ruffle-orange);
border: 2px solid var(--ruffle-orange);
font-weight: bold;
- font-size: 1.25em;
- border-radius: 0.6em;
- padding: 10px;
+ font-size: 16px;
+ font-family: inherit;
+ border-radius: 8px;
+ padding: 10px 16px;
text-decoration: none;
- margin: 2% 0;
- }
-
- a:hover,
- button:hover {
- background: #ffffff4c;
+ margin: 8px 0;
+ transition: background 0.15s;
}
}
-#continue-btn {
- cursor: pointer;
- background: var(--ruffle-blue);
- color: var(--ruffle-orange);
- border: 2px solid var(--ruffle-orange);
- font-weight: bold;
- font-size: 20px;
- border-radius: 20px;
- padding: 10px;
+#panic ul {
+ padding: 0;
+ margin: 0;
+ display: flex;
+ list-style-type: none;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 8px;
+
+ li a {
+ display: inline-block;
+ background: transparent;
+ border: 1px solid var(--ruffle-orange);
+ border-radius: 8px;
+ padding: 8px 16px;
+ color: var(--ruffle-orange);
+ text-decoration: none;
+ font-weight: bold;
+ font-size: 13px;
+ font-family: inherit;
+ transition: background 0.15s;
- &:hover {
- background: #ffffff4c;
+ &:hover {
+ background: rgb(255 173 51 / 15%);
+ }
}
}
+#message-overlay a:hover,
+#message-overlay button:hover {
+ background: #ffffff4c;
+}
+
#context-menu-overlay,
.modal {
width: 100%;
@@ -204,19 +292,38 @@
#context-menu {
color: rgb(var(--modal-foreground-rgb));
background-color: var(--modal-background);
- border: 1px solid gray;
- box-shadow: 0 5px 10px -5px black;
+ box-shadow: 0 4px 16px #0006;
+ border-radius: 8px;
+ overflow: hidden;
position: absolute;
font-size: 14px;
text-align: start;
list-style: none;
white-space: nowrap;
- padding: 3px 0;
+ padding: 5px 0;
margin: 0;
.menu-item {
- padding: 5px 10px;
- color: rgb(var(--modal-foreground-rgb));
+ padding: 7px 12px;
+ }
+
+ &.has-checkmarks .menu-item {
+ padding-left: 32px;
+ position: relative;
+ }
+
+ &.has-checkmarks .menu-item.checked::before {
+ content: "";
+ width: 16px;
+ height: 16px;
+ position: absolute;
+ left: 8px;
+ top: 50%;
+ transform: translateY(-50%);
+ background-image: url("data:image/svg+xml,");
+ background-repeat: no-repeat;
+ background-size: contain;
+ filter: var(--modal-foreground-filter);
}
.menu-item.disabled {
@@ -231,7 +338,7 @@
.menu-separator hr {
border: none;
border-bottom: 1px solid rgb(var(--modal-foreground-rgb), 0.2);
- margin: 3px;
+ margin: 4px 0;
}
}
@@ -251,7 +358,7 @@
max-width: 316px;
max-height: 10px;
height: 20%;
- background: #253559;
+ background: var(--ruffle-blue-dark);
}
.loadbar-inner {
@@ -299,31 +406,44 @@
.modal {
background-color: #0008;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 8px;
+ box-sizing: border-box;
+ overflow: auto;
}
.modal-area {
position: relative;
- left: 50%;
- transform: translateX(-50%);
background-color: var(--modal-background);
color: rgb(var(--modal-foreground-rgb));
width: fit-content;
- padding: 8px 12px;
+ padding: 40px 16px 16px;
border-radius: 12px;
+ overflow: hidden;
box-shadow: 0 2px 6px 0 #0008;
}
#modal-area {
width: 450px;
height: 300px;
+ padding: 0;
}
.close-modal {
width: 16px;
height: 16px;
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='16px' viewBox='0 -960 960 960' width='16px' fill='black'%3E%3Cpath d='M480-392 300-212q-18 18-44 18t-44-18q-18-18-18-44t18-44l180-180-180-180q-18-18-18-44t18-44q18-18 44-18t44 18l180 180 180-180q18-18 44-18t44 18q18 18 18 44t-18 44L568-480l180 180q18 18 18 44t-18 44q-18 18-44 18t-44-18L480-392Z'/%3E%3C/svg%3E");
+ background-image: url("data:image/svg+xml,");
cursor: pointer;
filter: var(--modal-foreground-filter);
+ border-radius: 4px;
+ opacity: 0.6;
+ transition: opacity 0.15s;
+
+ &:hover {
+ opacity: 1;
+ }
}
.modal-button {
@@ -334,6 +454,11 @@
padding: 4px 8px;
border-radius: 6px;
cursor: pointer;
+ transition: background-color 0.15s;
+
+ &:hover {
+ background-color: rgb(var(--modal-foreground-rgb), 0.3);
+ }
}
:not(#volume-controls) > .close-modal {
@@ -343,9 +468,12 @@
}
.general-save-options {
- text-align: center;
- padding-bottom: 8px;
- border-bottom: 2px solid rgb(var(--modal-foreground-rgb), 0.3);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 44px;
+ padding: 0 44px;
+ border-bottom: 1px solid rgb(var(--modal-foreground-rgb), 0.15);
}
#local-saves {
@@ -353,18 +481,40 @@
border-collapse: collapse;
overflow-y: auto;
display: block;
- height: calc(100% - 45px);
+ height: calc(100% - 44px);
min-height: 30px;
td {
- border-bottom: 2px solid rgb(var(--modal-foreground-rgb), 0.15);
- height: 30px;
+ border-bottom: 1px solid rgb(var(--modal-foreground-rgb), 0.08);
+ height: 34px;
+ padding: 0 6px;
}
td:nth-child(1) {
width: 100%;
word-break: break-all;
}
+
+ td:not(:first-child) {
+ padding: 0 2px;
+ }
+
+ > tr:hover td {
+ background-color: rgb(var(--modal-foreground-rgb), 0.05);
+ }
+
+ &::-webkit-scrollbar {
+ width: 8px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: rgb(var(--modal-foreground-rgb), 0.25);
+ border-radius: 3px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ }
}
.save-option {
@@ -374,11 +524,16 @@
cursor: pointer;
filter: var(--modal-foreground-filter);
vertical-align: middle;
- opacity: 0.4;
-}
+ opacity: 0.35;
+ transition: opacity 0.12s;
-#local-saves > tr:hover .save-option {
- opacity: 1;
+ :where(#local-saves tr:hover) & {
+ opacity: 0.65;
+ }
+
+ &:hover {
+ opacity: 1;
+ }
}
#download-save {
@@ -415,6 +570,10 @@
background-color: black;
}
+#volume-controls-modal .modal-area {
+ padding: 12px 16px;
+}
+
#volume-controls {
display: flex;
align-items: center;
@@ -449,6 +608,51 @@ label[for="mute-checkbox"] {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='9 -960 960 960' width='24px' fill='black'%3E%3Cpath d='M754.22-480.5q0-78.52-41.88-143.9-41.88-65.38-111.91-98.62-14.47-6.74-20.47-20.96-6-14.22-.53-28.93 5.74-15.72 20.34-22.46t29.58 0q92.48 42.46 147.97 127.05 55.48 84.6 55.48 187.82t-55.48 187.82q-55.49 84.59-147.97 127.05-14.98 6.74-29.58 0-14.6-6.74-20.34-22.46-5.47-14.71.53-28.93 6-14.22 20.47-20.96 70.03-33.24 111.91-98.62 41.88-65.38 41.88-143.9ZM286.98-357.87H170.2q-17.66 0-30.33-12.67-12.67-12.68-12.67-30.33v-158.26q0-17.65 12.67-30.33 12.67-12.67 30.33-12.67h116.78L416.65-731.8q20.63-20.64 47.1-9.57 26.47 11.07 26.47 39.65v443.44q0 28.58-26.47 39.65t-47.1-9.57L286.98-357.87ZM670.8-480q0 42.48-20.47 80.09-20.48 37.61-54.94 60.82-10.22 5.98-20.19.25-9.98-5.73-9.98-17.44v-248.44q0-11.71 9.98-17.32 9.97-5.61 20.19.37 34.46 23.71 54.94 61.45Q670.8-522.48 670.8-480Z'/%3E%3C/svg%3E");
}
+#volume-slider {
+ appearance: none;
+ height: 4px;
+ background: linear-gradient(
+ to right,
+ var(--ruffle-orange) var(--volume-pct, 100%),
+ rgb(var(--modal-foreground-rgb), 0.2) var(--volume-pct, 100%)
+ );
+ border-radius: 2px;
+ cursor: pointer;
+ outline: none;
+ border: none;
+
+ &::-webkit-slider-thumb {
+ appearance: none;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--ruffle-orange);
+ cursor: pointer;
+ border: none;
+ }
+
+ &::-moz-range-thumb {
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--ruffle-orange);
+ border: none;
+ cursor: pointer;
+ }
+
+ &:focus-visible::-webkit-slider-thumb {
+ box-shadow:
+ 0 0 0 3px var(--modal-background),
+ 0 0 0 5px var(--ruffle-orange);
+ }
+
+ &:focus-visible::-moz-range-thumb {
+ box-shadow:
+ 0 0 0 3px var(--modal-background),
+ 0 0 0 5px var(--ruffle-orange);
+ }
+}
+
#volume-slider-text {
width: 4.8ch;
text-align: center;
@@ -457,9 +661,7 @@ label[for="mute-checkbox"] {
#hardware-acceleration-modal .modal-area {
text-align: center;
- padding: 16px 48px;
- width: 95%;
- box-sizing: border-box;
+ padding: 48px 48px 24px;
}
#acceleration-text {
@@ -468,21 +670,23 @@ label[for="mute-checkbox"] {
}
#clipboard-modal h2 {
- margin-top: 4px;
- margin-right: 36px;
+ margin-top: 0;
+ margin-right: 24px;
}
#clipboard-modal p:last-child {
margin-bottom: 2px;
}
-/* Handle preferred color scheme. */
-@media (prefers-color-scheme: light) {
- :host {
- --modal-background: #fafafa;
- --modal-foreground-rgb: 0, 0, 0;
- --modal-foreground-filter: none;
- }
+#clipboard-modal b {
+ display: inline-block;
+ background-color: rgb(var(--modal-foreground-rgb), 0.1);
+ border: 1px solid rgb(var(--modal-foreground-rgb), 0.25);
+ border-radius: 4px;
+ padding: 1px 6px;
+ font-family: monospace;
+ font-size: 0.85em;
+ margin-right: 6px;
}
@media (prefers-color-scheme: dark) {
From 0ebf72305b21836b6ec456d442d9611fddff7d13 Mon Sep 17 00:00:00 2001
From: Robin <129759474+robinmiau@users.noreply.github.com>
Date: Tue, 21 Apr 2026 18:22:15 +0200
Subject: [PATCH 4/5] Fix formatting in inner.tsx
---
.../core/src/internal/player/inner.tsx | 25 +++++++++++--------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/web/packages/core/src/internal/player/inner.tsx b/web/packages/core/src/internal/player/inner.tsx
index 25291f25979b..863b47649e59 100644
--- a/web/packages/core/src/internal/player/inner.tsx
+++ b/web/packages/core/src/internal/player/inner.tsx
@@ -196,8 +196,7 @@ export class InnerPlayer {
// When set to `true`, the next context menu event will
// not show the context menu.
private _suppressContextMenu = false;
- private hideContextMenuOnWheel: ((event: WheelEvent) => void) | null =
- null;
+ private hideContextMenuOnWheel: ((event: WheelEvent) => void) | null = null;
// The effective config loaded upon `.load()`.
public loadedConfig?: URLLoadOptions | DataLoadOptions;
@@ -774,7 +773,9 @@ export class InnerPlayer {
// also match known software renderer names (WARP, SwiftShader, Mesa llvmpipe).
const isSoftwareRenderer =
this.rendererDebugInfo.includes("Adapter Device Type: Cpu") ||
- this.rendererDebugInfo.includes("Adapter Device Type: VirtualGpu") ||
+ this.rendererDebugInfo.includes(
+ "Adapter Device Type: VirtualGpu",
+ ) ||
this.rendererDebugInfo.includes("Microsoft Basic Render Driver") ||
this.rendererDebugInfo.includes("SwiftShader") ||
this.rendererDebugInfo.includes("llvmpipe") ||
@@ -1864,12 +1865,18 @@ export class InnerPlayer {
// when it would overflow, falling back to clamping if there's no room.
let cx = event.clientX;
if (cx + menuWidth > vw) {
- cx = event.clientX - menuWidth >= 0 ? event.clientX - menuWidth : vw - menuWidth;
+ cx =
+ event.clientX - menuWidth >= 0
+ ? event.clientX - menuWidth
+ : vw - menuWidth;
}
let cy = event.clientY;
if (cy + menuHeight > vh) {
- cy = event.clientY - menuHeight >= 0 ? event.clientY - menuHeight : vh - menuHeight;
+ cy =
+ event.clientY - menuHeight >= 0
+ ? event.clientY - menuHeight
+ : vh - menuHeight;
}
const x = cx - playerRect.x;
@@ -1893,11 +1900,9 @@ export class InnerPlayer {
this.contextMenuOverlay.classList.add("hidden");
this.contextMenuOpenPosition = null;
if (this.hideContextMenuOnWheel) {
- document.removeEventListener(
- "wheel",
- this.hideContextMenuOnWheel,
- { capture: true },
- );
+ document.removeEventListener("wheel", this.hideContextMenuOnWheel, {
+ capture: true,
+ });
this.hideContextMenuOnWheel = null;
}
}
From da0d73675c7fb4e4da4dcb9fdea028ed9e46fd67 Mon Sep 17 00:00:00 2001
From: Robin <129759474+robinmiau@users.noreply.github.com>
Date: Tue, 21 Apr 2026 18:22:53 +0200
Subject: [PATCH 5/5] Fix formatting in panic.tsx
---
web/packages/core/src/internal/ui/panic.tsx | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/web/packages/core/src/internal/ui/panic.tsx b/web/packages/core/src/internal/ui/panic.tsx
index eaadd1e09d96..10de8631468a 100644
--- a/web/packages/core/src/internal/ui/panic.tsx
+++ b/web/packages/core/src/internal/ui/panic.tsx
@@ -420,14 +420,8 @@ export function showPanicScreen(
)}
-