diff --git a/src/shared/versions.js b/src/shared/versions.js
index 82987ae..b8e0b96 100644
--- a/src/shared/versions.js
+++ b/src/shared/versions.js
@@ -1,6 +1,6 @@
export const HARBOR_VERSIONS = Object.freeze({
- macClient: '0.8.1',
- gatewayClient: '0.8.1',
+ macClient: '0.8.2',
+ gatewayClient: '0.8.2',
gatewayBackend: '0.8.0',
});
diff --git a/src/web/components/ClientOverviewPage.jsx b/src/web/components/ClientOverviewPage.jsx
index 50f7a2e..b1b46cf 100644
--- a/src/web/components/ClientOverviewPage.jsx
+++ b/src/web/components/ClientOverviewPage.jsx
@@ -1068,46 +1068,47 @@ export function ClientOverviewPage({
blocked={gatewayAutoBlocked}
onSetGatewayAuto={onSetGatewayAuto}
/>
- {hasSubscription && subscriptionContentReady && }
- {hasSubscription && subscriptionContentReady && }
+ {hasSubscription && subscriptionContentReady && }
{showPower && (
diff --git a/src/web/styles.css b/src/web/styles.css
index aeff5f6..91865ce 100644
--- a/src/web/styles.css
+++ b/src/web/styles.css
@@ -662,107 +662,21 @@ p {
92% { transform: translateX(-0.75px); }
}
-.client-instructions-toggle {
+.client-secondary-menu {
position: fixed;
top: 50%;
- left: 14px;
+ right: max(14px, env(safe-area-inset-right));
z-index: 30;
- width: 42px;
- height: 54px;
display: grid;
- place-items: center;
- padding: 0;
- border: 0;
- background: transparent;
- color: var(--client-muted);
- cursor: pointer;
+ gap: 6px;
transform: translateY(-50%);
- transition: transform 440ms cubic-bezier(0.16, 1, 0.3, 1);
-}
-
-.client-instructions-toggle svg {
- position: relative;
- z-index: 1;
- width: 23px;
- height: 18px;
- fill: none;
- stroke: currentColor;
- stroke-width: 1.7;
- stroke-linecap: round;
- stroke-linejoin: round;
- overflow: visible;
- transition: transform 500ms cubic-bezier(0.16, 1, 0.3, 1);
-}
-
-.client-instructions-toggle path {
- opacity: 0;
- transform: translateX(-5px);
- transition: opacity 260ms ease, transform 480ms cubic-bezier(0.16, 1, 0.3, 1);
-}
-
-.client-instructions-toggle path:last-child {
- opacity: 1;
- transform: translateX(-4px);
-}
-
-.client-instructions-toggle span {
- position: absolute;
- left: 48px;
- width: max-content;
- color: var(--client-text);
- font: 700 10px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace;
- letter-spacing: 0.04em;
- opacity: 0;
- filter: blur(5px);
- pointer-events: none;
- transform: translateX(-8px);
- transition: opacity 350ms ease, filter 350ms ease, transform 500ms cubic-bezier(0.16, 1, 0.3, 1);
-}
-
-.client-instructions-toggle:hover,
-.client-instructions-toggle:focus-visible {
- outline: none;
- transform: translateY(-50%) translateX(3px);
-}
-
-.client-instructions-toggle:hover path,
-.client-instructions-toggle:focus-visible path {
- opacity: 1;
- transform: translateX(0);
-}
-
-.client-instructions-toggle:hover span,
-.client-instructions-toggle:focus-visible span {
- opacity: 1;
- filter: blur(0);
- transform: translateX(0);
-}
-
-.client-instructions-toggle.is-open svg {
- transform: rotate(180deg);
-}
-
-.client-instructions-toggle.is-open path {
- opacity: 0;
-}
-
-.client-instructions-toggle.is-open path:first-child {
- opacity: 1;
- transform: translateX(4px);
-}
-
-.client-instructions-toggle.is-open span {
- opacity: 0;
- visibility: hidden;
}
+.client-instructions-toggle,
.client-local-rules-toggle {
- position: fixed;
- top: 50%;
- right: 14px;
- z-index: 30;
+ position: relative;
width: 42px;
- height: 54px;
+ height: 48px;
display: grid;
place-items: center;
padding: 0;
@@ -770,10 +684,10 @@ p {
background: transparent;
color: var(--client-muted);
cursor: pointer;
- transform: translateY(-50%);
transition: color 240ms ease, transform 440ms cubic-bezier(0.16, 1, 0.3, 1);
}
+.client-instructions-toggle svg,
.client-local-rules-toggle svg {
width: 23px;
height: 23px;
@@ -782,9 +696,11 @@ p {
stroke-width: 1.6;
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);
}
+.client-instructions-toggle span,
.client-local-rules-toggle span {
position: absolute;
right: 48px;
@@ -799,18 +715,17 @@ p {
transition: opacity 350ms ease, filter 350ms ease, transform 500ms cubic-bezier(0.16, 1, 0.3, 1);
}
+.client-instructions-toggle:hover,
+.client-instructions-toggle:focus-visible,
.client-local-rules-toggle:hover,
.client-local-rules-toggle:focus-visible {
- outline: none;
+ outline: 0;
color: var(--client-accent);
- transform: translateY(-50%) translateX(-3px);
-}
-
-.client-local-rules-toggle:hover svg,
-.client-local-rules-toggle:focus-visible svg {
- filter: drop-shadow(0 0 7px color-mix(in oklch, var(--client-accent) 52%, transparent));
+ transform: translateX(-3px);
}
+.client-instructions-toggle:hover span,
+.client-instructions-toggle:focus-visible span,
.client-local-rules-toggle:hover span,
.client-local-rules-toggle:focus-visible span {
opacity: 1;
@@ -818,9 +733,19 @@ p {
transform: translateX(0);
}
+.client-instructions-toggle:hover svg,
+.client-instructions-toggle:focus-visible svg,
+.client-local-rules-toggle:hover svg,
+.client-local-rules-toggle:focus-visible svg,
+.client-instructions-toggle.is-open svg,
+.client-local-rules-toggle.is-open svg {
+ filter: drop-shadow(0 0 7px color-mix(in oklch, var(--client-accent) 52%, transparent));
+}
+
.client-local-rules-toggle:disabled {
cursor: default;
color: var(--client-muted);
+ transform: none;
}
.client-local-rules-toggle:disabled span {
@@ -829,6 +754,7 @@ p {
transform: translateX(0);
}
+.client-instructions-toggle.is-open,
.client-local-rules-toggle.is-open {
color: var(--client-accent);
}
@@ -845,6 +771,7 @@ p {
transform: rotate(90deg);
}
+.client-instructions-toggle.is-open span,
.client-local-rules-toggle.is-open span {
opacity: 0;
visibility: hidden;
@@ -1380,16 +1307,16 @@ p {
.client-instructions {
position: fixed;
- inset: 0 auto 0 0;
+ inset: 0 0 0 auto;
z-index: 20;
width: min(470px, 100vw);
overflow-y: auto;
background: color-mix(in oklch, var(--client-bg) 99%, var(--client-panel));
color: var(--client-text);
- box-shadow: 26px 0 72px oklch(0.09 0.015 145 / 0.12);
+ box-shadow: -26px 0 72px oklch(0.09 0.015 145 / 0.12);
opacity: 0;
visibility: hidden;
- transform: translateX(-104%);
+ transform: translateX(104%);
transition: transform 760ms cubic-bezier(0.16, 1, 0.3, 1), opacity 500ms ease, visibility 0s 760ms;
}
@@ -1403,7 +1330,7 @@ p {
.client-instructions-sheet {
position: relative;
min-height: 100%;
- padding: 54px 34px 72px 72px;
+ padding: 54px 72px 72px 34px;
font-family: 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace;
}
@@ -3520,11 +3447,7 @@ p {
transform: translateX(-50%) scale(1.35);
}
- .client-instructions-toggle {
- left: 8px;
- }
-
- .client-local-rules-toggle {
+ .client-secondary-menu {
right: 8px;
}
@@ -3572,7 +3495,7 @@ p {
}
.client-instructions-sheet {
- padding: 40px 18px 60px 58px;
+ padding: 40px 58px 60px 18px;
}
.client-local-rules-sheet {
diff --git a/test/web/responsive-layout-contract.test.js b/test/web/responsive-layout-contract.test.js
index 8e1bc86..35c59f1 100644
--- a/test/web/responsive-layout-contract.test.js
+++ b/test/web/responsive-layout-contract.test.js
@@ -73,6 +73,17 @@ test('tablet and mobile regions use normal flow with viewport-safe widths', () =
assert.ok(Math.min(360, desktopOuterColumn) <= desktopOuterColumn, 'details fit the 1440px desktop grid');
});
+test('secondary menus share one right rail and both drawers open from the right', () => {
+ assert.match(component, /