mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-21 20:31:21 +00:00
* feat(keys_highway_3d): sharp-layout modes, lane-color opacity, octave lines Add a Highway layout section to the settings with a rebuilt way to draw sharps/flats and lanes on the 3D piano highway: - Sharps & flats layout (keys3d_bg_sharpMode): floating (original raised plane), flat (one plane, zero-overlap piano-shaped tiled lanes with the naturals evened out), and realistic (one plane, bars sized like the physical keys). All geometry lives in pure laneSpanFlat()/laneSpanReal() helpers. Default: realistic. - Lane color opacity (keys3d_bg_laneOpacity, 0-1): fades the pitch-class lane tint; at 0 it is a dark floor with guide lines only at the key-block boundaries (E-F and each octave), toward 1 full vivid colored lanes. The strips, per-lane separators and block lines crossfade with the value. Default: 0. - Octave separators (keys3d_bg_octaveGaps, default on) and Octave line contrast (keys3d_bg_octaveContrast, 0-1): the B->C octave line is a dark layer scaled by lane opacity plus a bright layer scaled by its inverse, so it auto-shifts dark->bright as the lanes fade. Settings re-read on init() so they apply on the next chart build. All other behavior (MIDI scoring, palettes, camera, themes, hit feedback) is unchanged. Unit tests cover the new defaults, the sharp-mode setting, and the lane geometry (tiling/evening for flat, uniform/overlap for realistic). Signed-off-by: LegionaryLeader <legionaryleader@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update plugins/keys_highway_3d/settings.html Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update plugins/keys_highway_3d/screen.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix(keys_highway_3d): don't trim active-range boundary lane when neighbor sharp is out of range laneSpanFlat() trimmed a white key's edge for its neighboring black key's lane even when that neighbor midi fell outside range.activeLow..range.activeHigh — the neighbor's lane is never drawn (see the activeLow/activeHigh skip around the lane-strip loop), so the trim left a dark, unfilled sliver at the active-range boundary with no sharp lane to fill it. Gate the trim on the neighbor being in-range; callers that don't pass a range (e.g. the raw-tiling unit tests) keep the prior unconditional-trim behavior. Also add the CHANGELOG entry for this PR's feature set, following the existing keys_highway_3d wording convention (no plugin-local CHANGELOG exists; plugin.json was already bumped 0.1.2 -> 0.2.0 by the original commits). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Signed-off-by: LegionaryLeader <legionaryleader@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: byrongamatos <xasiklas@gmail.com>
368 lines
21 KiB
HTML
368 lines
21 KiB
HTML
<div role="group" aria-labelledby="keysh3d-heading">
|
||
<h3 id="keysh3d-heading" class="text-sm font-medium text-gray-400 mb-2">3D Keys Highway</h3>
|
||
<p class="text-xs text-gray-500 mb-3">
|
||
Pick a song with keys notation (sloppak-spec §5.3) and choose
|
||
<em>Keys Highway 3D</em> from the viz picker (Auto selects it for
|
||
notation charts). The plugin auto-attaches to your MIDI keyboard;
|
||
device, channel and transpose are configured from the player for
|
||
now — this panel holds the graphics controls.
|
||
</p>
|
||
|
||
<!-- Graphics -->
|
||
<div class="mt-3">
|
||
<h4 class="text-xs font-medium text-gray-300 mb-2">Graphics</h4>
|
||
|
||
<label for="keysh3d-fx-palette" class="text-xs font-medium text-gray-400 mb-1 block">Note colors</label>
|
||
<select id="keysh3d-fx-palette"
|
||
onchange="window.keys3dSetPalette && window.keys3dSetPalette(this.value)"
|
||
class="w-full bg-dark-700 border border-gray-800 rounded-lg px-3 py-2 text-xs text-gray-300 outline-none">
|
||
<option value="octaves" selected>Octaves (color per octave, darker sharps)</option>
|
||
<option value="emerald">Emerald (green, darker sharps)</option>
|
||
<option value="ice">Ice (blue, darker sharps)</option>
|
||
<option value="classic">Rainbow (per-pitch)</option>
|
||
<option value="vivid">Vivid (per-pitch, punchier)</option>
|
||
<option value="pastel">Pastel (per-pitch, soft)</option>
|
||
</select>
|
||
<p class="text-xs text-gray-500 mt-1 mb-3">
|
||
Choose the color scheme for the falling notes, key glow, lane
|
||
guides and hit flames. Each option is described in its own label.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-theme" class="text-xs font-medium text-gray-400 mb-1 block">Scene theme</label>
|
||
<select id="keysh3d-fx-theme"
|
||
onchange="window.keys3dSetTheme && window.keys3dSetTheme(this.value)"
|
||
class="w-full bg-dark-700 border border-gray-800 rounded-lg px-3 py-2 text-xs text-gray-300 outline-none">
|
||
<option value="default">Default (original)</option>
|
||
<option value="midnight">Midnight</option>
|
||
<option value="charcoal">Charcoal</option>
|
||
<option value="deeppurple">Deep Purple</option>
|
||
<option value="forest">Forest</option>
|
||
<option value="warmslate">Warm Slate</option>
|
||
<option value="deepfocus">Deep Focus</option>
|
||
<option value="deepsea">Deep Sea</option>
|
||
<option value="cathode">Cathode</option>
|
||
<option value="cathodegreen">Cathode Green</option>
|
||
<option value="hearth">Hearth</option>
|
||
</select>
|
||
<p class="text-xs text-gray-500 mt-1 mb-3">
|
||
Background gradient, floor and lane rails — the same theme names
|
||
as the guitar highway. Note colors come from the
|
||
"Note colors" palette above.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-camera" class="text-xs font-medium text-gray-400 mb-1 block">Camera angle</label>
|
||
<select id="keysh3d-fx-camera"
|
||
onchange="window.keys3dSetCamera && window.keys3dSetCamera(this.value)"
|
||
class="w-full bg-dark-700 border border-gray-800 rounded-lg px-3 py-2 text-xs text-gray-300 outline-none">
|
||
<option value="classic">Classic (low, deep runway)</option>
|
||
<option value="elevated">Elevated (higher, more board)</option>
|
||
<option value="overhead" selected>Overhead (top-down reading view)</option>
|
||
</select>
|
||
<p class="text-xs text-gray-500 mt-1 mb-3">
|
||
Where the camera sits. Classic is the original low rig; Elevated
|
||
lifts it for a fuller view of the keybed; Overhead looks down the
|
||
lanes for a sheet-reading feel. Applies live, keeps the
|
||
auto-pan/zoom that follows your hands.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-camheight" class="text-xs font-medium text-gray-400 mb-1 block">
|
||
Camera height <span id="keysh3d-fx-camheight-val" class="text-gray-500 font-mono">1.00</span>
|
||
</label>
|
||
<input type="range" id="keysh3d-fx-camheight"
|
||
min="0.4" max="2.2" step="0.02" value="1"
|
||
oninput="window.keys3dSetFx && window.keys3dSetFx('camHeight', this.value); document.getElementById('keysh3d-fx-camheight-val').textContent = parseFloat(this.value).toFixed(2)"
|
||
class="w-full">
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
Raise or lower the camera around the angle above (higher = more
|
||
top-down). Fine-tunes the base view; the follow-motion stays.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-camdist" class="text-xs font-medium text-gray-400 mb-1 mt-3 block">
|
||
Camera distance <span id="keysh3d-fx-camdist-val" class="text-gray-500 font-mono">1.00</span>
|
||
</label>
|
||
<input type="range" id="keysh3d-fx-camdist"
|
||
min="0.4" max="2.2" step="0.02" value="1"
|
||
oninput="window.keys3dSetFx && window.keys3dSetFx('camDist', this.value); document.getElementById('keysh3d-fx-camdist-val').textContent = parseFloat(this.value).toFixed(2)"
|
||
class="w-full">
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
Pull the camera back or push it in (larger = further away, smaller
|
||
= closer).
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-camtilt" class="text-xs font-medium text-gray-400 mb-1 mt-3 block">
|
||
Camera tilt <span id="keysh3d-fx-camtilt-val" class="text-gray-500 font-mono">0.00</span>
|
||
</label>
|
||
<input type="range" id="keysh3d-fx-camtilt"
|
||
min="-1" max="1" step="0.02" value="0"
|
||
oninput="window.keys3dSetFx && window.keys3dSetFx('camTilt', this.value); document.getElementById('keysh3d-fx-camtilt-val').textContent = parseFloat(this.value).toFixed(2)"
|
||
class="w-full">
|
||
<p class="text-xs text-gray-500 mt-1 mb-3">
|
||
Tilt the view up (+) or down (−) without moving the camera —
|
||
aims higher up the runway or down toward the keys. 0 = neutral.
|
||
</p>
|
||
|
||
<h4 class="text-xs font-medium text-gray-300 mb-2 mt-4">Highway layout</h4>
|
||
|
||
<label for="keysh3d-fx-sharpmode" class="text-xs font-medium text-gray-400 mb-1 block">Sharps & flats</label>
|
||
<select id="keysh3d-fx-sharpmode"
|
||
onchange="window.keys3dSetSharpMode && window.keys3dSetSharpMode(this.value)"
|
||
class="w-full bg-dark-700 border border-gray-800 rounded-lg px-3 py-2 text-xs text-gray-300 outline-none">
|
||
<option value="floating">Floating</option>
|
||
<option value="flat">Non-floating</option>
|
||
<option value="realistic" selected>Realistic key sizes (default — best with no colored lanes)</option>
|
||
</select>
|
||
<p class="text-xs text-gray-500 mt-1 mb-3">
|
||
How sharps and flats are drawn. <em>Floating</em>: they ride a raised
|
||
plane above the naturals. <em>Non-floating</em>: everything on one
|
||
plane, each key its own even piano-shaped lane. <em>Realistic key
|
||
sizes</em>: one plane, bars sized like the real keys (full naturals,
|
||
full black keys on top). Applies next time you open a song.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-laneopacity" class="text-xs font-medium text-gray-400 mb-1 block">
|
||
Lane color opacity <span id="keysh3d-fx-laneopacity-val" class="text-gray-500 font-mono">0.00</span>
|
||
</label>
|
||
<input type="range" id="keysh3d-fx-laneopacity"
|
||
min="0" max="1" step="0.05" value="0"
|
||
oninput="window.keys3dSetFx && window.keys3dSetFx('laneOpacity', this.value); document.getElementById('keysh3d-fx-laneopacity-val').textContent = parseFloat(this.value).toFixed(2)"
|
||
class="w-full">
|
||
<p class="text-xs text-gray-500 mt-1 mb-3">
|
||
How strongly each lane is tinted its note color. 0.00 (default) is a
|
||
dark floor with plain guide lines only between the key blocks (at E–F
|
||
and each octave); the notes keep their colors and pop off the floor.
|
||
Raise toward 1.00 for full, vivid colored lanes. Applies next time you
|
||
open a song.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-octavegaps" class="flex items-center gap-2 text-xs text-gray-300 cursor-pointer mt-3">
|
||
<input type="checkbox" id="keysh3d-fx-octavegaps" checked
|
||
onchange="window.keys3dSetFx && window.keys3dSetFx('octaveGaps', this.checked)">
|
||
Octave separators
|
||
</label>
|
||
<p class="text-xs text-gray-500 mt-1 mb-3">
|
||
Widen the gap a little at each octave boundary (every B to the C
|
||
above it) so octaves are easier to read. Applies next time you open
|
||
a song.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-octavecontrast" class="text-xs font-medium text-gray-400 mb-1 mt-3 block">
|
||
Octave line contrast <span id="keysh3d-fx-octavecontrast-val" class="text-gray-500 font-mono">0.50</span>
|
||
</label>
|
||
<input type="range" id="keysh3d-fx-octavecontrast"
|
||
min="0" max="1" step="0.05" value="0.5"
|
||
oninput="window.keys3dSetFx && window.keys3dSetFx('octaveContrast', this.value); document.getElementById('keysh3d-fx-octavecontrast-val').textContent = parseFloat(this.value).toFixed(2)"
|
||
class="w-full">
|
||
<p class="text-xs text-gray-500 mt-1 mb-3">
|
||
How strongly the octave line (every B to C) stands out. It adapts to
|
||
the lane color opacity automatically — darkening the line against
|
||
bright lanes and brightening it as you fade them toward the dark
|
||
floor. Applies next time you open a song.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-cinematic" class="flex items-center gap-2 text-xs text-gray-300 cursor-pointer">
|
||
<input type="checkbox" id="keysh3d-fx-cinematic" checked
|
||
onchange="window.keys3dSetFx && window.keys3dSetFx('cinematic', this.checked)">
|
||
Cinematic lighting
|
||
</label>
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
Dimmer ambience, stronger key light — deeper shading on the keys
|
||
and gems.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-glow" class="text-xs font-medium text-gray-400 mb-1 mt-3 block">
|
||
Glow strength <span id="keysh3d-fx-glow-val" class="text-gray-500 font-mono">0.50</span>
|
||
</label>
|
||
<input type="range" id="keysh3d-fx-glow"
|
||
min="0" max="1" step="0.05" value="0.5"
|
||
oninput="window.keys3dSetFx && window.keys3dSetFx('glow', this.value); document.getElementById('keysh3d-fx-glow-val').textContent = parseFloat(this.value).toFixed(2)"
|
||
class="w-full">
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
How hard the notes, key glow and sustain consume-flash
|
||
self-illuminate (0.5 = stock). Pairs with bloom.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-bgstyle" class="text-xs font-medium text-gray-400 mb-1 mt-3 block">Background ambience</label>
|
||
<select id="keysh3d-fx-bgstyle"
|
||
onchange="window.keys3dSetBgStyle && window.keys3dSetBgStyle(this.value)"
|
||
class="w-full bg-dark-700 border border-gray-800 rounded-lg px-3 py-2 text-xs text-gray-300 outline-none">
|
||
<option value="off">Off</option>
|
||
<option value="particles" selected>Particles</option>
|
||
<option value="lights">Stage lights</option>
|
||
<option value="geometric">Geometric</option>
|
||
</select>
|
||
<label for="keysh3d-fx-bgintensity" class="text-xs font-medium text-gray-400 mb-1 mt-2 block">
|
||
Ambience intensity <span id="keysh3d-fx-bgintensity-val" class="text-gray-500 font-mono">0.50</span>
|
||
</label>
|
||
<input type="range" id="keysh3d-fx-bgintensity"
|
||
min="0" max="1" step="0.05" value="0.5"
|
||
oninput="window.keys3dSetFx && window.keys3dSetFx('bgIntensity', this.value); document.getElementById('keysh3d-fx-bgintensity-val').textContent = parseFloat(this.value).toFixed(2)"
|
||
class="w-full">
|
||
<label for="keysh3d-fx-bgreactive" class="flex items-center gap-2 text-xs text-gray-300 cursor-pointer mt-2">
|
||
<input type="checkbox" id="keysh3d-fx-bgreactive" checked
|
||
onchange="window.keys3dSetFx && window.keys3dSetFx('bgReactive', this.checked)">
|
||
Audio-reactive ambience
|
||
</label>
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
The backdrop pulses with the mix (stems analyser when a sloppak
|
||
is loaded). Off = it animates on time only.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-scorefx" class="flex items-center gap-2 text-xs text-gray-300 cursor-pointer mt-3">
|
||
<input type="checkbox" id="keysh3d-fx-scorefx" checked
|
||
onchange="window.keys3dSetFx && window.keys3dSetFx('scoreFx', this.checked)">
|
||
Score effects
|
||
</label>
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
+1 pops on scored presses, a ring pulse every 10-combo,
|
||
milestone bursts at 25/50/100, and a brief red flicker when a
|
||
streak breaks.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-bloom" class="flex items-center gap-2 text-xs text-gray-300 cursor-pointer mt-3">
|
||
<input type="checkbox" id="keysh3d-fx-bloom" checked
|
||
onchange="window.keys3dSetFx && window.keys3dSetFx('bloom', this.checked)">
|
||
Glow (bloom)
|
||
</label>
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
Soft light-bleed around the hit line, hit flames and consumed
|
||
sustains. Applies live; turn off to reclaim GPU headroom on
|
||
weak machines.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-sparks" class="flex items-center gap-2 text-xs text-gray-300 cursor-pointer mt-3">
|
||
<input type="checkbox" id="keysh3d-fx-sparks" checked
|
||
onchange="window.keys3dSetFx && window.keys3dSetFx('sparks', this.checked)">
|
||
Hit sparks
|
||
</label>
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
A small particle burst on every scored key press, beside the
|
||
pitch-colored flame.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-timing" class="flex items-center gap-2 text-xs text-gray-300 cursor-pointer mt-3">
|
||
<input type="checkbox" id="keysh3d-fx-timing" checked
|
||
onchange="window.keys3dSetFx && window.keys3dSetFx('timingFx', this.checked)">
|
||
Timing colors
|
||
</label>
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
Tint the sparks by timing — on-time green, early cyan, late
|
||
amber. Off = always green.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-streak" class="flex items-center gap-2 text-xs text-gray-300 cursor-pointer mt-3">
|
||
<input type="checkbox" id="keysh3d-fx-streak" checked
|
||
onchange="window.keys3dSetFx && window.keys3dSetFx('streakFx', this.checked)">
|
||
Streak feedback
|
||
</label>
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
Spark bursts grow with your combo.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-hitfx" class="text-xs font-medium text-gray-400 mb-1 mt-3 block">
|
||
Hit feedback intensity <span id="keysh3d-fx-hitfx-val" class="text-gray-500 font-mono">0.70</span>
|
||
</label>
|
||
<input type="range" id="keysh3d-fx-hitfx"
|
||
min="0" max="1" step="0.05" value="0.7"
|
||
oninput="window.keys3dSetFx && window.keys3dSetFx('hitFx', this.value); document.getElementById('keysh3d-fx-hitfx-val').textContent = parseFloat(this.value).toFixed(2)"
|
||
class="w-full">
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
Drives the hit-line brightness kick on scored presses. 0 turns
|
||
it off.
|
||
</p>
|
||
|
||
<label for="keysh3d-fx-vibrancy" class="text-xs font-medium text-gray-400 mb-1 mt-3 block">
|
||
Note vibrancy <span id="keysh3d-fx-vibrancy-val" class="text-gray-500 font-mono">0.85</span>
|
||
</label>
|
||
<input type="range" id="keysh3d-fx-vibrancy"
|
||
min="0" max="1" step="0.05" value="0.85"
|
||
oninput="window.keys3dSetFx && window.keys3dSetFx('vibrancy', this.value); document.getElementById('keysh3d-fx-vibrancy-val').textContent = parseFloat(this.value).toFixed(2)"
|
||
class="w-full">
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
Note-gem solidity and lane-guide strength — lower to see more of
|
||
the keyboard through the notes.
|
||
</p>
|
||
</div>
|
||
|
||
<script>
|
||
(function () {
|
||
'use strict';
|
||
// Hydrate controls from stored config on first paint. Narrow the
|
||
// try/catch to just the localStorage reads (drum_highway_3d
|
||
// settings-hydration convention).
|
||
try {
|
||
// FX toggles (keys3d_bg_* — guitar-parity graphics controls).
|
||
// Only explicit values override; absent/corrupt keys keep the
|
||
// default (ON), matching screen.js readFxSettings.
|
||
const hydrateFxBool = (key, elId) => {
|
||
const raw = localStorage.getItem('keys3d_bg_' + key);
|
||
if (raw === '1' || raw === 'true') document.getElementById(elId).checked = true;
|
||
else if (raw === '0' || raw === 'false') document.getElementById(elId).checked = false;
|
||
};
|
||
hydrateFxBool('bloom', 'keysh3d-fx-bloom');
|
||
hydrateFxBool('sparks', 'keysh3d-fx-sparks');
|
||
hydrateFxBool('timingFx', 'keysh3d-fx-timing');
|
||
hydrateFxBool('streakFx', 'keysh3d-fx-streak');
|
||
hydrateFxBool('cinematic', 'keysh3d-fx-cinematic');
|
||
hydrateFxBool('bgReactive', 'keysh3d-fx-bgreactive');
|
||
hydrateFxBool('scoreFx', 'keysh3d-fx-scorefx');
|
||
// Highway-layout: octaveGaps defaults ON (bool); laneOpacity /
|
||
// octaveContrast are 0-1 sliders hydrated with hydrateFxRange below.
|
||
hydrateFxBool('octaveGaps', 'keysh3d-fx-octavegaps');
|
||
const hydrateFxRange = (key, elId, valId) => {
|
||
const n = parseFloat(localStorage.getItem('keys3d_bg_' + key));
|
||
if (!Number.isFinite(n)) return;
|
||
const v = Math.min(1, Math.max(0, n));
|
||
document.getElementById(elId).value = String(v);
|
||
document.getElementById(valId).textContent = v.toFixed(2);
|
||
};
|
||
hydrateFxRange('hitFx', 'keysh3d-fx-hitfx', 'keysh3d-fx-hitfx-val');
|
||
hydrateFxRange('vibrancy', 'keysh3d-fx-vibrancy', 'keysh3d-fx-vibrancy-val');
|
||
hydrateFxRange('glow', 'keysh3d-fx-glow', 'keysh3d-fx-glow-val');
|
||
hydrateFxRange('bgIntensity', 'keysh3d-fx-bgintensity', 'keysh3d-fx-bgintensity-val');
|
||
hydrateFxRange('laneOpacity', 'keysh3d-fx-laneopacity', 'keysh3d-fx-laneopacity-val');
|
||
hydrateFxRange('octaveContrast', 'keysh3d-fx-octavecontrast', 'keysh3d-fx-octavecontrast-val');
|
||
// Camera fine-tune sliders live outside 0-1 — clamp to the
|
||
// control's own min/max (mirrors screen.js FX_RANGES).
|
||
const hydrateFxRangeIn = (key, elId, valId) => {
|
||
const n = parseFloat(localStorage.getItem('keys3d_bg_' + key));
|
||
if (!Number.isFinite(n)) return;
|
||
const el = document.getElementById(elId);
|
||
const v = Math.min(parseFloat(el.max), Math.max(parseFloat(el.min), n));
|
||
el.value = String(v);
|
||
document.getElementById(valId).textContent = v.toFixed(2);
|
||
};
|
||
hydrateFxRangeIn('camHeight', 'keysh3d-fx-camheight', 'keysh3d-fx-camheight-val');
|
||
hydrateFxRangeIn('camDist', 'keysh3d-fx-camdist', 'keysh3d-fx-camdist-val');
|
||
hydrateFxRangeIn('camTilt', 'keysh3d-fx-camtilt', 'keysh3d-fx-camtilt-val');
|
||
const storedCamera = localStorage.getItem('keys3d_bg_camera');
|
||
const cameraSel = document.getElementById('keysh3d-fx-camera');
|
||
if (storedCamera && Array.from(cameraSel.options).some(o => o.value === storedCamera)) {
|
||
cameraSel.value = storedCamera;
|
||
}
|
||
const storedStyle = localStorage.getItem('keys3d_bg_style');
|
||
const styleSel = document.getElementById('keysh3d-fx-bgstyle');
|
||
if (storedStyle && Array.from(styleSel.options).some(o => o.value === storedStyle)) {
|
||
styleSel.value = storedStyle;
|
||
}
|
||
const storedTheme = localStorage.getItem('keys3d_bg_theme');
|
||
const themeSel = document.getElementById('keysh3d-fx-theme');
|
||
if (storedTheme && Array.from(themeSel.options).some(o => o.value === storedTheme)) {
|
||
themeSel.value = storedTheme;
|
||
}
|
||
const storedPalette = localStorage.getItem('keys3d_bg_palette');
|
||
const paletteSel = document.getElementById('keysh3d-fx-palette');
|
||
if (storedPalette && Array.from(paletteSel.options).some(o => o.value === storedPalette)) {
|
||
paletteSel.value = storedPalette;
|
||
}
|
||
const storedSharp = localStorage.getItem('keys3d_bg_sharpMode');
|
||
const sharpSel = document.getElementById('keysh3d-fx-sharpmode');
|
||
if (storedSharp && Array.from(sharpSel.options).some(o => o.value === storedSharp)) {
|
||
sharpSel.value = storedSharp;
|
||
}
|
||
} catch (e) {
|
||
console.warn('[Keys-Hwy3D settings] hydration failed:', e);
|
||
}
|
||
})();
|
||
</script>
|
||
</div>
|