From 978fe71628b8e31e2132ba1a5776aeb08f6f7cc9 Mon Sep 17 00:00:00 2001 From: Dmitriy Petrov Date: Fri, 14 Aug 2026 23:40:05 +0300 Subject: [PATCH] Refine secondary rail icons and animations --- src/shared/versions.ts | 4 +- src/web/features/devices/DevicesFeature.tsx | 10 +- .../instructions/InstructionsFeature.tsx | 6 +- src/web/features/routing/RoutingFeature.tsx | 4 +- src/web/styles/primitives.css | 105 ++++++++++-------- test/web/responsive-layout-contract.test.js | 28 +++-- test/web/style-boundaries.test.js | 26 ++--- 7 files changed, 104 insertions(+), 79 deletions(-) diff --git a/src/shared/versions.ts b/src/shared/versions.ts index e2867dd..e1aabe6 100644 --- a/src/shared/versions.ts +++ b/src/shared/versions.ts @@ -1,6 +1,6 @@ export const HARBOR_VERSIONS = Object.freeze({ - macClient: '0.25.12', - gatewayClient: '0.26.11', + macClient: '0.25.13', + gatewayClient: '0.26.12', gatewayBackend: '0.26.5', }); diff --git a/src/web/features/devices/DevicesFeature.tsx b/src/web/features/devices/DevicesFeature.tsx index b102dd9..6cf7a9d 100644 --- a/src/web/features/devices/DevicesFeature.tsx +++ b/src/web/features/devices/DevicesFeature.tsx @@ -243,11 +243,11 @@ export function DevicesToggle({ onClick={onToggle} > ; } diff --git a/src/web/features/instructions/InstructionsFeature.tsx b/src/web/features/instructions/InstructionsFeature.tsx index a044e0c..bdd59f3 100644 --- a/src/web/features/instructions/InstructionsFeature.tsx +++ b/src/web/features/instructions/InstructionsFeature.tsx @@ -249,9 +249,9 @@ export function InstructionsToggle({ onClick={onToggle} > ; } diff --git a/src/web/features/routing/RoutingFeature.tsx b/src/web/features/routing/RoutingFeature.tsx index 30f4501..a69ed65 100644 --- a/src/web/features/routing/RoutingFeature.tsx +++ b/src/web/features/routing/RoutingFeature.tsx @@ -416,8 +416,8 @@ export function RoutingToggle({ > ); diff --git a/src/web/styles/primitives.css b/src/web/styles/primitives.css index 0fb03e4..21c1c21 100644 --- a/src/web/styles/primitives.css +++ b/src/web/styles/primitives.css @@ -71,8 +71,9 @@ .client-rail-subscription-back, .client-rail-subscription-front, .client-rail-subscription-lines, -.client-rail-book-page, -.client-rail-book-spine, +.client-rail-info-ring, +.client-rail-info-sweep, +.client-rail-info-mark, .client-rail-device-primary, .client-rail-device-secondary, .client-rail-device-link, @@ -83,7 +84,7 @@ .client-rail-rule-knob { transform-box: fill-box; transform-origin: center; - transition: transform 620ms cubic-bezier(0.16, 1, 0.3, 1), opacity 360ms ease, stroke-dashoffset 620ms cubic-bezier(0.16, 1, 0.3, 1); + transition: transform 620ms cubic-bezier(0.16, 1, 0.3, 1), opacity 360ms ease, stroke-dasharray 620ms cubic-bezier(0.16, 1, 0.3, 1), stroke-dashoffset 620ms cubic-bezier(0.16, 1, 0.3, 1); } .client-rail-action .client-rail-subscription-card, @@ -92,63 +93,52 @@ } .client-rail-subscription-back { - transform: translate(-1.4px, 1.4px); -} - -.client-rail-subscription-front { - transform: translateX(1px); + transform: translate(0, 0); } .client-rail-subscription-lines { - opacity: 0.52; - transform: scaleX(0.68); + opacity: 0.9; transform-origin: left center; } .client-subscription-toggle.is-open .client-rail-subscription-back { - transform: translate(0, 0); + transform: translate(0.6px, -0.6px); } .client-subscription-toggle.is-open .client-rail-subscription-front { - transform: translate(-1px, 0.6px); + transform: translate(-1.5px, 1px); } .client-subscription-toggle.is-open .client-rail-subscription-lines { opacity: 1; - transform: scaleX(1); + animation: client-rail-detail-pulse 10s ease-in-out infinite; } -.client-rail-book-page.is-left { - transform: translateX(1.7px) scaleX(0.58); - transform-origin: right center; +.client-rail-info-ring { + stroke-dasharray: 1 0; + stroke-dashoffset: 0.25; } -.client-rail-book-page.is-right { - transform: translateX(-1.7px) scaleX(0.58); - transform-origin: left center; +.client-rail-info-sweep { + stroke-width: 2.4; + stroke-dasharray: 0.14 0.86; + opacity: 0; } -.client-rail-book-spine { - opacity: 0.55; - transform: scaleY(0.74); +.client-instructions-toggle.is-open .client-rail-info-ring { + stroke-dasharray: 0.78 0.22; } -.client-instructions-toggle.is-open .client-rail-book-page, -.client-instructions-toggle.is-open .client-rail-book-spine { - opacity: 1; - transform: translate(0, 0) scale(1); +.client-instructions-toggle.is-open .client-rail-info-mark { + transform: translateY(-0.6px); } -.client-rail-device-primary { - transform: translate(-0.9px, -1.8px); -} - -.client-rail-device-secondary { - transform: translate(0.9px, -1.8px); +.client-instructions-toggle.is-open .client-rail-info-sweep { + animation: client-rail-info-scan 10s linear infinite; } .client-rail-device-link { - opacity: 0.55; + opacity: 0.9; } .client-rail-device-bridge { @@ -163,9 +153,16 @@ stroke-dasharray: 0.16 0.84; } -.client-devices-toggle.is-open .client-rail-device-primary, +.client-rail-device-pulse { + stroke-dasharray: 0.5 0.5; +} + +.client-devices-toggle.is-open .client-rail-device-primary { + transform: translateX(0.6px); +} + .client-devices-toggle.is-open .client-rail-device-secondary { - transform: translate(0, 0); + transform: translateX(-0.6px); } .client-devices-toggle.is-open .client-rail-device-link { @@ -178,13 +175,13 @@ } .client-devices-toggle.is-open .client-rail-device-pulse { - animation: client-rail-device-signal 760ms cubic-bezier(0.16, 1, 0.3, 1); + animation: client-rail-device-signal 10s linear infinite; } .client-rail-diagnostics-base, .client-rail-diagnostics-pulse { vector-effect: non-scaling-stroke; - transform: scaleY(0.22); + transform: scaleY(0.8); } .client-diagnostics-toggle.is-open .client-rail-diagnostics-base, @@ -193,27 +190,47 @@ } .client-diagnostics-toggle.is-open .client-rail-diagnostics-pulse { - animation: client-rail-diagnostics-scan 760ms cubic-bezier(0.16, 1, 0.3, 1); + animation: client-rail-diagnostics-scan 10s linear infinite; } .client-local-rules-toggle.is-open .client-rail-rule-knob.is-top { - transform: translateX(3px); + transform: translateX(2px); } .client-local-rules-toggle.is-open .client-rail-rule-knob.is-bottom { - transform: translateX(-3px); + transform: translateX(-2px); +} + +.client-local-rules-toggle.is-open .client-rail-rule-knob { + animation: client-rail-rule-pulse 10s ease-in-out infinite; +} + +@keyframes client-rail-detail-pulse { + 0%, 8%, 100% { transform: translateX(0); } + 4% { transform: translateX(0.8px); } +} + +@keyframes client-rail-info-scan { + 0% { opacity: 0; stroke-dashoffset: 1; } + 2%, 8% { opacity: 1; } + 10%, 100% { opacity: 0; stroke-dashoffset: 0; } } @keyframes client-rail-device-signal { 0% { opacity: 0; stroke-dashoffset: 1; } - 18%, 78% { opacity: 1; } - 100% { opacity: 0; stroke-dashoffset: 0; } + 2%, 8% { opacity: 1; } + 10%, 100% { opacity: 0; stroke-dashoffset: 0; } } @keyframes client-rail-diagnostics-scan { 0% { opacity: 0; stroke-dashoffset: 1; } - 16%, 84% { opacity: 1; } - 100% { opacity: 0; stroke-dashoffset: 0; } + 2%, 8% { opacity: 1; } + 10%, 100% { opacity: 0; stroke-dashoffset: 0; } +} + +@keyframes client-rail-rule-pulse { + 0%, 10%, 100% { stroke-width: 1.6; } + 5% { stroke-width: 2.4; } } .client-local-rules-toggle:disabled { diff --git a/test/web/responsive-layout-contract.test.js b/test/web/responsive-layout-contract.test.js index c6e3bf9..f81ee69 100644 --- a/test/web/responsive-layout-contract.test.js +++ b/test/web/responsive-layout-contract.test.js @@ -147,25 +147,33 @@ test('secondary menus share one right rail and switch equal drawers as a vertica assert.match(disabledRulesLabel, /filter:\s*blur\(5px\)/); assert.match(styles, /\.client-local-rules-toggle:disabled:hover span\s*\{[\s\S]*opacity:\s*1/); assert.match(subscription, /client-rail-subscription-back[\s\S]*client-rail-subscription-front[\s\S]*client-rail-subscription-lines/); - assert.match(instructions, /client-rail-book-page is-left[\s\S]*client-rail-book-page is-right[\s\S]*client-rail-book-spine/); + assert.match(instructions, /client-rail-info-ring[\s\S]*client-rail-info-sweep[\s\S]*client-rail-info-mark/); assert.match(component, / crypto.createHash('sha256').update(value).digest('hex'); const acceptedLedger = { counts: { - cascadeEdges: 822, + cascadeEdges: 823, customProperties: 103, - declarations: 3325, + declarations: 3333, important: 0, - keyframes: 52, + keyframes: 55, media: 12, - rules: 953, + rules: 961, variableReferences: 811, }, hashes: { - cascadeEdges: 'c9c0e3988cea68ecc1c5e06e12b3b12fe9bb4d20482a67abcf838296ccf9740f', + cascadeEdges: '20d8763bd907f8182994186e2c53e03885ecdcb8dda07c69ff7e64b5ac808625', customProperties: 'ef70fb1d9b61b177f1939f811babe1116bee631de26f5eac0aa0d0009ca5a1fb', - declarations: '856c8aa1395c435723a52d709a39c9d37e0ede48ddb59f4ce8eff91d695e605d', + declarations: '633264a595c8ade20cae3adb50bb484c61c22f430b7760155efb4ec4b15d30de', duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945', duplicateSelectors: '0fa66e370695261a2a5c6a189752c07d518d80d68d3e79a3ba4bb34407893c0e', - keyframes: 'd3050d026f6fba3975cf6bb07320d0c3f400c3b3b6eaa3cecbc53b0b137083a3', - ruleDeclarationSequences: 'e129c791ddbc3fa3f28fe4fb5565688a7e2e212eff2f8f8e85d76394f9628847', - selectors: '4ab0b3b0944d13512d5ea28255d031b4b2cb7469883057e8f16ab090580fbd51', + keyframes: '35e40cc917b4c0d5a99bb0a3a626736e134167d29cf303ec41d02bf713d18637', + ruleDeclarationSequences: 'b7643608ce8526684769dc22bf6641962e324093f9f20b6239e7d71dab031272', + selectors: '896dc29ad2ed823b0754d4c667c093c5733bc54af951cf2b9ed83e9c022836d5', variableReferences: '0ffb99792db35b451fb35d1ced24e1aeef3a6df769cb37bf3d61229b0c970eba', - witnesses: 'c502e252880f18c633b82d09bf349de134c52da4b67a8dfadd327ee01b464692', + witnesses: 'f9c177d5f967c116a1f8a79c05a6d1faf1231775045dbafbc1772d2f916e40e0', }, }; @@ -405,8 +405,8 @@ test('main owns one public stylesheet and the regrouped production CSS is determ assert.equal((main.match(/import ['"][^'"]+\.css['"]/g) || []).length, 1); const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css')); - assert.deepEqual(assets, ['index-D5lRz6FV.css']); + assert.deepEqual(assets, ['index-Ceg2DPeq.css']); const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0])); - assert.equal(built.byteLength, 129198); - assert.equal(sha256(built), '1108b32f6d493f170b7683a1947c248c5d4e1295dd810bdc0f0935d133c28497'); + assert.equal(built.byteLength, 129584); + assert.equal(sha256(built), '8ccbb067a01c28e729a1aa34908a96e508f07fb9e12c0d05ead10ddc959731bc'); });