mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Refine HUD backings and default scope feather to 100 percent
This commit is contained in:
1 parent
d8c264b501
commit
cfbdf22121
8 files changed
+144
-68
No files matched your search
@@ -1,4 +1,5 @@
|
||||
- Add a Scope checkbox and extend feathering to 200%, retaining scope settings
|
||||
across HUD layouts, share links and scenes. Give Tactical, Operator and Minimal
|
||||
text subtle black backings, including vertical metadata, while keeping map
|
||||
attribution clear (bilawalsidhu, #1397).
|
||||
across HUD layouts, share links and scenes. First run opens with Scope ON at
|
||||
100% feather; explicit settings and historical links retain their values. Give
|
||||
Tactical, Operator and Minimal text darker black backings without halos or
|
||||
overlapping lines, including vertical metadata, while keeping map attribution clear (bilawalsidhu, #1397).
|
||||
+13
-10
@@ -194,11 +194,12 @@ are independent of Tactical, Operator, Minimal and Cyber layout changes. Share
|
||||
links (`sc`/`scf`) and scene visual state restore both choices, including feather
|
||||
above 100%. The supported feather range is 0–200% of the keyhole radius: 0% is a
|
||||
hard edge and 200% extends the gradient to the center while keeping its midpoint
|
||||
on the same keyhole edge. First run remains 11%; links predating `scf` retain
|
||||
their historical 35% parse fallback.
|
||||
on the same keyhole edge. First run opens with Scope ON and feather 100%; links
|
||||
predating `scf` retain their historical 35% parse fallback.
|
||||
|
||||
Tactical, Operator and Minimal give every telemetry readout a small, softly
|
||||
edged black backing, including vertical metadata and multiline AIS. Glyphs keep
|
||||
Tactical, Operator and Minimal give every telemetry readout a small 70%-black
|
||||
backing with rounded corners, including vertical metadata and multiline AIS.
|
||||
These darker backings have no shadow halo or overlap between adjacent lines. Glyphs keep
|
||||
the active style hue with stronger alpha, while the red REC dot retains its own
|
||||
color. Readout backing sits outside each glyph's opacity; Operator's corner and
|
||||
bar frames stay transparent rather than stacking another dark panel. Summary
|
||||
@@ -208,7 +209,7 @@ lower coordinates clear map attribution. Cyber retains its existing skin.
|
||||
|
||||
`node scripts/qa-scope-hud.mjs` exercises native checkbox click and Space,
|
||||
feather range and keyboard boundaries, all four layout transitions, share/scene
|
||||
restore, text-backing coverage and geometry, multiline AIS, summary clipping and
|
||||
restore, text-backing coverage and non-overlapping geometry, multiline AIS, summary clipping and
|
||||
narrow viewport containment. Optional `QA_SCREENSHOT_DIR` saves mode screenshots.
|
||||
|
||||
## Cyber HUD — September 30, 2026
|
||||
@@ -5519,10 +5520,11 @@ the `_detectionUserOverridden` / explicit-intent flags that would suppress a
|
||||
separate landed behaviour. Pinned in `src/reasonableDefaults.test.mjs` (feather,
|
||||
detection, OUTSIDE opacity) and `src/data/layerState.test.mjs` (3D).
|
||||
|
||||
**The 2026-08-24 defaults** (superseding the interim 08-23 values of 8%/3%):
|
||||
**Current defaults** (scope feather revised to 100% on 2026-10-25;
|
||||
detection and outside opacity retain the 2026-08-24 defaults):
|
||||
the first-run look is Detection DENSE
|
||||
`75%`, ELASTIC allocation, Fade `7%`, OUTSIDE opacity `1%`, scope feather `11%`,
|
||||
and 3D fleet mode PROXIMITY, with `AIRCRAFT_BRACKET_FLOOR_ANCHOR` at `0.01` so
|
||||
`75%`, ELASTIC allocation, Fade `7%`, OUTSIDE opacity `1%`, Scope ON with feather
|
||||
`100%`, and 3D fleet mode PROXIMITY, with `AIRCRAFT_BRACKET_FLOOR_ANCHOR` at `0.01` so
|
||||
brackets keep their approved brightness exactly at the new OUTSIDE default. Two
|
||||
terms that must never be conflated: scope FEATHER softens the black scope-mask
|
||||
edge; detection FADE is the label/card fading band around the keyhole. The
|
||||
@@ -5592,8 +5594,9 @@ easier to meet (detection is now on more often), but does not create it.
|
||||
calibrated so `AIRCRAFT_BRACKET_ALPHA_FLOOR` (0.35) lands exactly at the
|
||||
default, and the mapping follows bracket brightness rather than slider
|
||||
position. The `ko` PARSE fallback stays at `5`.
|
||||
- **Scope feather: `11%`** — a soft scope-mask edge (moved `0% → 8% → 11%`; `0%` hard crop for one day,
|
||||
`35%` before that). `SCOPE_FEATHER_RATIO_DEFAULT` in `src/scopeMask.js`,
|
||||
- **Scope: `ON`, feather: `100%`** — a broad, soft scope-mask edge (revised on
|
||||
2026-10-25 from the prior `11%`; earlier defaults were `0%`, `8%` and `35%`).
|
||||
`SCOPE_FEATHER_RATIO_DEFAULT` in `src/scopeMask.js`,
|
||||
mirrored by `#scope-feather-slider`'s markup value AND readout and
|
||||
`_scopeFeatherPct` in `sharelink.js`. The slider spans
|
||||
0–200, and an explicit `0` is still the hard-crop path — pinned, so moving the
|
||||
|
||||
+71
-13
@@ -3,7 +3,8 @@
|
||||
* Scope and HUD browser acceptance. Requires the development server.
|
||||
* Run: node scripts/qa-scope-hud.mjs
|
||||
* Optional: QA_BASE_URL, QA_SCREENSHOT_DIR (screenshots stay outside the repo).
|
||||
* Proves native checkbox click/Space, feather 0/11/100/200, layout independence,
|
||||
* Proves a fresh boot defaults to Scope ON at 100%, native checkbox click/Space,
|
||||
* feather 0/11/100/200, layout independence,
|
||||
* share/scene restore, and visible text backing including rotated metadata.
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
@@ -55,12 +56,7 @@ const readScope = () =>
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
await page.setViewport({ width: 1440, height: 900 });
|
||||
const url = new URL(process.env.QA_BASE_URL || 'http://127.0.0.1:4173');
|
||||
url.searchParams.set('welcome', '0');
|
||||
url.hash =
|
||||
'v=2&lat=30.2672&lon=-97.7431&alt=2500&heading=0&pitch=-45&roll=0&map=esri&sc=1&scf=11&l=';
|
||||
async function openApplication(url) {
|
||||
await page.goto(url.href, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForFunction(
|
||||
() => window.__godsEyeView?.styleManager?._applicationShortcuts,
|
||||
@@ -76,6 +72,36 @@ try {
|
||||
},
|
||||
{ timeout: 60_000 },
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await page.setViewport({ width: 1440, height: 900 });
|
||||
const url = new URL(process.env.QA_BASE_URL || 'http://127.0.0.1:4173');
|
||||
url.searchParams.set('welcome', '0');
|
||||
// This browser has a fresh profile. Start without authored state so the
|
||||
// first assertion exercises the defaults rather than a share-link restore.
|
||||
url.hash = '';
|
||||
await openApplication(url);
|
||||
const fresh = await readScope();
|
||||
check(
|
||||
'bare-URL fresh boot opens Scope ON with 100% feather',
|
||||
fresh.enabled &&
|
||||
fresh.checked &&
|
||||
!fresh.disabled &&
|
||||
fresh.feather === 1 &&
|
||||
fresh.value === 100 &&
|
||||
fresh.readout === '100%' &&
|
||||
fresh.cornerAlpha > 0,
|
||||
fresh,
|
||||
);
|
||||
|
||||
// Use a known city view for canvas and readability checks below.
|
||||
url.hash =
|
||||
'v=2&lat=30.2672&lon=-97.7431&alt=2500&heading=0&pitch=-45&roll=0&map=esri&sc=1&scf=100&l=';
|
||||
// Changing only the hash does not rerun startup restoration. Leave the app
|
||||
// document first so this fixture is applied by a new application boot.
|
||||
await page.goto('about:blank');
|
||||
await openApplication(url);
|
||||
await page.evaluate(() => {
|
||||
const manager = window.__godsEyeView.styleManager;
|
||||
manager.setPanelCollapsed('pp-toggles', false, {
|
||||
@@ -87,12 +113,13 @@ try {
|
||||
});
|
||||
const initial = await readScope();
|
||||
check(
|
||||
'default Scope checkbox and 11% feather agree with the mask',
|
||||
'Austin fixture restores Scope ON and 100% feather',
|
||||
initial.enabled &&
|
||||
initial.checked &&
|
||||
!initial.disabled &&
|
||||
initial.feather === 0.11 &&
|
||||
initial.value === 11 &&
|
||||
initial.feather === 1 &&
|
||||
initial.value === 100 &&
|
||||
initial.readout === '100%' &&
|
||||
initial.cornerAlpha > 0,
|
||||
initial,
|
||||
);
|
||||
@@ -112,7 +139,7 @@ try {
|
||||
!off.enabled &&
|
||||
!off.checked &&
|
||||
off.disabled &&
|
||||
off.feather === 0.11 &&
|
||||
off.feather === 1 &&
|
||||
off.cornerAlpha === 0,
|
||||
off,
|
||||
);
|
||||
@@ -124,7 +151,7 @@ try {
|
||||
on.enabled &&
|
||||
on.checked &&
|
||||
!on.disabled &&
|
||||
on.feather === 0.11 &&
|
||||
on.feather === 1 &&
|
||||
on.cornerAlpha > 0,
|
||||
on,
|
||||
);
|
||||
@@ -243,7 +270,8 @@ try {
|
||||
const color = backing && getComputedStyle(backing).backgroundColor;
|
||||
if (
|
||||
!backing ||
|
||||
!color?.includes('0.34') ||
|
||||
color !== 'rgba(0, 0, 0, 0.7)' ||
|
||||
getComputedStyle(backing).boxShadow !== 'none' ||
|
||||
backing.parentElement.closest('.hud-readout')
|
||||
)
|
||||
missing.push(node.textContent.trim());
|
||||
@@ -252,6 +280,30 @@ try {
|
||||
(el) => el.offsetWidth > 0 && el.offsetHeight > 0,
|
||||
);
|
||||
const empty = wrapped.filter((el) => !el.innerText.trim()).length;
|
||||
const overlaps = [];
|
||||
for (const parent of new Set(wrapped.map((el) => el.parentElement))) {
|
||||
const siblings = wrapped.filter((el) => el.parentElement === parent);
|
||||
for (let i = 1; i < siblings.length; i += 1) {
|
||||
const previous = siblings[i - 1].getBoundingClientRect();
|
||||
const current = siblings[i].getBoundingClientRect();
|
||||
const horizontal =
|
||||
Math.min(previous.right, current.right) -
|
||||
Math.max(previous.left, current.left);
|
||||
const vertical =
|
||||
Math.min(previous.bottom, current.bottom) -
|
||||
Math.max(previous.top, current.top);
|
||||
// Horizontal bars share a vertical band. Only intersecting text boxes
|
||||
// can stack their backing alpha; allow subpixel layout rounding.
|
||||
if (horizontal > 0.5 && vertical > 0.5) {
|
||||
overlaps.push({
|
||||
parent: parent.className,
|
||||
previous: siblings[i - 1].innerText,
|
||||
current: siblings[i].innerText,
|
||||
vertical,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const lineIds = [
|
||||
'hud-mgrs',
|
||||
'hud-latlon',
|
||||
@@ -291,6 +343,7 @@ try {
|
||||
visible,
|
||||
missing,
|
||||
empty,
|
||||
overlaps,
|
||||
multiline,
|
||||
edges,
|
||||
broadBackings: broadBackings.length,
|
||||
@@ -304,6 +357,11 @@ try {
|
||||
coverage.broadBackings === 0,
|
||||
coverage,
|
||||
);
|
||||
check(
|
||||
`${layout}: adjacent readout backings do not overlap`,
|
||||
coverage.overlaps.length === 0,
|
||||
coverage,
|
||||
);
|
||||
check(
|
||||
`${layout}: desktop telemetry remains single line; rotated backing follows the labels`,
|
||||
coverage.multiline.length === 0 && coverage.edges.every(Boolean),
|
||||
|
||||
@@ -11,9 +11,8 @@ import { expandApplicationHtml } from '../build/application-html.js';
|
||||
// actually decides fresh-boot layer state — including the early return that
|
||||
// makes each layer's own initializer the operative default.
|
||||
// 2. Scope feather. Owner: "I like to hide feather" set it to 0% on
|
||||
// 2026-08-22; the owner revised that to 8% on 2026-08-23 and locked 11%
|
||||
// on 2026-08-24, a soft
|
||||
// edge. The hard crop is still one drag away, and that is pinned too.
|
||||
// 2026-08-22; later revisions were 8%, then 11%, then 100% on
|
||||
// 2026-10-25. Scope stays ON on a fresh boot. The hard crop remains available.
|
||||
// 3. Detection ON (Dense @ 75%) for EVERY style, Normal included.
|
||||
// Owner: "detect should also be on by default."
|
||||
// 4. Detection OUTSIDE opacity 1% (owner final lock, 2026-08-24; 3% on 08-23, 5% before), with
|
||||
@@ -38,6 +37,7 @@ import { AIRCRAFT_BRACKET_FLOOR_ANCHOR } from './data/detectionPolicy.js';
|
||||
import {
|
||||
SCOPE_FEATHER_RATIO_DEFAULT,
|
||||
getScopeMaskFeather,
|
||||
isScopeMaskEnabled,
|
||||
scopeMaskGeometry,
|
||||
setScopeMaskFeather,
|
||||
} from './scopeMask.js';
|
||||
@@ -75,32 +75,40 @@ function managerForHash(hash) {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2. Scope feather — a subtle soft edge on a first run
|
||||
// 2. Scope ON with 100% feather on a first run
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('first run opens with a subtle scope feather, at every surface that decides it', () => {
|
||||
assert.equal(SCOPE_FEATHER_RATIO_DEFAULT, 0.11,
|
||||
'owner final lock 2026-08-24, superseding the 08-22 hard-crop and 08-23 8% rulings');
|
||||
assert.equal(getScopeMaskFeather(), 0.11,
|
||||
test('first run opens with Scope ON and 100% feather at every default surface', () => {
|
||||
assert.equal(isScopeMaskEnabled(), true, 'the live mask starts enabled');
|
||||
assert.equal(SCOPE_FEATHER_RATIO_DEFAULT, 1, 'owner preference 2026-10-25');
|
||||
assert.equal(getScopeMaskFeather(), 1,
|
||||
'and the live module starts there, not merely documents it');
|
||||
|
||||
// The slider and its readout are the same default rendered as markup — a
|
||||
// fresh boot applies no restore, so a stale value here would show one number
|
||||
// over a mask drawn at another.
|
||||
assert.match(indexHtml, /id="scope-feather-slider"[^>]*\svalue="11"/,
|
||||
'index.html: the feather slider ships at 11');
|
||||
assert.match(indexHtml, /id="scope-feather-value"[^>]*>11%</,
|
||||
assert.match(indexHtml, /id="scope-feather-slider"[^>]*\svalue="100"/,
|
||||
'index.html: the feather slider ships at 100');
|
||||
assert.match(indexHtml, /id="scope-feather-value"[^>]*>100%</,
|
||||
'index.html: and its readout agrees with the handle');
|
||||
|
||||
assert.match(indexHtml, /<input type="checkbox" id="scope-toggle"[^>]*\schecked\s*\/>/,
|
||||
'the native Scope checkbox starts checked');
|
||||
|
||||
// The link this session generates must describe the mask this session draws,
|
||||
// for the window before the first _syncShareState.
|
||||
assert.match(shareSource, /this\._scopeFeatherPct = 11;/,
|
||||
assert.match(shareSource, /this\._scopeFeatherPct = 100;/,
|
||||
'sharelink.js: the generator starts from the same value the mask starts at');
|
||||
const params = managerForHash('')._buildHashParams();
|
||||
assert.equal(params.get('sc'), '1');
|
||||
assert.equal(params.get('scf'), '100');
|
||||
});
|
||||
|
||||
test('an explicit feather still wins over the subtle default', () => {
|
||||
test('an explicit feather still wins over the 100% default', () => {
|
||||
// A link is authored state. The new default governs a session that said
|
||||
// nothing; it must never overwrite one that said something.
|
||||
assert.equal(managerForHash('#lat=10&lon=20&scf=11').parseInitialHash().scopeFeatherPct, 11,
|
||||
'links carrying the previous default preserve their authored feather');
|
||||
assert.equal(managerForHash('#lat=10&lon=20&scf=35').parseInitialHash().scopeFeatherPct, 35);
|
||||
assert.equal(managerForHash('#lat=10&lon=20&scf=64').parseInitialHash().scopeFeatherPct, 64);
|
||||
assert.equal(managerForHash('#lat=10&lon=20&scf=0').parseInitialHash().scopeFeatherPct, 0,
|
||||
@@ -113,7 +121,13 @@ test('an explicit feather still wins over the subtle default', () => {
|
||||
'a pre-scf link restores the author\'s view, not the new default');
|
||||
});
|
||||
|
||||
test('the subtle default did not weaken the feather control, and 0 is still reachable', () => {
|
||||
test('share links preserve explicit Scope OFF and feather choices over the defaults', () => {
|
||||
const shared = managerForHash('#lat=10&lon=20&sc=0&scf=11').parseInitialHash();
|
||||
assert.equal(shared.scopeEnabled, false);
|
||||
assert.equal(shared.scopeFeatherPct, 11);
|
||||
});
|
||||
|
||||
test('the 100% default retains the expanded feather range and hard edge', () => {
|
||||
// The cheap way to move a default would be to nerf the control. Prove the
|
||||
// slider still spans its full range and the geometry is still DERIVED from
|
||||
// the ratio — a check that would pass vacuously if it only ever saw one value.
|
||||
@@ -128,8 +142,7 @@ test('the subtle default did not weaken the feather control, and 0 is still reac
|
||||
assert.ok(Math.abs((geo.outerR - geo.innerR) - keyholeR * ratio) < 1e-9,
|
||||
`feather ${ratio} must still widen the band to that fraction of the keyhole`);
|
||||
}
|
||||
// The new default is a real, narrow band — not the hard crop, and nowhere
|
||||
// near the retired 35 % halo.
|
||||
// The first-run value draws a band derived from the same keyhole radius.
|
||||
setScopeMaskFeather(SCOPE_FEATHER_RATIO_DEFAULT);
|
||||
const soft = scopeMaskGeometry(1200, 900);
|
||||
const keyholeR = 900 * 0.5 * KEYHOLE_OUTER_RADIUS;
|
||||
|
||||
+5
-8
@@ -49,13 +49,10 @@ const SCOPE_OUTSIDE_COLOR = { r: 5, g: 5, b: 8 };
|
||||
/**
|
||||
* Default edge feather as a fraction of the keyhole radius.
|
||||
*
|
||||
* 0.11 since 2026-08-24 (owner final lock; 0.08 on 08-23, hard-crop 0 on
|
||||
* 08-22 — this supersedes both), REVISING the 2026-08-22 ruling that
|
||||
* set it to zero: a subtle soft edge rather than either the hard crop or the
|
||||
* retired 35 % halo. The slider spans 0..200; this is
|
||||
* only where it STARTS. Any feather a
|
||||
* share link carries, or the operator dials in, overrides this immediately, and
|
||||
* the hard-crop path at 0 is still reachable from the handle.
|
||||
* 1.0 since 2026-10-25 (owner preference), superseding the 11% default.
|
||||
* The slider spans 0..200; this is only where it starts. An explicit feather
|
||||
* from a share link, scene or the operator overrides the default immediately,
|
||||
* including the hard-crop path at 0.
|
||||
*
|
||||
* Keep in lockstep with `#scope-feather-slider`'s markup value AND readout in
|
||||
* index.html and `_scopeFeatherPct` in sharelink.js — a fresh boot applies no
|
||||
@@ -65,7 +62,7 @@ const SCOPE_OUTSIDE_COLOR = { r: 5, g: 5, b: 8 };
|
||||
* carries `scf=0` explicitly because the generator always writes the field.
|
||||
* Pinned in reasonableDefaults.test.mjs.
|
||||
*/
|
||||
export const SCOPE_FEATHER_RATIO_DEFAULT = 0.11;
|
||||
export const SCOPE_FEATHER_RATIO_DEFAULT = 1;
|
||||
/**
|
||||
* Terminus opacity at/above SCOPE_TERMINUS_FAR_M — slightly translucent so
|
||||
* faint stars survive in the corners at globe scale.
|
||||
|
||||
+3
-4
@@ -113,10 +113,9 @@ export class ShareLinkManager {
|
||||
this._detectionOutsideOpacityPct = 1;
|
||||
this._celestialRingEnabled = false;
|
||||
this._scopeEnabled = true;
|
||||
// Feather opens on a soft 11% scope-mask edge (owner final lock 2026-08-24,
|
||||
// superseding the 08-22 hard-crop and 08-23 8% rulings) — mirrors
|
||||
// SCOPE_FEATHER_RATIO_DEFAULT in scopeMask.js and the slider's markup value.
|
||||
this._scopeFeatherPct = 11;
|
||||
// Mirrors the first-run mask and slider default. The historical missing-scf
|
||||
// parse fallback below remains 35 because it restores an older authored view.
|
||||
this._scopeFeatherPct = 100;
|
||||
// null = the altitude-adaptive terminus (the default). A number pins the
|
||||
// outside-fill opacity as a percent, 94..100. (`sce`, 2026-08-17)
|
||||
this._scopeTerminusPct = null;
|
||||
|
||||
@@ -386,11 +386,13 @@
|
||||
)
|
||||
.hud-readout,
|
||||
:root:not([data-ui-theme='cyber']) .scene-text-holdout {
|
||||
background: rgba(0, 0, 0, 0.34);
|
||||
box-shadow: 0 0 6px 3px rgba(0, 0, 0, 0.16);
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
box-shadow: none;
|
||||
border-radius: 2px;
|
||||
padding: 1px 4px;
|
||||
margin: -1px -4px;
|
||||
padding-block: 0;
|
||||
padding-inline: 6px;
|
||||
margin-block: 0;
|
||||
margin-inline: -6px;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
box-sizing: content-box;
|
||||
@@ -416,7 +418,7 @@
|
||||
[data-variant='tactical']
|
||||
)
|
||||
.hud-readout
|
||||
:not(#hud-rec-dot) {
|
||||
:where(:not(#hud-rec-dot)) {
|
||||
color: rgb(from var(--hud-color) r g b / 0.86);
|
||||
text-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.8),
|
||||
@@ -431,7 +433,7 @@
|
||||
:is(.hud-top-right, .hud-bottom-right)
|
||||
.hud-readout,
|
||||
:root:not([data-ui-theme='cyber']) #style-indicator .scene-text-holdout {
|
||||
margin-left: auto;
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
/* Rotating the backing with its text also covers both vertical annotations. */
|
||||
@@ -443,7 +445,7 @@
|
||||
.hud-edge
|
||||
.hud-readout
|
||||
> * {
|
||||
opacity: 0.72;
|
||||
opacity: 0.78;
|
||||
}
|
||||
|
||||
#intel-hud:is(
|
||||
@@ -483,6 +485,11 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* The title's inline text box needs a little vertical room around its glyphs. */
|
||||
:root:not([data-ui-theme='cyber']) #title-bar h1 > .scene-text-holdout {
|
||||
padding-block: 0.12em;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber']) #title-bar .subtitle {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@@ -113,13 +113,11 @@
|
||||
</label>
|
||||
<div class="pp-slider-row visible" id="scope-slider-row">
|
||||
<span class="pp-slider-mini-label" data-i18n>Feather</span>
|
||||
<!-- Feather starts at an 11% soft scope-mask edge (owner final lock
|
||||
2026-08-24, superseding the 08-22 hard-crop and 08-23 8% rulings);
|
||||
mirrors SCOPE_FEATHER_RATIO_DEFAULT in src/scopeMask.js. Feather
|
||||
softens the black mask edge; label fading is the separate Fade
|
||||
slider. -->
|
||||
<input type="range" class="gev-quantitative-slider pp-slider" id="scope-feather-slider" min="0" max="200" value="11" aria-label="Scope edge feather" title="Scope edge feather as a percentage of the keyhole radius" data-i18n-aria-label data-i18n-title />
|
||||
<span class="pp-slider-value" id="scope-feather-value">11%</span>
|
||||
<!-- First-run feather is 100% (owner preference 2026-10-25), matching
|
||||
SCOPE_FEATHER_RATIO_DEFAULT in src/scopeMask.js. Feather softens
|
||||
the black mask edge; detection fading uses the separate Fade slider. -->
|
||||
<input type="range" class="gev-quantitative-slider pp-slider" id="scope-feather-slider" min="0" max="200" value="100" aria-label="Scope edge feather" title="Scope edge feather as a percentage of the keyhole radius" data-i18n-aria-label data-i18n-title />
|
||||
<span class="pp-slider-value" id="scope-feather-value">100%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pp-toggle-group">
|
||||
|
||||
Reference in new issue
Block a user