fix: unify lock and orange icon colors

Issue: #219
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-08-20 14:56:21 +03:00
parent 84fa434a64
commit bc75e00c2d
16 changed files with 229 additions and 143 deletions
+8 -1
View File
@@ -10,7 +10,14 @@ mkdirSync(artifactDir, { recursive: true });
const referenceAsset = (theme, file, coreSize) => {
let source = readFileSync(resolve('demo/srv/reference/device-icons', theme, file), 'utf8');
if (theme === 'Dark' && file === 'Unlock.svg') source = source.replaceAll('#1DC21D', '#F0A00C');
if (file === 'Lock.svg') {
const old = theme === 'Dark' ? '#252525' : 'black';
source = source.replaceAll(old, '#66D17A');
if (theme === 'Dark') source = source.replaceAll('fill="white"', 'fill="#252525"');
}
if (file === 'Unlock.svg') {
source = source.replaceAll(theme === 'Dark' ? '#1DC21D' : '#F0A00C', '#F0410C');
}
const nativeWidth = Number(source.match(/<svg[^>]*width="([\d.]+)"/)?.[1] || 127);
return {
url: `data:image/svg+xml;base64,${Buffer.from(source).toString('base64')}`,
+1
View File
@@ -242,6 +242,7 @@ export const GOLDEN_SCENARIOS = Object.freeze([
'golden-light-three': ['lock-unlocked'],
'golden-presence': ['virtual'],
'golden-climate': ['alarm', 'sel'],
'golden-right-linkquality': ['open'],
},
focusDevice: 'golden-climate',
hoverDevice: 'golden-presence',
+46 -10
View File
@@ -55,13 +55,20 @@ const reference = {
alert: svgState('Dark', 'Alert Value.svg'),
},
};
// Owner decision in #179 overrides the stale green Dark/Unlock asset.
reference.dark.unlock = {
...reference.dark.unlock,
core: reference.light.unlock.core,
glyph: reference.dark.active.glyph,
shell: reference.light.unlock.shell,
};
// Owner decision in #219 supersedes the package paint while retaining its
// geometry: a closed lock is green, an open lock is red, and the glyph follows
// the same Light/Dark foreground as every other coloured core.
for (const theme of ['light', 'dark']) {
const glyph = theme === 'light' ? browserColor('#FFFFFF') : browserColor('#252525');
reference[theme].lock = {
...reference[theme].lock,
core: browserColor('#66D17A'), glyph, shell: browserColor('#66D17A'),
};
reference[theme].unlock = {
...reference[theme].unlock,
core: browserColor('#F0410C'), glyph, shell: browserColor('#F0410C'),
};
}
const { page, browser } = await launch(
{ width: 1120, height: 900 }, 1, [], { colorScheme: 'dark' },
@@ -320,6 +327,7 @@ const result = await page.evaluate(async ({ beforeUnavailable, afterUnavailable,
const stateMatrix = Object.fromEntries(['light', 'dark'].map((theme) => [theme, {
default: sampleState(theme),
active: sampleState(theme, ['on']),
open: sampleState(theme, ['open']),
lock: sampleState(theme, ['lock-locked']),
unlock: sampleState(theme, ['lock-unlocked']),
alert: sampleState(theme, ['alarm']),
@@ -329,6 +337,22 @@ const result = await page.evaluate(async ({ beforeUnavailable, afterUnavailable,
lockNode.className = originalLockClasses;
lockNode.setAttribute('style', originalLockStyle);
const openingProbe = document.createElement('div');
openingProbe.innerHTML = '<span class="oplock-shell"><span class="oplock-core">'
+ '<ha-icon icon="mdi:lock"></ha-icon></span></span>';
sr().append(openingProbe);
const sampleOpeningLock = (theme, state) => {
openingProbe.className = `oplock theme-${theme} ${state}`;
const core = getComputedStyle(openingProbe.querySelector('.oplock-core'));
const shell = getComputedStyle(openingProbe.querySelector('.oplock-shell'));
return { core: core.backgroundColor, glyph: core.color, shell: shell.borderColor };
};
const openingLockMatrix = Object.fromEntries(['light', 'dark'].map((theme) => [theme, {
lock: sampleOpeningLock(theme, 'locked'),
unlock: sampleOpeningLock(theme, 'unlocked'),
}]));
openingProbe.remove();
const originalLightStyle = light.getAttribute('style') || '';
const originalDoubleStyle = double.getAttribute('style') || '';
const originalTextStyle = text.getAttribute('style') || '';
@@ -490,6 +514,7 @@ const result = await page.evaluate(async ({ beforeUnavailable, afterUnavailable,
defaultShell,
focusCoreDecoration,
stateMatrix,
openingLockMatrix,
sizeMatrix,
textShellSizeMatrix,
textFrameRadius,
@@ -520,6 +545,15 @@ const result = await page.evaluate(async ({ beforeUnavailable, afterUnavailable,
&& stateMatrix[theme][state].shell === reference[theme][state].shell
&& Math.abs(stateMatrix[theme][state].shellWidth
- Math.max(1, Math.floor(reference[theme][state].shellWidth * 50.4))) <= 0.5)),
openingLockPaletteMatches: ['light', 'dark'].every((theme) =>
['lock', 'unlock'].every((state) =>
openingLockMatrix[theme][state].core === reference[theme][state].core
&& openingLockMatrix[theme][state].glyph === reference[theme][state].glyph
&& openingLockMatrix[theme][state].shell === reference[theme][state].shell)),
orangeGlyphsFollowTheme: stateMatrix.light.active.glyph === 'rgb(255, 255, 255)'
&& stateMatrix.light.open.glyph === 'rgb(255, 255, 255)'
&& stateMatrix.dark.active.glyph === 'rgb(37, 37, 37)'
&& stateMatrix.dark.open.glyph === 'rgb(37, 37, 37)',
referenceHoverMatches: lightHover.core === reference.light.hover.core
&& lightHover.glyph === reference.light.hover.glyph
&& lightHover.shell === reference.light.default.shell
@@ -575,9 +609,11 @@ const result = await page.evaluate(async ({ beforeUnavailable, afterUnavailable,
keyboardActionFeedback,
informationalActionHasNoFeedback,
lightThemeCoreIsWhite: lightThemeCore === 'rgb(255, 255, 255)',
darkThemeCoreIs252525: darkThemeClassProjected && darkThemeCore === 'rgb(37, 37, 37)',
darkLockUsesWhiteGlyphAndDarkShell: darkLockGlyph === 'rgb(255, 255, 255)'
&& darkLockShell === 'rgb(37, 37, 37)',
darkThemeCoreIs252525: darkThemeClassProjected
&& stateMatrix.dark.default.core === 'rgb(37, 37, 37)',
darkLockUsesGreenCoreAndDarkGlyph: darkThemeCore === 'rgb(102, 209, 122)'
&& darkLockGlyph === 'rgb(37, 37, 37)'
&& darkLockShell === 'rgb(102, 209, 122)',
hitAreaAtLeast44: parseFloat(pseudo.width) >= 44 && parseFloat(pseudo.height) >= 44,
presenceContinuousGreen: presence?.querySelector('.device-pulse.continuous.reason-presence')
&& getComputedStyle(presence.querySelector('.device-pulse i')).borderColor === 'rgb(29, 194, 29)'
File diff suppressed because one or more lines are too long
+5 -2
View File
@@ -7,8 +7,11 @@ for issues #179 and #211. They do not ship in the production bundle.
- Export: 2026-08-19
- Source archive SHA-256:
`63670C73E25D1E59DDAF1BE236F3D7F2FAC827B9B5D6DD4B77125EA9BC012025`
- Owner override: Dark Unlock is amber, not the stale green paint in
`Dark/Unlock.svg`.
- Owner override #219: package geometry remains normative, but Lock/Unlock
paint is projected at runtime as closed green `#66D17A` and open red
`#F0410C`; glyphs are white in Light and `#252525` in Dark. The SVG files stay
byte-identical source evidence, while the smoke and comparison capture apply
this explicit product override.
`demo/smoke_device_icon_design.mjs` reads the SVG attributes directly for the
computed-style contract. `demo/capture_device_icon_reference.mjs` builds a