mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -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')}`,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user