diff --git a/src/shared/versions.ts b/src/shared/versions.ts
index e1aabe6..01161f0 100644
--- a/src/shared/versions.ts
+++ b/src/shared/versions.ts
@@ -1,6 +1,6 @@
export const HARBOR_VERSIONS = Object.freeze({
- macClient: '0.25.13',
- gatewayClient: '0.26.12',
+ macClient: '0.25.14',
+ gatewayClient: '0.26.13',
gatewayBackend: '0.26.5',
});
diff --git a/src/web/features/devices/DevicesFeature.tsx b/src/web/features/devices/DevicesFeature.tsx
index 6cf7a9d..6d62ac5 100644
--- a/src/web/features/devices/DevicesFeature.tsx
+++ b/src/web/features/devices/DevicesFeature.tsx
@@ -243,11 +243,13 @@ export function DevicesToggle({
onClick={onToggle}
>
;
}
diff --git a/src/web/features/diagnostics/DiagnosticsFeature.tsx b/src/web/features/diagnostics/DiagnosticsFeature.tsx
index 2e1a48e..df3a00e 100644
--- a/src/web/features/diagnostics/DiagnosticsFeature.tsx
+++ b/src/web/features/diagnostics/DiagnosticsFeature.tsx
@@ -60,8 +60,12 @@ export function DiagnosticsToggle({
onClick={onToggle}
>
;
}
diff --git a/src/web/features/routing/RoutingFeature.tsx b/src/web/features/routing/RoutingFeature.tsx
index a69ed65..543bf89 100644
--- a/src/web/features/routing/RoutingFeature.tsx
+++ b/src/web/features/routing/RoutingFeature.tsx
@@ -416,6 +416,8 @@ export function RoutingToggle({
>
diff --git a/src/web/features/subscription/SubscriptionFeature.tsx b/src/web/features/subscription/SubscriptionFeature.tsx
index f86fe20..3c866ae 100644
--- a/src/web/features/subscription/SubscriptionFeature.tsx
+++ b/src/web/features/subscription/SubscriptionFeature.tsx
@@ -318,10 +318,11 @@ export function SubscriptionToggle({
onClick={onToggle}
>
;
diff --git a/src/web/styles/primitives.css b/src/web/styles/primitives.css
index 21c1c21..a22b611 100644
--- a/src/web/styles/primitives.css
+++ b/src/web/styles/primitives.css
@@ -8,6 +8,7 @@
}
.client-rail-action {
+ --client-rail-state-duration: 340ms;
position: relative;
width: 42px;
height: 48px;
@@ -26,11 +27,16 @@
height: 23px;
fill: none;
stroke: currentColor;
- stroke-width: 1.6;
+ stroke-width: 1.75;
stroke-linecap: round;
stroke-linejoin: round;
overflow: visible;
- transition: color 240ms ease, filter 360ms ease, transform 500ms cubic-bezier(0.16, 1, 0.3, 1);
+ transition: color 240ms ease, filter 360ms ease, transform 180ms cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.client-rail-action:active:not(:disabled) svg {
+ transform: scale(0.94);
+ transition: color 120ms ease, filter 120ms ease, transform 120ms cubic-bezier(0.16, 1, 0.3, 1);
}
.client-rail-action > span {
@@ -70,21 +76,16 @@
.client-rail-subscription-back,
.client-rail-subscription-front,
-.client-rail-subscription-lines,
.client-rail-info-ring,
-.client-rail-info-sweep,
.client-rail-info-mark,
-.client-rail-device-primary,
-.client-rail-device-secondary,
-.client-rail-device-link,
+.client-rail-device-monitor,
+.client-rail-device-phone,
.client-rail-device-bridge,
-.client-rail-device-pulse,
-.client-rail-diagnostics-base,
-.client-rail-diagnostics-pulse,
+.client-rail-diagnostics-glass,
.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-dasharray 620ms cubic-bezier(0.16, 1, 0.3, 1), stroke-dashoffset 620ms cubic-bezier(0.16, 1, 0.3, 1);
+ transition: transform var(--client-rail-state-duration) cubic-bezier(0.16, 1, 0.3, 1), opacity 260ms ease, stroke-dasharray var(--client-rail-state-duration) cubic-bezier(0.16, 1, 0.3, 1), stroke-dashoffset var(--client-rail-state-duration) cubic-bezier(0.16, 1, 0.3, 1);
}
.client-rail-action .client-rail-subscription-card,
@@ -92,53 +93,29 @@
fill: var(--client-bg);
}
-.client-rail-subscription-back {
- transform: translate(0, 0);
-}
-
.client-rail-subscription-lines {
- opacity: 0.9;
- transform-origin: left center;
+ opacity: 1;
}
.client-subscription-toggle.is-open .client-rail-subscription-back {
- transform: translate(0.6px, -0.6px);
+ transform: translate(1.5px, -1px);
}
.client-subscription-toggle.is-open .client-rail-subscription-front {
- transform: translate(-1.5px, 1px);
-}
-
-.client-subscription-toggle.is-open .client-rail-subscription-lines {
- opacity: 1;
- animation: client-rail-detail-pulse 10s ease-in-out infinite;
+ transform: translate(-2px, 1px);
}
.client-rail-info-ring {
stroke-dasharray: 1 0;
- stroke-dashoffset: 0.25;
-}
-
-.client-rail-info-sweep {
- stroke-width: 2.4;
- stroke-dasharray: 0.14 0.86;
- opacity: 0;
+ stroke-dashoffset: 0.1;
}
.client-instructions-toggle.is-open .client-rail-info-ring {
- stroke-dasharray: 0.78 0.22;
+ stroke-dasharray: 0.9 0.1;
}
.client-instructions-toggle.is-open .client-rail-info-mark {
- transform: translateY(-0.6px);
-}
-
-.client-instructions-toggle.is-open .client-rail-info-sweep {
- animation: client-rail-info-scan 10s linear infinite;
-}
-
-.client-rail-device-link {
- opacity: 0.9;
+ transform: translateY(-1px);
}
.client-rail-device-bridge {
@@ -146,27 +123,27 @@
transform: scaleX(0);
}
-.client-rail-device-pulse,
-.client-rail-diagnostics-pulse {
+.client-rail-subscription-scan,
+.client-rail-info-sweep,
+.client-rail-device-packet,
+.client-rail-diagnostics-scan,
+.client-rail-rule-scan {
stroke-width: 2.4;
opacity: 0;
- stroke-dasharray: 0.16 0.84;
+ stroke-dasharray: 0.18 0.82;
+ stroke-dashoffset: 1;
}
-.client-rail-device-pulse {
- stroke-dasharray: 0.5 0.5;
+.client-rail-device-packet {
+ stroke-dasharray: 0.36 0.64;
}
-.client-devices-toggle.is-open .client-rail-device-primary {
- transform: translateX(0.6px);
+.client-devices-toggle.is-open .client-rail-device-monitor {
+ transform: translateX(1px);
}
-.client-devices-toggle.is-open .client-rail-device-secondary {
- transform: translateX(-0.6px);
-}
-
-.client-devices-toggle.is-open .client-rail-device-link {
- opacity: 1;
+.client-devices-toggle.is-open .client-rail-device-phone {
+ transform: translateX(-1px);
}
.client-devices-toggle.is-open .client-rail-device-bridge {
@@ -174,23 +151,8 @@
transform: scaleX(1);
}
-.client-devices-toggle.is-open .client-rail-device-pulse {
- animation: client-rail-device-signal 10s linear infinite;
-}
-
-.client-rail-diagnostics-base,
-.client-rail-diagnostics-pulse {
- vector-effect: non-scaling-stroke;
- transform: scaleY(0.8);
-}
-
-.client-diagnostics-toggle.is-open .client-rail-diagnostics-base,
-.client-diagnostics-toggle.is-open .client-rail-diagnostics-pulse {
- transform: scaleY(1);
-}
-
-.client-diagnostics-toggle.is-open .client-rail-diagnostics-pulse {
- animation: client-rail-diagnostics-scan 10s linear infinite;
+.client-diagnostics-toggle.is-open .client-rail-diagnostics-glass {
+ transform: translate(1px, -1px) rotate(-4deg);
}
.client-local-rules-toggle.is-open .client-rail-rule-knob.is-top {
@@ -201,38 +163,25 @@
transform: translateX(-2px);
}
-.client-local-rules-toggle.is-open .client-rail-rule-knob {
- animation: client-rail-rule-pulse 10s ease-in-out infinite;
+.client-subscription-toggle.is-open .client-rail-subscription-scan,
+.client-instructions-toggle.is-open .client-rail-info-sweep,
+.client-devices-toggle.is-open .client-rail-device-packet,
+.client-diagnostics-toggle.is-open .client-rail-diagnostics-scan,
+.client-local-rules-toggle.is-open .client-rail-rule-scan {
+ animation: client-rail-ambient-trace 10s 1.1s linear infinite;
}
-@keyframes client-rail-detail-pulse {
- 0%, 8%, 100% { transform: translateX(0); }
- 4% { transform: translateX(0.8px); }
+.client-local-rules-toggle.is-open .client-rail-rule-scan.is-bottom {
+ animation: client-rail-ambient-trace 10s 1.32s linear infinite;
}
-@keyframes client-rail-info-scan {
+@keyframes client-rail-ambient-trace {
0% { opacity: 0; stroke-dashoffset: 1; }
- 2%, 8% { opacity: 1; }
+ 2% { opacity: 1; }
+ 8% { opacity: 1; stroke-dashoffset: 0; }
10%, 100% { opacity: 0; stroke-dashoffset: 0; }
}
-@keyframes client-rail-device-signal {
- 0% { opacity: 0; stroke-dashoffset: 1; }
- 2%, 8% { opacity: 1; }
- 10%, 100% { opacity: 0; stroke-dashoffset: 0; }
-}
-
-@keyframes client-rail-diagnostics-scan {
- 0% { opacity: 0; stroke-dashoffset: 1; }
- 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 {
cursor: default;
color: var(--client-muted);
@@ -252,6 +201,7 @@
}
.client-rail-action.is-open {
+ --client-rail-state-duration: 480ms;
color: var(--client-accent);
}
diff --git a/src/web/styles/themes.css b/src/web/styles/themes.css
index 3cdefaf..8fa3d12 100644
--- a/src/web/styles/themes.css
+++ b/src/web/styles/themes.css
@@ -117,6 +117,10 @@
stroke-dashoffset: 0;
}
+ .client-rail-action:active:not(:disabled) svg {
+ transform: none;
+ }
+
.client-local-rule,
.client-local-rule-enabled svg,
.client-local-rule-enabled circle,
diff --git a/test/web/responsive-layout-contract.test.js b/test/web/responsive-layout-contract.test.js
index f81ee69..d0b7c8c 100644
--- a/test/web/responsive-layout-contract.test.js
+++ b/test/web/responsive-layout-contract.test.js
@@ -146,34 +146,32 @@ test('secondary menus share one right rail and switch equal drawers as a vertica
assert.match(disabledRulesLabel, /opacity:\s*0/);
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(subscription, /client-rail-subscription-back[\s\S]*client-rail-subscription-front[\s\S]*client-rail-subscription-lines[\s\S]*client-rail-subscription-scan/);
assert.match(instructions, /client-rail-info-ring[\s\S]*client-rail-info-sweep[\s\S]*client-rail-info-mark/);
assert.match(component, / {
assert.match(reducedMotion, /\.client-drawer/);
assert.match(reducedMotion, /transition:\s*none/);
assert.match(reducedMotion, /animation:\s*none/);
+ assert.match(reducedMotion, /\.client-rail-action:active:not\(:disabled\) svg[\s\S]*transform:\s*none/);
});
test('tooltips stay opaque, above adjacent content, and do not stick after pointer clicks', () => {
diff --git a/test/web/style-boundaries.test.js b/test/web/style-boundaries.test.js
index adb0f2a..976c741 100644
--- a/test/web/style-boundaries.test.js
+++ b/test/web/style-boundaries.test.js
@@ -38,25 +38,25 @@ const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex'
const acceptedLedger = {
counts: {
cascadeEdges: 823,
- customProperties: 103,
- declarations: 3333,
+ customProperties: 105,
+ declarations: 3314,
important: 0,
- keyframes: 55,
+ keyframes: 51,
media: 12,
- rules: 961,
- variableReferences: 811,
+ rules: 946,
+ variableReferences: 814,
},
hashes: {
- cascadeEdges: '20d8763bd907f8182994186e2c53e03885ecdcb8dda07c69ff7e64b5ac808625',
- customProperties: 'ef70fb1d9b61b177f1939f811babe1116bee631de26f5eac0aa0d0009ca5a1fb',
- declarations: '633264a595c8ade20cae3adb50bb484c61c22f430b7760155efb4ec4b15d30de',
+ cascadeEdges: '42851684f732dd5e7f04959d009648dd4a8fab85904759da16786411ab199d1a',
+ customProperties: 'dede9598ee62929fb29ff470897cd475ca34eb5af5dda342b09e306f3eaee887',
+ declarations: 'fac169725174c1303de5673abfd42b7316c2180830d66173dca8a2e449b1db5e',
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
duplicateSelectors: '0fa66e370695261a2a5c6a189752c07d518d80d68d3e79a3ba4bb34407893c0e',
- keyframes: '35e40cc917b4c0d5a99bb0a3a626736e134167d29cf303ec41d02bf713d18637',
- ruleDeclarationSequences: 'b7643608ce8526684769dc22bf6641962e324093f9f20b6239e7d71dab031272',
- selectors: '896dc29ad2ed823b0754d4c667c093c5733bc54af951cf2b9ed83e9c022836d5',
- variableReferences: '0ffb99792db35b451fb35d1ced24e1aeef3a6df769cb37bf3d61229b0c970eba',
- witnesses: 'f9c177d5f967c116a1f8a79c05a6d1faf1231775045dbafbc1772d2f916e40e0',
+ keyframes: 'b2b826683e11dfa95cf2d01996d43a02b3a2b33672a14353ebfbee7701d4532e',
+ ruleDeclarationSequences: '7750298af4530d2b0f7d7b0feee21c6bd00a5f9a7ed1d6cfa368e2d9bcace26c',
+ selectors: 'aa487c004e9960e60982e983fcbdb3becef76b0007d3ef85a96f45d4c3d7933e',
+ variableReferences: '67fbd84828e97d10d7fe163ebea6504e8c48355e99616ede2498307d836fa616',
+ witnesses: '5ae376bbd8884cc14a6907fc94302e0cd34974bf0b5beeb66dda1978a23d1959',
},
};
@@ -209,7 +209,7 @@ test('client typography uses the shared semantic scale outside the token owner',
test('accepted stylesheet has pinned declaration, selector, keyframe, variable, and cascade ledgers', () => {
const witnesses = readStyleWitnesses(root);
- assert.equal(witnesses.length, 817);
+ assert.equal(witnesses.length, 824);
assert.equal(witnesses.filter((witness) => witness.unknown || witness.ancestorUnknown).length, 0);
const ledger = createStyleLedger(readStyleSource(root), { witnesses });
assert.deepEqual(ledger.counts, acceptedLedger.counts);
@@ -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-Ceg2DPeq.css']);
+ assert.deepEqual(assets, ['index-DUedU53N.css']);
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
- assert.equal(built.byteLength, 129584);
- assert.equal(sha256(built), '8ccbb067a01c28e729a1aa34908a96e508f07fb9e12c0d05ead10ddc959731bc');
+ assert.equal(built.byteLength, 128983);
+ assert.equal(sha256(built), 'eeeb9cc0bebc7afc37c2d6e60808aa4980f91c564762e47d240c654dd1b00fcf');
});