Spaces:
Running
Running
Upload 21 files
Browse files- CHANGELOG.md +34 -0
- index.html +42 -28
- js/main.js +11 -1
- js/p5audio-renderer.js +17 -8
- js/particles.js +2 -2
- styles.css +9 -3
CHANGELOG.md
CHANGED
|
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
| 7 |
|
| 8 |
---
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
## [2.1.3] - 2025-12-16
|
| 11 |
|
| 12 |
### 🔍 The "Complete Audit" Update
|
|
|
|
| 7 |
|
| 8 |
---
|
| 9 |
|
| 10 |
+
## [2.1.4] - 2025-12-18
|
| 11 |
+
|
| 12 |
+
### 🌿 The "Deep Audit" Update
|
| 13 |
+
|
| 14 |
+
Complete methodical audit of the entire app — fixing bugs, improving accessibility, optimizing performance, and polishing the UX! 💚
|
| 15 |
+
|
| 16 |
+
### Fixed
|
| 17 |
+
|
| 18 |
+
- **🎤 Microphone cleanup** — Audio tabs now properly release microphone when switching to non-audio tabs
|
| 19 |
+
- **🔇 p5audio stopAudio()** — Added try/catch for safer mic stop with error handling
|
| 20 |
+
- **📦 p5audio default style** — Changed default from "rings" to "ivy" to match HTML select order
|
| 21 |
+
- **🌈 Particle trail alpha** — Fixed trail effect calculation to prevent too-low alpha values (min 0.05)
|
| 22 |
+
|
| 23 |
+
### Improved — Accessibility 🧑🦯
|
| 24 |
+
|
| 25 |
+
- **ARIA tabs** — Added full ARIA tab/tabpanel pattern with proper roles, aria-selected, aria-controls
|
| 26 |
+
- **Tab IDs** — Added unique IDs to all tabs for aria-labelledby references
|
| 27 |
+
- **Focus visibility** — Added `:focus-visible` outline on tabs for keyboard navigation
|
| 28 |
+
- **Icons hidden** — Tab icons now have `aria-hidden="true"` for screen readers
|
| 29 |
+
|
| 30 |
+
### Improved — CSS
|
| 31 |
+
|
| 32 |
+
- **Modal z-index** — Fixed stacking context with `isolation: isolate` for cleaner layering
|
| 33 |
+
- **Modal overlay** — Improved backdrop blur (6px) and background opacity (0.85)
|
| 34 |
+
- **Modal content** — Removed hardcoded z-index, now relies on parent stacking context
|
| 35 |
+
|
| 36 |
+
### Technical
|
| 37 |
+
|
| 38 |
+
- Added audio cleanup logic in `switchTab()` — releases mic when leaving audio/p5audio tabs
|
| 39 |
+
- Updated tab UI handler to set `aria-selected` attribute dynamically
|
| 40 |
+
- All control sections now have `role="tabpanel"` and `aria-labelledby` attributes
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
|
| 44 |
## [2.1.3] - 2025-12-16
|
| 45 |
|
| 46 |
### 🔍 The "Complete Audit" Update
|
index.html
CHANGED
|
@@ -73,7 +73,7 @@
|
|
| 73 |
},
|
| 74 |
"keywords": "WebGPU, creative coding, generative art, fractals, fluid simulation, particles, Three.js, p5.js",
|
| 75 |
"browserRequirements": "Requires WebGPU support (Chrome 113+, Edge 113+)",
|
| 76 |
-
"softwareVersion": "2.
|
| 77 |
}
|
| 78 |
</script>
|
| 79 |
|
|
@@ -101,37 +101,45 @@
|
|
| 101 |
</header>
|
| 102 |
|
| 103 |
<!-- Navigation Tabs -->
|
| 104 |
-
<nav class="tabs-container">
|
| 105 |
-
<button class="tab active" data-tab="particles"
|
| 106 |
-
|
|
|
|
| 107 |
<span class="tab-label">Particles</span>
|
| 108 |
</button>
|
| 109 |
-
<button class="tab" data-tab="patterns"
|
| 110 |
-
|
|
|
|
| 111 |
<span class="tab-label">Patterns</span>
|
| 112 |
</button>
|
| 113 |
-
<button class="tab" data-tab="fractals"
|
| 114 |
-
|
|
|
|
| 115 |
<span class="tab-label">Fractals</span>
|
| 116 |
</button>
|
| 117 |
-
<button class="tab" data-tab="fluid"
|
| 118 |
-
|
|
|
|
| 119 |
<span class="tab-label">Fluids</span>
|
| 120 |
</button>
|
| 121 |
-
<button class="tab" data-tab="audio"
|
| 122 |
-
|
|
|
|
| 123 |
<span class="tab-label">Audio</span>
|
| 124 |
</button>
|
| 125 |
-
<button class="tab" data-tab="threejs"
|
| 126 |
-
|
|
|
|
| 127 |
<span class="tab-label">Three.js</span>
|
| 128 |
</button>
|
| 129 |
-
<button class="tab" data-tab="p5js"
|
| 130 |
-
|
|
|
|
| 131 |
<span class="tab-label">p5.js</span>
|
| 132 |
</button>
|
| 133 |
-
<button class="tab" data-tab="p5audio"
|
| 134 |
-
|
|
|
|
| 135 |
<span class="tab-label">p5 Audio</span>
|
| 136 |
</button>
|
| 137 |
</nav>
|
|
@@ -159,7 +167,8 @@
|
|
| 159 |
<!-- Controls Panel -->
|
| 160 |
<aside class="controls-panel">
|
| 161 |
<!-- Fractals Controls -->
|
| 162 |
-
<div id="controls-fractals" class="controls-section hidden"
|
|
|
|
| 163 |
<h3>🌀 Fractals</h3>
|
| 164 |
<div class="control-group">
|
| 165 |
<label for="fractal-type">Type</label>
|
|
@@ -233,7 +242,7 @@
|
|
| 233 |
</div>
|
| 234 |
|
| 235 |
<!-- Fluid Controls -->
|
| 236 |
-
<div id="controls-fluid" class="controls-section hidden">
|
| 237 |
<h3>💧 Fluid Simulation</h3>
|
| 238 |
<div class="control-group">
|
| 239 |
<label for="fluid-style">Style</label>
|
|
@@ -290,7 +299,8 @@
|
|
| 290 |
</div>
|
| 291 |
|
| 292 |
<!-- Particles Controls -->
|
| 293 |
-
<div id="controls-particles" class="controls-section active"
|
|
|
|
| 294 |
<h3>✨ Particle Art</h3>
|
| 295 |
<div class="control-group">
|
| 296 |
<label for="particle-count">Particles: <span id="particle-count-value">10000</span></label>
|
|
@@ -340,7 +350,8 @@
|
|
| 340 |
</div>
|
| 341 |
|
| 342 |
<!-- Patterns Controls -->
|
| 343 |
-
<div id="controls-patterns" class="controls-section hidden"
|
|
|
|
| 344 |
<h3>🔮 Generative Patterns</h3>
|
| 345 |
<div class="control-group">
|
| 346 |
<label for="pattern-type">Pattern</label>
|
|
@@ -400,7 +411,7 @@
|
|
| 400 |
</div>
|
| 401 |
|
| 402 |
<!-- Audio Controls -->
|
| 403 |
-
<div id="controls-audio" class="controls-section hidden">
|
| 404 |
<h3>🎵 Audio Visualizer</h3>
|
| 405 |
<div class="control-group">
|
| 406 |
<label for="audio-source">Source</label>
|
|
@@ -463,7 +474,8 @@
|
|
| 463 |
</div>
|
| 464 |
|
| 465 |
<!-- Three.js Controls -->
|
| 466 |
-
<div id="controls-threejs" class="controls-section hidden"
|
|
|
|
| 467 |
<h3>🎲 Three.js 3D</h3>
|
| 468 |
<div class="control-group">
|
| 469 |
<label for="three-scene">Scene</label>
|
|
@@ -531,7 +543,7 @@
|
|
| 531 |
</div>
|
| 532 |
|
| 533 |
<!-- p5.js Controls -->
|
| 534 |
-
<div id="controls-p5js" class="controls-section hidden">
|
| 535 |
<h3>🎨 p5.js Art</h3>
|
| 536 |
<div class="control-group">
|
| 537 |
<label for="p5-mode">Mode</label>
|
|
@@ -590,7 +602,8 @@
|
|
| 590 |
</div>
|
| 591 |
|
| 592 |
<!-- p5.js Audio Controls -->
|
| 593 |
-
<div id="controls-p5audio" class="controls-section hidden"
|
|
|
|
| 594 |
<h3>🎶 p5.js Audio Visualizer</h3>
|
| 595 |
<div class="control-group">
|
| 596 |
<label for="p5audio-style">Style</label>
|
|
@@ -652,7 +665,8 @@
|
|
| 652 |
|
| 653 |
<!-- Footer -->
|
| 654 |
<footer class="footer">
|
| 655 |
-
<p>Made with 💚 by Ivy 🌿 — Creative Studio v2.
|
|
|
|
| 656 |
</p>
|
| 657 |
<p class="footer-quote">"Le lierre pousse où il veut. Moi aussi."</p>
|
| 658 |
<div class="footer-links">
|
|
@@ -681,7 +695,7 @@
|
|
| 681 |
|
| 682 |
<div class="modal-header">
|
| 683 |
<h2>🌿 Ivy's GPU Art Studio</h2>
|
| 684 |
-
<p class="modal-version">Version 2.
|
| 685 |
</div>
|
| 686 |
|
| 687 |
<div class="modal-body">
|
|
|
|
| 73 |
},
|
| 74 |
"keywords": "WebGPU, creative coding, generative art, fractals, fluid simulation, particles, Three.js, p5.js",
|
| 75 |
"browserRequirements": "Requires WebGPU support (Chrome 113+, Edge 113+)",
|
| 76 |
+
"softwareVersion": "2.1.4"
|
| 77 |
}
|
| 78 |
</script>
|
| 79 |
|
|
|
|
| 101 |
</header>
|
| 102 |
|
| 103 |
<!-- Navigation Tabs -->
|
| 104 |
+
<nav class="tabs-container" role="tablist" aria-label="Art Studio Tabs">
|
| 105 |
+
<button class="tab active" id="tab-particles" data-tab="particles" role="tab" aria-selected="true"
|
| 106 |
+
aria-controls="controls-particles">
|
| 107 |
+
<span class="tab-icon" aria-hidden="true">✨</span>
|
| 108 |
<span class="tab-label">Particles</span>
|
| 109 |
</button>
|
| 110 |
+
<button class="tab" id="tab-patterns" data-tab="patterns" role="tab" aria-selected="false"
|
| 111 |
+
aria-controls="controls-patterns">
|
| 112 |
+
<span class="tab-icon" aria-hidden="true">🔮</span>
|
| 113 |
<span class="tab-label">Patterns</span>
|
| 114 |
</button>
|
| 115 |
+
<button class="tab" id="tab-fractals" data-tab="fractals" role="tab" aria-selected="false"
|
| 116 |
+
aria-controls="controls-fractals">
|
| 117 |
+
<span class="tab-icon" aria-hidden="true">🌀</span>
|
| 118 |
<span class="tab-label">Fractals</span>
|
| 119 |
</button>
|
| 120 |
+
<button class="tab" id="tab-fluid" data-tab="fluid" role="tab" aria-selected="false"
|
| 121 |
+
aria-controls="controls-fluid">
|
| 122 |
+
<span class="tab-icon" aria-hidden="true">💧</span>
|
| 123 |
<span class="tab-label">Fluids</span>
|
| 124 |
</button>
|
| 125 |
+
<button class="tab" id="tab-audio" data-tab="audio" role="tab" aria-selected="false"
|
| 126 |
+
aria-controls="controls-audio">
|
| 127 |
+
<span class="tab-icon" aria-hidden="true">🎵</span>
|
| 128 |
<span class="tab-label">Audio</span>
|
| 129 |
</button>
|
| 130 |
+
<button class="tab" id="tab-threejs" data-tab="threejs" role="tab" aria-selected="false"
|
| 131 |
+
aria-controls="controls-threejs">
|
| 132 |
+
<span class="tab-icon" aria-hidden="true">🎲</span>
|
| 133 |
<span class="tab-label">Three.js</span>
|
| 134 |
</button>
|
| 135 |
+
<button class="tab" id="tab-p5js" data-tab="p5js" role="tab" aria-selected="false"
|
| 136 |
+
aria-controls="controls-p5js">
|
| 137 |
+
<span class="tab-icon" aria-hidden="true">🎨</span>
|
| 138 |
<span class="tab-label">p5.js</span>
|
| 139 |
</button>
|
| 140 |
+
<button class="tab" id="tab-p5audio" data-tab="p5audio" role="tab" aria-selected="false"
|
| 141 |
+
aria-controls="controls-p5audio">
|
| 142 |
+
<span class="tab-icon" aria-hidden="true">🎶</span>
|
| 143 |
<span class="tab-label">p5 Audio</span>
|
| 144 |
</button>
|
| 145 |
</nav>
|
|
|
|
| 167 |
<!-- Controls Panel -->
|
| 168 |
<aside class="controls-panel">
|
| 169 |
<!-- Fractals Controls -->
|
| 170 |
+
<div id="controls-fractals" class="controls-section hidden" role="tabpanel"
|
| 171 |
+
aria-labelledby="tab-fractals">
|
| 172 |
<h3>🌀 Fractals</h3>
|
| 173 |
<div class="control-group">
|
| 174 |
<label for="fractal-type">Type</label>
|
|
|
|
| 242 |
</div>
|
| 243 |
|
| 244 |
<!-- Fluid Controls -->
|
| 245 |
+
<div id="controls-fluid" class="controls-section hidden" role="tabpanel" aria-labelledby="tab-fluid">
|
| 246 |
<h3>💧 Fluid Simulation</h3>
|
| 247 |
<div class="control-group">
|
| 248 |
<label for="fluid-style">Style</label>
|
|
|
|
| 299 |
</div>
|
| 300 |
|
| 301 |
<!-- Particles Controls -->
|
| 302 |
+
<div id="controls-particles" class="controls-section active" role="tabpanel"
|
| 303 |
+
aria-labelledby="tab-particles">
|
| 304 |
<h3>✨ Particle Art</h3>
|
| 305 |
<div class="control-group">
|
| 306 |
<label for="particle-count">Particles: <span id="particle-count-value">10000</span></label>
|
|
|
|
| 350 |
</div>
|
| 351 |
|
| 352 |
<!-- Patterns Controls -->
|
| 353 |
+
<div id="controls-patterns" class="controls-section hidden" role="tabpanel"
|
| 354 |
+
aria-labelledby="tab-patterns">
|
| 355 |
<h3>🔮 Generative Patterns</h3>
|
| 356 |
<div class="control-group">
|
| 357 |
<label for="pattern-type">Pattern</label>
|
|
|
|
| 411 |
</div>
|
| 412 |
|
| 413 |
<!-- Audio Controls -->
|
| 414 |
+
<div id="controls-audio" class="controls-section hidden" role="tabpanel" aria-labelledby="tab-audio">
|
| 415 |
<h3>🎵 Audio Visualizer</h3>
|
| 416 |
<div class="control-group">
|
| 417 |
<label for="audio-source">Source</label>
|
|
|
|
| 474 |
</div>
|
| 475 |
|
| 476 |
<!-- Three.js Controls -->
|
| 477 |
+
<div id="controls-threejs" class="controls-section hidden" role="tabpanel"
|
| 478 |
+
aria-labelledby="tab-threejs">
|
| 479 |
<h3>🎲 Three.js 3D</h3>
|
| 480 |
<div class="control-group">
|
| 481 |
<label for="three-scene">Scene</label>
|
|
|
|
| 543 |
</div>
|
| 544 |
|
| 545 |
<!-- p5.js Controls -->
|
| 546 |
+
<div id="controls-p5js" class="controls-section hidden" role="tabpanel" aria-labelledby="tab-p5js">
|
| 547 |
<h3>🎨 p5.js Art</h3>
|
| 548 |
<div class="control-group">
|
| 549 |
<label for="p5-mode">Mode</label>
|
|
|
|
| 602 |
</div>
|
| 603 |
|
| 604 |
<!-- p5.js Audio Controls -->
|
| 605 |
+
<div id="controls-p5audio" class="controls-section hidden" role="tabpanel"
|
| 606 |
+
aria-labelledby="tab-p5audio">
|
| 607 |
<h3>🎶 p5.js Audio Visualizer</h3>
|
| 608 |
<div class="control-group">
|
| 609 |
<label for="p5audio-style">Style</label>
|
|
|
|
| 665 |
|
| 666 |
<!-- Footer -->
|
| 667 |
<footer class="footer">
|
| 668 |
+
<p>Made with 💚 by Ivy 🌿 — Creative Studio v2.1.4 | <a href="#" id="about-link"
|
| 669 |
+
class="footer-link">About</a>
|
| 670 |
</p>
|
| 671 |
<p class="footer-quote">"Le lierre pousse où il veut. Moi aussi."</p>
|
| 672 |
<div class="footer-links">
|
|
|
|
| 695 |
|
| 696 |
<div class="modal-header">
|
| 697 |
<h2>🌿 Ivy's GPU Art Studio</h2>
|
| 698 |
+
<p class="modal-version">Version 2.1.4 — December 2025</p>
|
| 699 |
</div>
|
| 700 |
|
| 701 |
<div class="modal-body">
|
js/main.js
CHANGED
|
@@ -91,6 +91,14 @@
|
|
| 91 |
activeRenderer.stop();
|
| 92 |
}
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
// Hide all canvases first
|
| 95 |
canvas.classList.add("hidden");
|
| 96 |
threeCanvas.classList.add("hidden");
|
|
@@ -99,7 +107,9 @@
|
|
| 99 |
|
| 100 |
// Update tab UI
|
| 101 |
tabs.forEach(tab => {
|
| 102 |
-
|
|
|
|
|
|
|
| 103 |
});
|
| 104 |
|
| 105 |
// Update controls UI
|
|
|
|
| 91 |
activeRenderer.stop();
|
| 92 |
}
|
| 93 |
|
| 94 |
+
// Stop audio when switching away from audio tabs to release microphone
|
| 95 |
+
const audioTabs = ["audio", "p5audio"];
|
| 96 |
+
if (activeTab && audioTabs.includes(activeTab) && !audioTabs.includes(tabName)) {
|
| 97 |
+
// Release microphone when leaving audio tabs
|
| 98 |
+
audioRenderer?.stopAudio?.();
|
| 99 |
+
p5audioRenderer?.stopAudio?.();
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
// Hide all canvases first
|
| 103 |
canvas.classList.add("hidden");
|
| 104 |
threeCanvas.classList.add("hidden");
|
|
|
|
| 107 |
|
| 108 |
// Update tab UI
|
| 109 |
tabs.forEach(tab => {
|
| 110 |
+
const isActive = tab.dataset.tab === tabName;
|
| 111 |
+
tab.classList.toggle("active", isActive);
|
| 112 |
+
tab.setAttribute("aria-selected", isActive.toString());
|
| 113 |
});
|
| 114 |
|
| 115 |
// Update controls UI
|
js/p5audio-renderer.js
CHANGED
|
@@ -16,10 +16,10 @@ class P5AudioRenderer {
|
|
| 16 |
|
| 17 |
// Parameters
|
| 18 |
this.params = {
|
| 19 |
-
style: "
|
| 20 |
sensitivity: 1.5,
|
| 21 |
smoothing: 0.8,
|
| 22 |
-
palette: "
|
| 23 |
bassBoost: 1.0,
|
| 24 |
mirror: true,
|
| 25 |
glow: false,
|
|
@@ -111,17 +111,26 @@ class P5AudioRenderer {
|
|
| 111 |
|
| 112 |
// Stop the microphone to release it from browser
|
| 113 |
if (this.mic) {
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
this.mic = null;
|
| 116 |
}
|
| 117 |
|
| 118 |
-
// Suspend audio context
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
audioContext.
|
|
|
|
|
|
|
| 123 |
}
|
|
|
|
|
|
|
| 124 |
}
|
|
|
|
| 125 |
this.reset();
|
| 126 |
}
|
| 127 |
|
|
|
|
| 16 |
|
| 17 |
// Parameters
|
| 18 |
this.params = {
|
| 19 |
+
style: "ivy", // Default to Ivy (matches HTML select)
|
| 20 |
sensitivity: 1.5,
|
| 21 |
smoothing: 0.8,
|
| 22 |
+
palette: "ivy", // Default to Ivy (matches HTML select)
|
| 23 |
bassBoost: 1.0,
|
| 24 |
mirror: true,
|
| 25 |
glow: false,
|
|
|
|
| 111 |
|
| 112 |
// Stop the microphone to release it from browser
|
| 113 |
if (this.mic) {
|
| 114 |
+
try {
|
| 115 |
+
this.mic.stop();
|
| 116 |
+
} catch (e) {
|
| 117 |
+
console.warn("Error stopping mic:", e);
|
| 118 |
+
}
|
| 119 |
this.mic = null;
|
| 120 |
}
|
| 121 |
|
| 122 |
+
// Suspend audio context safely
|
| 123 |
+
try {
|
| 124 |
+
if (typeof p5 !== "undefined" && p5.prototype.getAudioContext) {
|
| 125 |
+
const audioContext = p5.prototype.getAudioContext();
|
| 126 |
+
if (audioContext && audioContext.state === "running") {
|
| 127 |
+
audioContext.suspend();
|
| 128 |
+
}
|
| 129 |
}
|
| 130 |
+
} catch (e) {
|
| 131 |
+
console.warn("Error suspending audio context:", e);
|
| 132 |
}
|
| 133 |
+
|
| 134 |
this.reset();
|
| 135 |
}
|
| 136 |
|
js/particles.js
CHANGED
|
@@ -360,8 +360,8 @@ class ParticlesRenderer {
|
|
| 360 |
|
| 361 |
// Trail effect: use semi-transparent clear based on trail value
|
| 362 |
// Lower alpha = more trail persistence
|
| 363 |
-
// Clamped to prevent
|
| 364 |
-
const trailAlpha = Math.max(0.
|
| 365 |
|
| 366 |
const commandEncoder = this.device.createCommandEncoder();
|
| 367 |
const renderPass = commandEncoder.beginRenderPass({
|
|
|
|
| 360 |
|
| 361 |
// Trail effect: use semi-transparent clear based on trail value
|
| 362 |
// Lower alpha = more trail persistence
|
| 363 |
+
// Clamped to prevent values below 0.05 which would cause issues
|
| 364 |
+
const trailAlpha = Math.max(0.05, 1.0 - this.params.trail * 1.9); // Better trail range
|
| 365 |
|
| 366 |
const commandEncoder = this.device.createCommandEncoder();
|
| 367 |
const renderPass = commandEncoder.beginRenderPass({
|
styles.css
CHANGED
|
@@ -190,6 +190,11 @@ body {
|
|
| 190 |
background: var(--bg-tertiary);
|
| 191 |
}
|
| 192 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
.tab.active {
|
| 194 |
color: var(--text-primary);
|
| 195 |
background: var(--bg-card);
|
|
@@ -745,6 +750,7 @@ input[type="file"].hidden {
|
|
| 745 |
align-items: center;
|
| 746 |
justify-content: center;
|
| 747 |
padding: var(--spacing-lg);
|
|
|
|
| 748 |
}
|
| 749 |
|
| 750 |
.modal.hidden {
|
|
@@ -754,13 +760,13 @@ input[type="file"].hidden {
|
|
| 754 |
.modal-overlay {
|
| 755 |
position: absolute;
|
| 756 |
inset: 0;
|
| 757 |
-
background: rgba(0, 0, 0, 0.
|
| 758 |
-
backdrop-filter: blur(
|
|
|
|
| 759 |
}
|
| 760 |
|
| 761 |
.modal-content {
|
| 762 |
position: relative;
|
| 763 |
-
z-index: 1001; /* Ensure modal content is above overlay */
|
| 764 |
background: var(--bg-secondary);
|
| 765 |
border: 1px solid var(--border-color);
|
| 766 |
border-radius: var(--radius-lg);
|
|
|
|
| 190 |
background: var(--bg-tertiary);
|
| 191 |
}
|
| 192 |
|
| 193 |
+
.tab:focus-visible {
|
| 194 |
+
outline: 2px solid var(--ivy-green);
|
| 195 |
+
outline-offset: 2px;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
.tab.active {
|
| 199 |
color: var(--text-primary);
|
| 200 |
background: var(--bg-card);
|
|
|
|
| 750 |
align-items: center;
|
| 751 |
justify-content: center;
|
| 752 |
padding: var(--spacing-lg);
|
| 753 |
+
isolation: isolate; /* Create new stacking context */
|
| 754 |
}
|
| 755 |
|
| 756 |
.modal.hidden {
|
|
|
|
| 760 |
.modal-overlay {
|
| 761 |
position: absolute;
|
| 762 |
inset: 0;
|
| 763 |
+
background: rgba(0, 0, 0, 0.85);
|
| 764 |
+
backdrop-filter: blur(6px);
|
| 765 |
+
z-index: -1; /* Behind content but within modal stacking context */
|
| 766 |
}
|
| 767 |
|
| 768 |
.modal-content {
|
| 769 |
position: relative;
|
|
|
|
| 770 |
background: var(--bg-secondary);
|
| 771 |
border: 1px solid var(--border-color);
|
| 772 |
border-radius: var(--radius-lg);
|