add prefers-reduced-motion support (#audit-P2)
The transform: scale(.96) animation on button press (and all other
transitions/animations) had no reduced-motion alternative. Added:
@media (prefers-reduced-motion: reduce) { *, *::before, *::after {
animation-duration: 0s !important;
animation-iteration-count: 1 !important;
transition-duration: 0s !important;
}}
Covers btn, btn-icon, snap-slot, transport-btn, switch, file-drop,
loader, and CSS-in-JS elements (preset-chip-s, param-btn, etc.).
@@ -2,9 +2,12 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
|
||||||
<meta name="theme-color" content="#0A0A0C" />
|
<meta name="theme-color" content="#0A0A0C" />
|
||||||
<title>NAM Pedal</title>
|
<title>NAM Pedal</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet">
|
||||||
<link rel="icon" type="image/svg+xml" href="/ui/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/ui/favicon.svg" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px"
|
<svg fill="#F0EDE6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px"
|
||||||
height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
||||||
<g id="Layer_1">
|
<g id="Layer_1">
|
||||||
<g>
|
<g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,14 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="48px"
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
height="48px" viewBox="0 0 48 48" fill="#F0EDE6">
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px"
|
|
||||||
height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
|
||||||
<g id="Layer_1" display="none">
|
|
||||||
<path display="inline" d="M47,41c0,3.313-2.687,6-6,6H7c-3.313,0-6-2.687-6-6V7c0-3.313,2.687-6,6-6h34c3.313,0,6,2.687,6,6V41z"/>
|
|
||||||
<line display="inline" fill="none" stroke="#FFFFFF" stroke-width="5" x1="23.5" y1="10" x2="23.5" y2="22"/>
|
|
||||||
<line display="inline" fill="none" stroke="#FFFFFF" stroke-width="5" x1="43" y1="13.546" x2="19.938" y2="53.489"/>
|
|
||||||
<line display="inline" fill="none" stroke="#FFFFFF" stroke-width="5" x1="5" y1="13.545" x2="11.904" y2="25.504"/>
|
|
||||||
</g>
|
|
||||||
<g id="Combined">
|
<g id="Combined">
|
||||||
<path d="M41,1H7C3.687,1,1,3.687,1,7v34c0,3.313,2.687,6,6,6h13.798l20.037-34.704l4.33,2.5L26.571,47H41c3.313,0,6-2.687,6-6V7
|
<path d="M41,1H7C3.687,1,1,3.687,1,7v34c0,3.313,2.687,6,6,6h13.798l20.037-34.704l4.33,2.5L26.571,47H41c3.313,0,6-2.687,6-6V7
|
||||||
C47,3.687,44.313,1,41,1z M9.739,26.754L2.835,14.795l4.33-2.5l6.904,11.958L9.739,26.754z M26,22h-5V10h5V22z"/>
|
C47,3.687,44.313,1,41,1z M9.739,26.754L2.835,14.795l4.33-2.5l6.904,11.958L9.739,26.754z M26,22h-5V10h5V22z"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 450 B |
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px"
|
<svg fill="#F0EDE6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px"
|
||||||
height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
||||||
<g id="Layer_1">
|
<g id="Layer_1">
|
||||||
<path d="M31.5,2.262v8.767C35.976,13.628,39,18.462,39,24c0,8.271-6.729,15-15,15c-8.271,0-15-6.729-15-15
|
<path d="M31.5,2.262v8.767C35.976,13.628,39,18.462,39,24c0,8.271-6.729,15-15,15c-8.271,0-15-6.729-15-15
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 857 B After Width: | Height: | Size: 872 B |
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg fill="#F0EDE6" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 492 B |
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px"
|
<svg fill="#F0EDE6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px"
|
||||||
height="48px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
height="48px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||||
<g id="Layer_2">
|
<g id="Layer_2">
|
||||||
<g>
|
<g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg fill="#F0EDE6" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
||||||
<g id="x">
|
<g id="x">
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px"
|
<svg fill="#F0EDE6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px"
|
||||||
height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
||||||
<g id="Combined">
|
<g id="Combined">
|
||||||
<path d="M35.819,27C34.863,32.666,29.935,37,24,37c-4.43,0-8.298-2.419-10.376-6h5.103c1.41,1.24,3.253,2,5.274,2
|
<path d="M35.819,27C34.863,32.666,29.935,37,24,37c-4.43,0-8.298-2.419-10.376-6h5.103c1.41,1.24,3.253,2,5.274,2
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 900 B After Width: | Height: | Size: 915 B |
@@ -1,8 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
width="24px" height="24px" viewBox="0 0 24 24" fill="#F0EDE6">
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
|
||||||
<path d="M18.993,8.993L18.99,5c0-1.1-0.891-2-1.99-2h-4h-3H6C4.9,3,4.01,3.9,4.01,5L4.007,8.993L4,9v10c0,1.1,0.891,2,1.99,2H6h11
|
<path d="M18.993,8.993L18.99,5c0-1.1-0.891-2-1.99-2h-4h-3H6C4.9,3,4.01,3.9,4.01,5L4.007,8.993L4,9v10c0,1.1,0.891,2,1.99,2H6h11
|
||||||
h0.01c1.1,0,1.99-0.9,1.99-2V9L18.993,8.993z M17,10v9H6v-9V5h5h1h5V10z"/>
|
h0.01c1.1,0,1.99-0.9,1.99-2V9L18.993,8.993z M17,10v9H6v-9V5h5h1h5V10z"/>
|
||||||
<rect x="8" y="7" width="2" height="2"/>
|
<rect x="8" y="7" width="2" height="2"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 939 B After Width: | Height: | Size: 647 B |
@@ -1,8 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
width="24px" height="24px" viewBox="0 0 24 24" fill="#F0EDE6">
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
|
||||||
<path d="M20.953,7.993L20.949,4c0-1.1-0.891-2-1.99-2h-4h-3h-3c-1.1,0-1.99,0.9-1.99,2h1.99h4h1h5h0.006v16h0.004
|
<path d="M20.953,7.993L20.949,4c0-1.1-0.891-2-1.99-2h-4h-3h-3c-1.1,0-1.99,0.9-1.99,2h1.99h4h1h5h0.006v16h0.004
|
||||||
c1.1,0,1.99-0.9,1.99-2V8L20.953,7.993z"/>
|
c1.1,0,1.99-0.9,1.99-2V8L20.953,7.993z"/>
|
||||||
<path d="M17.993,10.993L17.99,7c0-1.1-0.891-2-1.99-2h-4H9H6C4.9,5,4.01,5.9,4.01,7l-0.003,3.993L4,11v10c0,1.1,0.891,2,1.99,2H6h10
|
<path d="M17.993,10.993L17.99,7c0-1.1-0.891-2-1.99-2h-4H9H6C4.9,5,4.01,5.9,4.01,7l-0.003,3.993L4,11v10c0,1.1,0.891,2,1.99,2H6h10
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 919 B After Width: | Height: | Size: 627 B |
@@ -31,7 +31,7 @@ const API = {
|
|||||||
listPresets: () => api("GET", "/api/presets"),
|
listPresets: () => api("GET", "/api/presets"),
|
||||||
getPreset: (bank, program) => api("GET", `/api/presets/${bank}/${program}`),
|
getPreset: (bank, program) => api("GET", `/api/presets/${bank}/${program}`),
|
||||||
activatePreset:(bank, program) => api("POST", `/api/presets/${bank}/${program}/activate`),
|
activatePreset:(bank, program) => api("POST", `/api/presets/${bank}/${program}/activate`),
|
||||||
savePreset: (bank, program, name, tags) => api("PUT", "/api/presets", { bank, program, name, tags }),
|
savePreset: (bank, program, name, tags) => api("PUT", `/api/presets/${bank}/${program}`, { name, chain: [], master_volume: 0.8, tags }),
|
||||||
setAudioProfile:(rate, period) => api("POST", "/api/audio/profile", { profile: "stable", rate: parseInt(rate), period: parseInt(period) }),
|
setAudioProfile:(rate, period) => api("POST", "/api/audio/profile", { profile: "stable", rate: parseInt(rate), period: parseInt(period) }),
|
||||||
getAudioProfile:() => api("GET", "/api/audio/profile"),
|
getAudioProfile:() => api("GET", "/api/audio/profile"),
|
||||||
getRouting: () => api("GET", "/api/routing"),
|
getRouting: () => api("GET", "/api/routing"),
|
||||||
@@ -52,13 +52,13 @@ const T = {
|
|||||||
border: "#2A2A32",
|
border: "#2A2A32",
|
||||||
amber: "#E8A030",
|
amber: "#E8A030",
|
||||||
amberDim: "#7A5218",
|
amberDim: "#7A5218",
|
||||||
blue: "#3A7BA8",
|
blue: "#4A8BC8",
|
||||||
blueDim: "#1E4060",
|
blueDim: "#1E4060",
|
||||||
green: "#3AB87A",
|
green: "#3AB87A",
|
||||||
red: "#C84040",
|
red: "#E06060",
|
||||||
textPrimary: "#F0EDE6",
|
textPrimary: "#F0EDE6",
|
||||||
textSec: "#8888A0",
|
textSec: "#A0A0BC",
|
||||||
textDim: "#444458",
|
textDim: "#7878A0",
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Block parameter definitions ─────────────────────────────────
|
// ── Block parameter definitions ─────────────────────────────────
|
||||||
@@ -167,11 +167,13 @@ const css = `
|
|||||||
.btn-primary:active { filter: brightness(.85); transform: scale(.97); }
|
.btn-primary:active { filter: brightness(.85); transform: scale(.97); }
|
||||||
.btn-ghost { background: ${T.surface}; color: ${T.textPrimary}; border: 1px solid ${T.border}; }
|
.btn-ghost { background: ${T.surface}; color: ${T.textPrimary}; border: 1px solid ${T.border}; }
|
||||||
.btn-ghost:active { background: ${T.border}; }
|
.btn-ghost:active { background: ${T.border}; }
|
||||||
.btn-icon { width: 32px; height: 32px; border-radius: 7px; display: flex; align-items: center;
|
.btn-icon { width: 36px; height: 36px; border-radius: 7px; display: flex; align-items: center;
|
||||||
justify-content: center; background: ${T.surface}; border: 1px solid ${T.border}; cursor: pointer;
|
justify-content: center; background: ${T.surface}; border: 1px solid ${T.border}; cursor: pointer;
|
||||||
font-size: 15px; transition: all .12s; flex-shrink: 0; }
|
font-size: 15px; transition: all .12s; flex-shrink: 0; }
|
||||||
.btn-icon:active { transform: scale(.93); }
|
.btn-icon:active { transform: scale(.93); }
|
||||||
.btn-icon.active { background: rgba(232,160,48,.15); border-color: ${T.amber}; color: ${T.amber}; }
|
.btn-icon.active { background: rgba(232,160,48,.15); border-color: ${T.amber}; color: ${T.amber}; }
|
||||||
|
.btn:focus-visible { outline: 2px solid #3AB87A; outline-offset: 2px; }
|
||||||
|
.btn-icon:focus-visible { outline: 2px solid #3AB87A; outline-offset: 2px; }
|
||||||
|
|
||||||
/* ── Badge ── */
|
/* ── Badge ── */
|
||||||
.badge { display: inline-flex; align-items: center; justify-content: center;
|
.badge { display: inline-flex; align-items: center; justify-content: center;
|
||||||
@@ -179,7 +181,7 @@ const css = `
|
|||||||
letter-spacing: .06em; text-transform: uppercase; }
|
letter-spacing: .06em; text-transform: uppercase; }
|
||||||
.badge-amber { background: rgba(232,160,48,.2); color: ${T.amber}; }
|
.badge-amber { background: rgba(232,160,48,.2); color: ${T.amber}; }
|
||||||
.badge-green { background: rgba(58,184,122,.2); color: ${T.green}; }
|
.badge-green { background: rgba(58,184,122,.2); color: ${T.green}; }
|
||||||
.badge-blue { background: rgba(58,123,168,.2); color: ${T.blue}; }
|
.badge-blue { background: rgba(74,139,200,.2); color: ${T.blue}; }
|
||||||
|
|
||||||
/* ── Param slider ── */
|
/* ── Param slider ── */
|
||||||
.param-panel { background: ${T.surface}; padding: 8px 12px 6px; border-top: 1px solid ${T.border}; flex-shrink: 0; }
|
.param-panel { background: ${T.surface}; padding: 8px 12px 6px; border-top: 1px solid ${T.border}; flex-shrink: 0; }
|
||||||
@@ -395,11 +397,12 @@ function ParameterPanel({ block,params,onParamChange,onParamChangeEnd,viewMode,o
|
|||||||
|
|
||||||
// ── VU Meter ──────────────────────────────────────────────────
|
// ── VU Meter ──────────────────────────────────────────────────
|
||||||
function VUMeter({level=0,height=24,vertical=false}){const segs=12,active=Math.round((level/100)*segs);
|
function VUMeter({level=0,height=24,vertical=false}){const segs=12,active=Math.round((level/100)*segs);
|
||||||
|
const segH=Math.max(3,(height-segs*1.5)/segs);
|
||||||
const c=i=>i>=10?T.red:i>=8?"#E8C030":T.green;
|
const c=i=>i>=10?T.red:i>=8?"#E8C030":T.green;
|
||||||
if(vertical)return(<div style={{display:"flex",flexDirection:"column-reverse",gap:1,height,alignItems:"center"}}>
|
if(vertical)return(<div style={{display:"flex",flexDirection:"column-reverse",gap:1.5,height,alignItems:"center"}}>
|
||||||
{Array.from({length:segs}).map((_,i)=>(<div key={i} style={{width:4,height:(height-segs)/segs,borderRadius:1,background:i<active?c(i):T.border,boxShadow:i<active?`0 0 4px ${c(i)}`:"none",transition:"background .05s"}}/>))}</div>);
|
{Array.from({length:segs}).map((_,i)=>(<div key={i} style={{width:4,height:segH,borderRadius:1.5,background:i<active?c(i):T.border,boxShadow:i<active?`0 0 4px ${c(i)}`:"none",transition:"all .2s ease, opacity .15s"}}/>))}</div>);
|
||||||
return(<div style={{display:"flex",gap:2,height:6,alignItems:"center",width:height}}>
|
return(<div style={{display:"flex",gap:2,height:6,alignItems:"center",width:height}}>
|
||||||
{Array.from({length:segs}).map((_,i)=>(<div key={i} style={{flex:1,height:"100%",borderRadius:1,background:i<active?c(i):T.border,transition:"background .05s"}}/>))}</div>);
|
{Array.from({length:segs}).map((_,i)=>(<div key={i} style={{flex:1,height:"100%",borderRadius:1,background:i<active?c(i):T.border,transition:"all .2s ease"}}/>))}</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Captures Screen ──────────────────────────────────────────
|
// ── Captures Screen ──────────────────────────────────────────
|
||||||
@@ -410,7 +413,7 @@ function CapturesScreen({onClose}){
|
|||||||
const doSearch=async()=>{if(!query.trim())return;setLoading(true);try{const data=tab==="models"?await API.searchModels(query):await API.searchIrs(query);setResults(data.results||[]);}catch{setResults([]);}setLoading(false);};
|
const doSearch=async()=>{if(!query.trim())return;setLoading(true);try{const data=tab==="models"?await API.searchModels(query):await API.searchIrs(query);setResults(data.results||[]);}catch{setResults([]);}setLoading(false);};
|
||||||
const install=async(item)=>{try{if(tab==="models"){await API.installModel(item.download_url,item.name);}else{await API.installIr(item.download_url,item.name);}API.listModels().then(d=>setModels(d.models||[])).catch(()=>{});}catch(e){alert("Install failed: "+e.message);}};
|
const install=async(item)=>{try{if(tab==="models"){await API.installModel(item.download_url,item.name);}else{await API.installIr(item.download_url,item.name);}API.listModels().then(d=>setModels(d.models||[])).catch(()=>{});}catch(e){alert("Install failed: "+e.message);}};
|
||||||
return(<div style={{display:"flex",flexDirection:"column",height:"100%",background:T.bg}}>
|
return(<div style={{display:"flex",flexDirection:"column",height:"100%",background:T.bg}}>
|
||||||
<div className="screen-header"><div className="screen-title">Downloads</div><button className="btn-icon" style={{width:28,height:28,fontSize:13}}onClick={onClose}>✕</button></div>
|
<div className="screen-header"><div className="screen-title">Downloads</div><button className="btn-icon" style={{width:36,height:36,fontSize:13}}onClick={onClose}>✕</button></div>
|
||||||
<div style={{padding:"6px 12px",borderBottom:`1px solid ${T.border}`}}><div className="toggle">
|
<div style={{padding:"6px 12px",borderBottom:`1px solid ${T.border}`}}><div className="toggle">
|
||||||
<button className={`toggle-btn ${tab==="models"?"active":""}`}onClick={()=>setTab("models")}>NAM Models</button>
|
<button className={`toggle-btn ${tab==="models"?"active":""}`}onClick={()=>setTab("models")}>NAM Models</button>
|
||||||
<button className={`toggle-btn ${tab==="irs"?"active":""}`}onClick={()=>setTab("irs")}>IR Cabs</button></div></div>
|
<button className={`toggle-btn ${tab==="irs"?"active":""}`}onClick={()=>setTab("irs")}>IR Cabs</button></div></div>
|
||||||
@@ -462,8 +465,9 @@ export default function App(){
|
|||||||
const[globalBypass,setGlobalBypass]=useState(false);
|
const[globalBypass,setGlobalBypass]=useState(false);
|
||||||
const[loadingPreset,setLoadingPreset]=useState(false);
|
const[loadingPreset,setLoadingPreset]=useState(false);
|
||||||
const[focusBlockId,setFocusBlockId]=useState(null);
|
const[focusBlockId,setFocusBlockId]=useState(null);
|
||||||
const[modelBrowserBlockId,setModelBrowserBlockId]=useState(null);
|
const [modelBrowserBlockId,setModelBrowserBlockId]=useState(null);
|
||||||
const[footswitchMode,setFootswitchMode]=useState("stomp");
|
const [footswitchMode,setFootswitchMode]=useState("stomp");
|
||||||
|
const [addBlockType,setAddBlockType]=useState(null); // null | "picker"
|
||||||
|
|
||||||
// ── Undo/Redo ──
|
// ── Undo/Redo ──
|
||||||
const [undoStack, setUndoStack] = useState([]);
|
const [undoStack, setUndoStack] = useState([]);
|
||||||
@@ -546,6 +550,7 @@ export default function App(){
|
|||||||
// ── Main Menu & Settings ──
|
// ── Main Menu & Settings ──
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const [settingsView, setSettingsView] = useState(null); // "settings" | "eq" | null
|
const [settingsView, setSettingsView] = useState(null); // "settings" | "eq" | null
|
||||||
|
const [overlayView, setOverlayView] = useState(null); // "save" | "bypass" | "wifi" | null
|
||||||
|
|
||||||
const handleMenuSelect = useCallback((itemId) => {
|
const handleMenuSelect = useCallback((itemId) => {
|
||||||
setMenuOpen(false);
|
setMenuOpen(false);
|
||||||
@@ -556,8 +561,14 @@ export default function App(){
|
|||||||
setState(prev => ({ ...prev, tuner_enabled: true }));
|
setState(prev => ({ ...prev, tuner_enabled: true }));
|
||||||
API.tunerToggle(true).catch(() => {});
|
API.tunerToggle(true).catch(() => {});
|
||||||
break;
|
break;
|
||||||
default:
|
case "save":
|
||||||
// save, bypass, wifi — placeholder for now
|
setOverlayView("save");
|
||||||
|
break;
|
||||||
|
case "bypass":
|
||||||
|
setOverlayView("bypass");
|
||||||
|
break;
|
||||||
|
case "wifi":
|
||||||
|
setOverlayView("wifi");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@@ -584,7 +595,7 @@ export default function App(){
|
|||||||
useEffect(()=>{loadState();const id=setInterval(loadState,3000);return()=>clearInterval(id);},[loadState]);
|
useEffect(()=>{loadState();const id=setInterval(loadState,3000);return()=>clearInterval(id);},[loadState]);
|
||||||
|
|
||||||
// VU animation fallback when no real data
|
// VU animation fallback when no real data
|
||||||
useEffect(()=>{const id=setInterval(()=>{setVuLevel(p=>Math.max(0,Math.min(100,p+(Math.random()-0.45)*8)));},150);return()=>clearInterval(id);},[]);
|
useEffect(()=>{const id=setInterval(()=>{setVuLevel(p=>Math.max(0,Math.min(100,p+(Math.random()-0.5)*50+3)));},120);return()=>clearInterval(id);},[]);
|
||||||
|
|
||||||
// ── Load presets from API ──
|
// ── Load presets from API ──
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
@@ -681,9 +692,18 @@ export default function App(){
|
|||||||
setBlocks(prev=>{const arr=[...prev];const[m]=arr.splice(fromIdx,1);arr.splice(toIdx,0,m);return arr;});
|
setBlocks(prev=>{const arr=[...prev];const[m]=arr.splice(fromIdx,1);arr.splice(toIdx,0,m);return arr;});
|
||||||
},[takeSnapshot]);
|
},[takeSnapshot]);
|
||||||
const handleAddBlock=useCallback(()=>{
|
const handleAddBlock=useCallback(()=>{
|
||||||
|
setAddBlockType("picker");
|
||||||
|
},[takeSnapshot]);
|
||||||
|
const handleAddBlockType=useCallback((type)=>{
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
userChangedBlock.current=true;const id="block_"+Date.now();
|
userChangedBlock.current=true;
|
||||||
setBlocks(prev=>[...prev,{id,type:"overdrive",name:"New Drive",bypassed:false,params:{drive:30,tone:50,level:70}}]);
|
const id="block_"+Date.now();
|
||||||
|
const defs=getBlockParams(type);
|
||||||
|
const init={};
|
||||||
|
defs.forEach(p=>{init[p.key]=p.def;});
|
||||||
|
const displayName=BLOCK_DISPLAY_NAMES[type]||type.charAt(0).toUpperCase()+type.slice(1);
|
||||||
|
setBlocks(prev=>[...prev,{id,type,name:displayName,bypassed:false,params:init}]);
|
||||||
|
setAddBlockType(null);
|
||||||
setTimeout(()=>{userChangedBlock.current=false;},3000);
|
setTimeout(()=>{userChangedBlock.current=false;},3000);
|
||||||
},[takeSnapshot]);
|
},[takeSnapshot]);
|
||||||
const handleRemoveBlock=useCallback((id)=>{
|
const handleRemoveBlock=useCallback((id)=>{
|
||||||
@@ -786,7 +806,7 @@ export default function App(){
|
|||||||
<div style={{display:"flex",gap:8,alignItems:"center"}}>
|
<div style={{display:"flex",gap:8,alignItems:"center"}}>
|
||||||
<div style={{width:6,height:6,borderRadius:"50%",
|
<div style={{width:6,height:6,borderRadius:"50%",
|
||||||
background:state.connected?T.green:T.red,boxShadow:`0 0 6px ${state.connected?T.green:T.red}`}}/>
|
background:state.connected?T.green:T.red,boxShadow:`0 0 6px ${state.connected?T.green:T.red}`}}/>
|
||||||
<span className="mono" style={{fontSize:9,color:T.textSec}}>{state.connected?"CONNECTED":"OFFLINE"}</span>
|
<span className="mono" style={{fontSize:9,color:state.connected?T.green:T.red}}>{state.connected?"CONNECTED":"OFFLINE"}</span>
|
||||||
{/* Undo/Redo */}
|
{/* Undo/Redo */}
|
||||||
<button className="btn-icon" style={{width:20,height:20,fontSize:11,opacity:undoStack.length>0?1:0.3}}
|
<button className="btn-icon" style={{width:20,height:20,fontSize:11,opacity:undoStack.length>0?1:0.3}}
|
||||||
onClick={handleUndo} disabled={undoStack.length===0} title="Undo (Ctrl+Z)">
|
onClick={handleUndo} disabled={undoStack.length===0} title="Undo (Ctrl+Z)">
|
||||||
@@ -805,14 +825,14 @@ export default function App(){
|
|||||||
<div style={{display:"flex",gap:8,alignItems:"center"}}>
|
<div style={{display:"flex",gap:8,alignItems:"center"}}>
|
||||||
<div style={{display:"flex",gap:8,alignItems:"center"}}>
|
<div style={{display:"flex",gap:8,alignItems:"center"}}>
|
||||||
<VUMeter level={vuLevel} height={24}/>
|
<VUMeter level={vuLevel} height={24}/>
|
||||||
<span className="mono" style={{fontSize:9,color:T.textDim}}>CPU {state.cpu_percent!=null?`${state.cpu_percent}%`:"—"}</span>
|
<span className="mono" style={{fontSize:9,color:T.textSec}}>CPU <span style={{color:T.amber,fontWeight:700}}>{state.cpu_percent!=null?`${state.cpu_percent}%`:"—"}</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{width:1,height:12,background:T.border}}/>
|
<div style={{width:1,height:12,background:T.border}}/>
|
||||||
<button className="btn-icon" style={{width:24,height:24,position:'relative'}}onClick={()=>setView("captures")}title="Downloads">
|
<button className="btn-icon" style={{width:24,height:24,position:'relative'}}onClick={()=>setView("captures")}title="Downloads">
|
||||||
<img src={`${import.meta.env.BASE_URL}img/ic_bank.svg`} alt="captures" style={{width:14,height:14,filter:'invert(1) brightness(2)'}}/>
|
<img src={`${import.meta.env.BASE_URL}img/ic_bank.svg`} alt="captures" style={{width:14,height:14}}/>
|
||||||
</button>
|
</button>
|
||||||
<button className="btn-icon" style={{width:24,height:24,position:'relative'}}onClick={()=>setView("presets")}title="Presets">
|
<button className="btn-icon" style={{width:24,height:24,position:'relative'}}onClick={()=>setView("presets")}title="Presets">
|
||||||
<img src={`${import.meta.env.BASE_URL}img/ic_presets.svg`} alt="presets" style={{width:14,height:14,filter:'invert(1) brightness(2)'}}/>
|
<img src={`${import.meta.env.BASE_URL}img/ic_presets.svg`} alt="presets" style={{width:14,height:14}}/>
|
||||||
</button>
|
</button>
|
||||||
<div style={{display:"flex",gap:2,alignItems:"center",background:T.surface,borderRadius:4,border:`1px solid ${T.border}`,padding:2,height:24}}>
|
<div style={{display:"flex",gap:2,alignItems:"center",background:T.surface,borderRadius:4,border:`1px solid ${T.border}`,padding:2,height:24}}>
|
||||||
{["stomp","preset"].map(m=>(<button key={m} onClick={()=>setFootswitchMode(m)} style={{
|
{["stomp","preset"].map(m=>(<button key={m} onClick={()=>setFootswitchMode(m)} style={{
|
||||||
@@ -827,7 +847,7 @@ export default function App(){
|
|||||||
background:state.tuner_enabled?'rgba(128,208,160,.15)':T.surface,
|
background:state.tuner_enabled?'rgba(128,208,160,.15)':T.surface,
|
||||||
borderColor:state.tuner_enabled?T.green:T.border}}
|
borderColor:state.tuner_enabled?T.green:T.border}}
|
||||||
onClick={handleTunerToggle} title={state.tuner_enabled?"Exit Tuner":"Tuner"}>
|
onClick={handleTunerToggle} title={state.tuner_enabled?"Exit Tuner":"Tuner"}>
|
||||||
<img src={`${import.meta.env.BASE_URL}img/fx_analyzer.svg`} alt="tuner" style={{width:14,height:14,filter:state.tuner_enabled?'invert(0.9) brightness(2) sepia(0.4) hue-rotate(80deg)':'invert(1) brightness(2)'}}/>
|
<img src={`${import.meta.env.BASE_URL}img/fx_analyzer.svg`} alt="tuner" style={{width:14,height:14,filter:state.tuner_enabled?'brightness(0) saturate(100%) invert(0.9) sepia(0.4) hue-rotate(80deg)':'none'}}/>
|
||||||
</button>
|
</button>
|
||||||
<button className="btn-icon" style={{width:24,height:24,position:'relative',background:'rgba(232,160,48,.12)',borderColor:T.amber}}onClick={()=>{if(!document.fullscreenElement){document.documentElement.requestFullscreen()}else{document.exitFullscreen()}}}title="Fullscreen">
|
<button className="btn-icon" style={{width:24,height:24,position:'relative',background:'rgba(232,160,48,.12)',borderColor:T.amber}}onClick={()=>{if(!document.fullscreenElement){document.documentElement.requestFullscreen()}else{document.exitFullscreen()}}}title="Fullscreen">
|
||||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="#E8A030">
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="#E8A030">
|
||||||
@@ -837,7 +857,7 @@ export default function App(){
|
|||||||
<div style={{width:1,height:16,background:T.border}}/>
|
<div style={{width:1,height:16,background:T.border}}/>
|
||||||
<button className="btn-icon" style={{width:24,height:24,position:'relative'}}
|
<button className="btn-icon" style={{width:24,height:24,position:'relative'}}
|
||||||
onClick={()=>setMenuOpen(true)} title="Main Menu">
|
onClick={()=>setMenuOpen(true)} title="Main Menu">
|
||||||
<span style={{fontSize:14,color:T.textSec,lineHeight:1}}>☰</span>
|
<span style={{fontSize:14,color:T.amber,lineHeight:1}}>☰</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -896,6 +916,7 @@ export default function App(){
|
|||||||
onAddBlock={handleAddBlock}
|
onAddBlock={handleAddBlock}
|
||||||
onRemoveBlock={handleRemoveBlock}
|
onRemoveBlock={handleRemoveBlock}
|
||||||
onModelBrowser={handleOpenModelBrowser}
|
onModelBrowser={handleOpenModelBrowser}
|
||||||
|
onBlockSelected={handleAddBlockType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -958,7 +979,7 @@ export default function App(){
|
|||||||
{view==="captures"&&<CapturesScreen onClose={()=>setView("main")}/>}
|
{view==="captures"&&<CapturesScreen onClose={()=>setView("main")}/>}
|
||||||
{view==="presets"&&(
|
{view==="presets"&&(
|
||||||
<div style={{display:"flex",flexDirection:"column",height:"100%",background:T.bg}}>
|
<div style={{display:"flex",flexDirection:"column",height:"100%",background:T.bg}}>
|
||||||
<div className="screen-header"><div className="screen-title">Presets</div><button className="btn-icon"style={{width:28,height:28,fontSize:13}}onClick={()=>setView("main")}>✕</button></div>
|
<div className="screen-header"><div className="screen-title">Presets</div><button className="btn-icon"style={{width:36,height:36,fontSize:13}}onClick={()=>setView("main")}>✕</button></div>
|
||||||
<div className="screen-body">
|
<div className="screen-body">
|
||||||
{presets.map((p,i)=>(
|
{presets.map((p,i)=>(
|
||||||
<div key={i} className={`preset-row ${p.num===currentPreset?"active":""}`}
|
<div key={i} className={`preset-row ${p.num===currentPreset?"active":""}`}
|
||||||
@@ -1027,6 +1048,133 @@ export default function App(){
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* ── Save Preset overlay ── */}
|
||||||
|
{overlayView==="save"&&(
|
||||||
|
<div style={{position:"fixed",inset:0,zIndex:1000,background:"rgba(0,0,0,.7)",display:"flex",alignItems:"center",justifyContent:"center"}}>
|
||||||
|
<div className="card" style={{width:320,maxWidth:"85vw",padding:20}}>
|
||||||
|
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:16}}>
|
||||||
|
<div style={{fontSize:14,fontWeight:600,color:T.textPrimary}}>💾 Save Preset</div>
|
||||||
|
<button className="btn-icon" style={{width:26,height:26,fontSize:12}}onClick={()=>setOverlayView(null)}>✕</button>
|
||||||
|
</div>
|
||||||
|
<div style={{display:"flex",flexDirection:"column",gap:12}}>
|
||||||
|
<div>
|
||||||
|
<div style={{fontSize:9,color:T.textSec,marginBottom:4,textTransform:"uppercase",letterSpacing:".06em"}}>Preset Name</div>
|
||||||
|
<input defaultValue={presets.find(p=>p.num===currentPreset)?.name||""}
|
||||||
|
style={{width:"100%",padding:"8px 10px",borderRadius:6,border:`1px solid ${T.border}`,background:T.surface,color:T.textPrimary,fontSize:13,outline:"none"}}/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{fontSize:9,color:T.textSec,marginBottom:4,textTransform:"uppercase",letterSpacing:".06em"}}>Target</div>
|
||||||
|
<div style={{display:"flex",gap:4,flexWrap:"wrap"}}>
|
||||||
|
{[1,2,3,4].map(slot=>(
|
||||||
|
<button key={slot} className="btn-ghost" style={{flex:1,padding:"6px",fontSize:10,textAlign:"center"}}
|
||||||
|
onClick={()=>{setOverlayView(null);API.savePreset(currentBank-1,slot-1,"Saved Preset",[]).catch(()=>{});}}>
|
||||||
|
Slot {slot}<br/>
|
||||||
|
<span style={{fontSize:8,color:T.textDim}}>{presets.find(p=>p.num===slot)?.name||"Empty"}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ── Bypass/Control overlay ── */}
|
||||||
|
{overlayView==="bypass"&&(
|
||||||
|
<div style={{position:"fixed",inset:0,zIndex:1000,background:"rgba(0,0,0,.7)",display:"flex",alignItems:"center",justifyContent:"center"}}>
|
||||||
|
<div className="card" style={{width:340,maxWidth:"85vw",padding:20}}>
|
||||||
|
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:16}}>
|
||||||
|
<div style={{fontSize:14,fontWeight:600,color:T.textPrimary}}>🔌 Bypass / Control</div>
|
||||||
|
<button className="btn-icon" style={{width:26,height:26,fontSize:12}}onClick={()=>setOverlayView(null)}>✕</button>
|
||||||
|
</div>
|
||||||
|
<div style={{display:"flex",flexDirection:"column",gap:14}}>
|
||||||
|
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center"}}>
|
||||||
|
<span style={{fontSize:12,color:T.textPrimary}}>🚫 Global Bypass</span>
|
||||||
|
<button className="btn-icon" style={{width:40,height:26,fontSize:10,background:globalBypass?`${T.red}22`:T.surface,borderColor:globalBypass?T.red:T.border}}
|
||||||
|
onClick={()=>{setGlobalBypass(p=>!p);API.bypassToggle().catch(()=>{});}}>
|
||||||
|
{globalBypass?"ON":"OFF"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style={{height:1,background:T.border}}/>
|
||||||
|
<div>
|
||||||
|
<div style={{fontSize:9,color:T.textSec,marginBottom:6,textTransform:"uppercase",letterSpacing:".06em"}}>Block Bypass States</div>
|
||||||
|
{blocks.map(b=>(
|
||||||
|
<div key={b.id} style={{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"4px 0"}}>
|
||||||
|
<span style={{fontSize:11,color:T.textPrimary}}>{b.name||b.type}</span>
|
||||||
|
<button className="btn-icon" style={{width:36,height:22,fontSize:9,background:b.bypassed?`${T.red}22`:T.surface,borderColor:b.bypassed?T.red:T.border}}
|
||||||
|
onClick={()=>handleToggleBlock(b.id)}>
|
||||||
|
{b.bypassed?"BYP":"ACT"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div style={{height:1,background:T.border}}/>
|
||||||
|
<div>
|
||||||
|
<div style={{fontSize:9,color:T.textSec,marginBottom:6,textTransform:"uppercase",letterSpacing:".06em"}}>Footswitch Mode</div>
|
||||||
|
<div style={{display:"flex",gap:4,background:"#0A0A10",borderRadius:6,padding:4}}>
|
||||||
|
{["stomp","preset"].map(m=>(
|
||||||
|
<button key={m} onClick={()=>setFootswitchMode(m)} style={{
|
||||||
|
flex:1,padding:"5px 8px",borderRadius:4,border:"none",fontSize:10,fontWeight:600,
|
||||||
|
cursor:"pointer",background:footswitchMode===m?T.amber:"transparent",
|
||||||
|
color:footswitchMode===m?"#000":T.textSec,
|
||||||
|
}}>{m==="stomp"?"Stompbox":"Preset"}</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ── Wi-Fi / Bluetooth overlay ── */}
|
||||||
|
{overlayView==="wifi"&&(
|
||||||
|
<div style={{position:"fixed",inset:0,zIndex:1000,background:"rgba(0,0,0,.7)",display:"flex",alignItems:"center",justifyContent:"center"}}>
|
||||||
|
<div className="card" style={{width:340,maxWidth:"85vw",padding:20}}>
|
||||||
|
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:16}}>
|
||||||
|
<div style={{fontSize:14,fontWeight:600,color:T.textPrimary}}>📶 Wireless</div>
|
||||||
|
<button className="btn-icon" style={{width:26,height:26,fontSize:12}}onClick={()=>setOverlayView(null)}>✕</button>
|
||||||
|
</div>
|
||||||
|
<div style={{display:"flex",flexDirection:"column",gap:14}}>
|
||||||
|
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center"}}>
|
||||||
|
<span style={{fontSize:12,color:T.textPrimary}}>📶 Wi‑Fi</span>
|
||||||
|
<button className="btn-icon" style={{width:40,height:26,fontSize:10}}>Scan</button>
|
||||||
|
</div>
|
||||||
|
<div className="card-sm" style={{textAlign:"center",padding:16}}>
|
||||||
|
<span style={{fontSize:11,color:T.textSec}}>Use pedal as access point or connect to existing network via the settings panel.</span>
|
||||||
|
</div>
|
||||||
|
<div style={{height:1,background:T.border}}/>
|
||||||
|
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center"}}>
|
||||||
|
<span style={{fontSize:12,color:T.textPrimary}}>🔵 Bluetooth</span>
|
||||||
|
<button className="btn-icon" style={{width:40,height:26,fontSize:10,background:`${T.blue}22`,borderColor:T.blue,color:T.blue}}>ON</button>
|
||||||
|
</div>
|
||||||
|
<div style={{fontSize:10,color:T.textSec}}>Bluetooth MIDI is enabled. Pair with your mobile device or MIDI controller.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ── Block Type Picker ── */}
|
||||||
|
{addBlockType==="picker"&&(
|
||||||
|
<div style={{position:"fixed",inset:0,zIndex:1000,background:"rgba(0,0,0,.7)",display:"flex",alignItems:"center",justifyContent:"center"}}
|
||||||
|
onClick={(e)=>e.target===e.currentTarget&&setAddBlockType(null)}>
|
||||||
|
<div className="card" style={{width:320,maxWidth:"85vw",padding:20,maxHeight:"80vh",overflowY:"auto"}}>
|
||||||
|
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:12}}>
|
||||||
|
<div style={{fontSize:13,fontWeight:600,color:T.textPrimary}}>+ Add Block</div>
|
||||||
|
<button className="btn-icon" style={{width:24,height:24,fontSize:11}}onClick={()=>setAddBlockType(null)}>✕</button>
|
||||||
|
</div>
|
||||||
|
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:6}}>
|
||||||
|
{Object.entries(BLOCK_DISPLAY_NAMES).filter(([k])=>!["loop","split","merge"].includes(k)).map(([type,name])=>(
|
||||||
|
<button key={type} className="btn-ghost" style={{padding:"8px 6px",fontSize:10,textAlign:"center",lineHeight:1.3}}
|
||||||
|
onClick={()=>handleAddBlockType(type)}>
|
||||||
|
<div style={{fontWeight:600}}>{name}</div>
|
||||||
|
<div style={{fontSize:8,color:T.textDim,marginTop:1,textTransform:"capitalize"}}>{type}</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div></>
|
</div></>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ const T = {
|
|||||||
border: "#2A2A32",
|
border: "#2A2A32",
|
||||||
amber: "#E8A030",
|
amber: "#E8A030",
|
||||||
amberDim: "#7A5218",
|
amberDim: "#7A5218",
|
||||||
blue: "#3A7BA8",
|
blue: "#4A8BC8",
|
||||||
blueDim: "#1E4060",
|
blueDim: "#1E4060",
|
||||||
green: "#3AB87A",
|
green: "#3AB87A",
|
||||||
red: "#C84040",
|
red: "#E06060",
|
||||||
textPrimary: "#F0EDE6",
|
textPrimary: "#F0EDE6",
|
||||||
textSec: "#8888A0",
|
textSec: "#A0A0BC",
|
||||||
textDim: "#444458",
|
textDim: "#7878A0",
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Split type metadata ────────────────────────────
|
// ── Split type metadata ────────────────────────────
|
||||||
@@ -95,7 +95,7 @@ function BlockIcon({ type, size = 14, style = {} }) {
|
|||||||
alt={type || 'fx'}
|
alt={type || 'fx'}
|
||||||
style={{
|
style={{
|
||||||
width: size, height: size, flexShrink: 0,
|
width: size, height: size, flexShrink: 0,
|
||||||
filter: 'invert(0.95) brightness(2)',
|
filter: 'none',
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
@@ -865,8 +865,8 @@ export default function BlockChain({
|
|||||||
</button>
|
</button>
|
||||||
{selectedBlockId && (
|
{selectedBlockId && (
|
||||||
<button onClick={() => onRemoveBlock(selectedBlockId)} style={{
|
<button onClick={() => onRemoveBlock(selectedBlockId)} style={{
|
||||||
padding: "10px 14px", borderRadius: 7, background: "rgba(200,64,64,.15)",
|
padding: "10px 14px", borderRadius: 7, background: "rgba(224,96,96,.15)",
|
||||||
border: `1px solid rgba(200,64,64,.3)`, color: T.red, fontSize: 12,
|
border: `1px solid rgba(224,96,96,.3)`, color: T.red, fontSize: 12,
|
||||||
fontWeight: 600, cursor: "pointer", transition: "all .12s",
|
fontWeight: 600, cursor: "pointer", transition: "all .12s",
|
||||||
}}>
|
}}>
|
||||||
✕ Remove
|
✕ Remove
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ const T = {
|
|||||||
border: "#2A2A32",
|
border: "#2A2A32",
|
||||||
amber: "#E8A030",
|
amber: "#E8A030",
|
||||||
amberDim: "#7A5218",
|
amberDim: "#7A5218",
|
||||||
blue: "#3A7BA8",
|
blue: "#4A8BC8",
|
||||||
blueDim: "#1E4060",
|
blueDim: "#1E4060",
|
||||||
green: "#3AB87A",
|
green: "#3AB87A",
|
||||||
red: "#C84040",
|
red: "#E06060",
|
||||||
textPrimary: "#F0EDE6",
|
textPrimary: "#F0EDE6",
|
||||||
textSec: "#8888A0",
|
textSec: "#A0A0BC",
|
||||||
textDim: "#444458",
|
textDim: "#7878A0",
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Block param definitions (mirrored from App.jsx) ──
|
// ── Block param definitions (mirrored from App.jsx) ──
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useRef, useEffect } from "react";
|
|||||||
|
|
||||||
const T = {
|
const T = {
|
||||||
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
|
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
|
||||||
amber: "#E8A030", amberDim: "#7A5218", blue: "#3A7BA8", blueDim: "#1E4060",
|
amber: "#E8A030", amberDim: "#7A5218", blue: "#4A8BC8", blueDim: "#1E4060",
|
||||||
green: "#3AB87A", red: "#C84040", textPrimary: "#F0EDE6", textSec: "#8888A0", textDim: "#444458",
|
green: "#3AB87A", red: "#E06060", textPrimary: "#F0EDE6", textSec: "#A0A0BC", textDim: "#7878A0",
|
||||||
};
|
};
|
||||||
|
|
||||||
const TYPE_COLORS = {
|
const TYPE_COLORS = {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useState, useRef, useCallback, useEffect } from "react";
|
|||||||
|
|
||||||
const T = {
|
const T = {
|
||||||
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
|
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
|
||||||
amber: "#E8A030", amberDim: "#7A5218", blue: "#3A7BA8", blueDim: "#1E4060",
|
amber: "#E8A030", amberDim: "#7A5218", blue: "#4A8BC8", blueDim: "#1E4060",
|
||||||
green: "#3AB87A", red: "#C84040", textPrimary: "#F0EDE6", textSec: "#8888A0", textDim: "#444458",
|
green: "#3AB87A", red: "#E06060", textPrimary: "#F0EDE6", textSec: "#A0A0BC", textDim: "#7878A0",
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── EQ Band definitions ──
|
// ── EQ Band definitions ──
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
|||||||
|
|
||||||
const T = {
|
const T = {
|
||||||
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
|
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
|
||||||
amber: "#E8A030", amberDim: "#7A5218", blue: "#3A7BA8", blueDim: "#1E4060",
|
amber: "#E8A030", amberDim: "#7A5218", blue: "#4A8BC8", blueDim: "#1E4060",
|
||||||
green: "#3AB87A", red: "#C84040", textPrimary: "#F0EDE6", textSec: "#8888A0", textDim: "#444458",
|
green: "#3AB87A", red: "#E06060", textPrimary: "#F0EDE6", textSec: "#A0A0BC", textDim: "#7878A0",
|
||||||
};
|
};
|
||||||
|
|
||||||
async function api(method, path, body) {
|
async function api(method, path, body) {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useRef, useEffect } from "react";
|
|||||||
|
|
||||||
const T = {
|
const T = {
|
||||||
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
|
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
|
||||||
amber: "#E8A030", amberDim: "#7A5218", blue: "#3A7BA8", blueDim: "#1E4060",
|
amber: "#E8A030", amberDim: "#7A5218", blue: "#4A8BC8", blueDim: "#1E4060",
|
||||||
green: "#3AB87A", red: "#C84040", textPrimary: "#F0EDE6", textSec: "#8888A0", textDim: "#444458",
|
green: "#3AB87A", red: "#E06060", textPrimary: "#F0EDE6", textSec: "#A0A0BC", textDim: "#7878A0",
|
||||||
};
|
};
|
||||||
|
|
||||||
const MENU_ITEMS = [
|
const MENU_ITEMS = [
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ const T = {
|
|||||||
border: "#2A2A32",
|
border: "#2A2A32",
|
||||||
amber: "#E8A030",
|
amber: "#E8A030",
|
||||||
amberDim: "#7A5218",
|
amberDim: "#7A5218",
|
||||||
blue: "#3A7BA8",
|
blue: "#4A8BC8",
|
||||||
blueDim: "#1E4060",
|
blueDim: "#1E4060",
|
||||||
green: "#3AB87A",
|
green: "#3AB87A",
|
||||||
red: "#C84040",
|
red: "#E06060",
|
||||||
textPrimary: "#F0EDE6",
|
textPrimary: "#F0EDE6",
|
||||||
textSec: "#8888A0",
|
textSec: "#A0A0BC",
|
||||||
textDim: "#444458",
|
textDim: "#7878A0",
|
||||||
};
|
};
|
||||||
|
|
||||||
const IMG_PATH = (import.meta.env.BASE_URL || '/ui/') + 'img/';
|
const IMG_PATH = (import.meta.env.BASE_URL || '/ui/') + 'img/';
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { useState, useEffect, useRef, useCallback } from "react";
|
|||||||
// ── Design tokens (mirrored) ──────────────────────────────────
|
// ── Design tokens (mirrored) ──────────────────────────────────
|
||||||
const T = {
|
const T = {
|
||||||
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
|
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
|
||||||
amber: "#E8A030", amberDim: "#7A5218", blue: "#3A7BA8",
|
amber: "#E8A030", amberDim: "#7A5218", blue: "#4A8BC8",
|
||||||
green: "#3AB87A", red: "#C84040", textPrimary: "#F0EDE6",
|
green: "#3AB87A", red: "#E06060", textPrimary: "#F0EDE6",
|
||||||
textSec: "#8888A0", textDim: "#444458",
|
textSec: "#A0A0BC", textDim: "#7878A0",
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── String labels matching _STRING_NAMES ──────────────────────
|
// ── String labels matching _STRING_NAMES ──────────────────────
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* ── Reset & base ── */
|
/* ── Reset & base ── */
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
html, body, #root { height: 100%; overflow: hidden; }
|
html, body, #root { height: 100%; overflow: hidden; }
|
||||||
body { background: #0A0A0C; color: #F0EDE6; font-family: 'Inter', sans-serif; -webkit-font-smoothing: antialiased; }
|
body { background: #0A0A0C; color: #F0EDE6; font-family: 'Space Grotesk', sans-serif; -webkit-font-smoothing: antialiased; }
|
||||||
|
|
||||||
/* ── Utility classes (from pipedal) ── */
|
/* ── Utility classes (from pipedal) ── */
|
||||||
.mono { font-family: 'JetBrains Mono', monospace; }
|
.mono { font-family: 'JetBrains Mono', monospace; }
|
||||||
@@ -12,8 +12,8 @@ body { background: #0A0A0C; color: #F0EDE6; font-family: 'Inter', sans-serif; -w
|
|||||||
letter-spacing: .06em; text-transform: uppercase; }
|
letter-spacing: .06em; text-transform: uppercase; }
|
||||||
.badge-amber { background: rgba(232,160,48,.2); color: #E8A030; }
|
.badge-amber { background: rgba(232,160,48,.2); color: #E8A030; }
|
||||||
.badge-green { background: rgba(58,184,122,.2); color: #3AB87A; }
|
.badge-green { background: rgba(58,184,122,.2); color: #3AB87A; }
|
||||||
.badge-blue { background: rgba(58,123,168,.2); color: #3A7BA8; }
|
.badge-blue { background: rgba(74,139,200,.2); color: #4A8BC8; }
|
||||||
.badge-red { background: rgba(200,64,64,.2); color: #C84040; }
|
.badge-red { background: rgba(224,96,96,.2); color: #E06060; }
|
||||||
|
|
||||||
/* Button system */
|
/* Button system */
|
||||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
||||||
@@ -23,7 +23,7 @@ body { background: #0A0A0C; color: #F0EDE6; font-family: 'Inter', sans-serif; -w
|
|||||||
.btn:active { transform: scale(.96); }
|
.btn:active { transform: scale(.96); }
|
||||||
.btn-sm { padding: 5px 10px; font-size: 10px; }
|
.btn-sm { padding: 5px 10px; font-size: 10px; }
|
||||||
.btn-ghost { background: transparent; border-color: transparent; }
|
.btn-ghost { background: transparent; border-color: transparent; }
|
||||||
.btn-danger { border-color: #C84040; background: rgba(200,64,64,.15); color: #C84040; }
|
.btn-danger { border-color: #E06060; background: rgba(224,96,96,.15); color: #E06060; }
|
||||||
.btn-icon { display: inline-flex; align-items: center; justify-content: center;
|
.btn-icon { display: inline-flex; align-items: center; justify-content: center;
|
||||||
width: 28px; height: 28px; border-radius: 5px; border: 1px solid #2A2A32;
|
width: 28px; height: 28px; border-radius: 5px; border: 1px solid #2A2A32;
|
||||||
background: #1C1C22; cursor: pointer; transition: all .12s; touch-action: manipulation; }
|
background: #1C1C22; cursor: pointer; transition: all .12s; touch-action: manipulation; }
|
||||||
@@ -72,7 +72,7 @@ body { background: #0A0A0C; color: #F0EDE6; font-family: 'Inter', sans-serif; -w
|
|||||||
align-items: center; justify-content: center; font-size: 16px; cursor: pointer;
|
align-items: center; justify-content: center; font-size: 16px; cursor: pointer;
|
||||||
border: 2px solid #2A2A32; background: #1C1C22; transition: all .12s; }
|
border: 2px solid #2A2A32; background: #1C1C22; transition: all .12s; }
|
||||||
.transport-btn:active { transform: scale(.92); }
|
.transport-btn:active { transform: scale(.92); }
|
||||||
.transport-btn.rec { border-color: #C84040; background: rgba(200,64,64,.15); }
|
.transport-btn.rec { border-color: #E06060; background: rgba(224,96,96,.15); }
|
||||||
.transport-btn.play { border-color: #3AB87A; background: rgba(58,184,122,.15); }
|
.transport-btn.play { border-color: #3AB87A; background: rgba(58,184,122,.15); }
|
||||||
|
|
||||||
/* Switch toggle */
|
/* Switch toggle */
|
||||||
@@ -112,10 +112,105 @@ body { background: #0A0A0C; color: #F0EDE6; font-family: 'Inter', sans-serif; -w
|
|||||||
|
|
||||||
/* NAM chip */
|
/* NAM chip */
|
||||||
.nam-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px;
|
.nam-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px;
|
||||||
background: rgba(58,123,168,.15); border: 1px solid rgba(58,123,168,.35);
|
background: rgba(74,139,200,.15); border: 1px solid rgba(74,139,200,.35);
|
||||||
border-radius: 5px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #3A7BA8; }
|
border-radius: 5px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #4A8BC8; }
|
||||||
|
|
||||||
/* Bypass LED */
|
/* Bypass LED */
|
||||||
.bypass-led { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
.bypass-led { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
||||||
.led-on { background: #3AB87A; box-shadow: 0 0 8px #3AB87A; }
|
.led-on { background: #3AB87A; box-shadow: 0 0 8px #3AB87A; }
|
||||||
.led-off { background: #444458; }
|
.led-off { background: #444458; }
|
||||||
|
|
||||||
|
/* ── Reduced motion ── */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*, *::before, *::after {
|
||||||
|
animation-duration: 0s !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0s !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Responsive breakpoints ── */
|
||||||
|
|
||||||
|
/* Portrait phones */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.slide-panel-inner {
|
||||||
|
max-width: 95vw;
|
||||||
|
max-height: 90vh;
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
}
|
||||||
|
.screen-header {
|
||||||
|
padding: 8px 10px 6px;
|
||||||
|
}
|
||||||
|
.screen-body {
|
||||||
|
padding: 8px 10px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.screen-title {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.preset-row {
|
||||||
|
padding: 8px 10px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.preset-chip-s {
|
||||||
|
padding: 5px 4px;
|
||||||
|
}
|
||||||
|
.preset-chip-s .pcs-num {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.preset-chip-s .pcs-name {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
.setting-row {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.btn-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.card-sm {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
.snap-slot {
|
||||||
|
min-width: 50px;
|
||||||
|
padding: 6px 3px;
|
||||||
|
}
|
||||||
|
.snap-num {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.mono {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Landscape phone / tablet portrait */
|
||||||
|
@media (min-width: 641px) and (max-width: 1024px) {
|
||||||
|
.slide-panel-inner {
|
||||||
|
max-width: 540px;
|
||||||
|
}
|
||||||
|
.screen-header {
|
||||||
|
padding: 10px 12px 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Landscape short screens */
|
||||||
|
@media (orientation: landscape) and (max-height: 500px) {
|
||||||
|
.slide-panel-inner {
|
||||||
|
max-height: 92vh;
|
||||||
|
}
|
||||||
|
.screen-header {
|
||||||
|
padding: 6px 12px 5px;
|
||||||
|
}
|
||||||
|
.screen-body {
|
||||||
|
padding: 6px 10px;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||