diff --git a/src/web/components/ClientOverviewPage.jsx b/src/web/components/ClientOverviewPage.jsx index b55ba72..83e95e3 100644 --- a/src/web/components/ClientOverviewPage.jsx +++ b/src/web/components/ClientOverviewPage.jsx @@ -133,6 +133,30 @@ export function ClientOverviewPage({

{canStart ? 'Нажмите, чтобы подключиться' : 'Добавьте ссылку и выберите сервер'}

)} + +
+ Адрес + + {proxyUrls.http.replace(/^https?:\/\//, '')} + +
+ {[ + ['socks5', 'SOCKS5'], + ['http', 'HTTP'], + ].map(([kind, label]) => ( + + ))} +
+
@@ -151,6 +175,7 @@ export function ClientOverviewPage({
setSubscriptionUrl(event.target.value)} onKeyDown={(event) => { @@ -173,6 +199,11 @@ export function ClientOverviewPage({ } }} /> + {subscriptionUrl && ( + + {subscriptionDomain(subscriptionUrl)} + + )} {subscriptionUrl.trim() && ( )} @@ -210,27 +241,6 @@ export function ClientOverviewPage({
-
- {[ - ['socks5', 'SOCKS5'], - ['http', 'HTTP'], - ].map(([kind, label]) => ( - - ))} -
); } diff --git a/src/web/styles.css b/src/web/styles.css index 69921b4..ed9bfd9 100644 --- a/src/web/styles.css +++ b/src/web/styles.css @@ -866,7 +866,7 @@ code, .mono { } .client-shell { - width: min(100%, 440px); + width: min(100%, 1100px); display: grid; gap: 12px; font-family: 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; @@ -874,8 +874,10 @@ code, .mono { } .client-panel { + position: relative; display: grid; - gap: 30px; + place-items: center; + min-height: 520px; padding: 24px 0; } @@ -891,29 +893,35 @@ code, .mono { text-align: center; } +.client-power-section > div { + min-height: 54px; +} + .client-power { width: 96px; height: 96px; display: grid; place-items: center; - border: 1px solid var(--client-border); + border: 0; border-radius: 50%; - background: var(--client-control); + background: transparent; color: var(--client-muted); cursor: pointer; - transition: color 180ms ease, background 180ms ease, border-color 180ms ease, transform 180ms ease; + transition: color 600ms cubic-bezier(0.16, 1, 0.3, 1), transform 600ms cubic-bezier(0.16, 1, 0.3, 1); } .client-power:hover:not(:disabled) { - transform: translateY(-2px); - color: var(--client-text); - border-color: var(--client-muted); + transform: translateY(-3px) scale(1.08); + color: var(--client-accent); } .client-power[aria-checked='true'] { color: var(--client-accent); - background: var(--client-accent-soft); - border-color: var(--client-accent); +} + +.client-power:active:not(:disabled) { + transform: translateY(0) scale(0.92); + transition-duration: 220ms; } .client-power:disabled { @@ -928,6 +936,22 @@ code, .mono { stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; + filter: drop-shadow(0 0 0 transparent); + transition: filter 700ms cubic-bezier(0.16, 1, 0.3, 1), transform 700ms cubic-bezier(0.16, 1, 0.3, 1); +} + +.client-power:hover:not(:disabled) svg { + filter: drop-shadow(0 0 6px color-mix(in oklch, var(--client-accent) 75%, transparent)) drop-shadow(0 0 18px color-mix(in oklch, var(--client-accent) 35%, transparent)); +} + +.client-power:active:not(:disabled) svg { + transform: scale(0.88); + filter: drop-shadow(0 0 10px var(--client-accent)) drop-shadow(0 0 26px color-mix(in oklch, var(--client-accent) 55%, transparent)); + transition-duration: 220ms; +} + +.client-power[aria-checked='true'] svg { + filter: drop-shadow(0 0 5px color-mix(in oklch, var(--client-accent) 65%, transparent)) drop-shadow(0 0 14px color-mix(in oklch, var(--client-accent) 28%, transparent)); } .client-power-section h2 { @@ -950,6 +974,9 @@ code, .mono { } .client-form { + position: absolute; + left: calc(50% + 130px); + width: min(360px, calc(50% - 154px)); display: grid; gap: 36px; } @@ -1028,6 +1055,33 @@ code, .mono { color: var(--client-muted); } +.client-subscription-edit input.has-value { + color: transparent; + caret-color: transparent; +} + +.client-subscription-edit input:-webkit-autofill, +.client-subscription-edit input:-webkit-autofill:hover, +.client-subscription-edit input:-webkit-autofill:focus { + box-shadow: 0 0 0 1000px var(--client-bg) inset; + -webkit-text-fill-color: transparent; +} + +.client-subscription-domain { + position: absolute; + top: 50%; + left: 42px; + right: 42px; + overflow: hidden; + color: var(--client-text); + font-size: 12px; + text-align: center; + text-overflow: ellipsis; + transform: translateY(-50%); + white-space: nowrap; + pointer-events: none; +} + .client-subscription-edit button { position: absolute; top: 4px; @@ -1076,11 +1130,12 @@ code, .mono { letter-spacing: -0.055em; opacity: 0.88; text-shadow: 0 0 20px oklch(0.62 0.05 151 / 0.24); - transition: opacity 300ms cubic-bezier(0.16, 1, 0.3, 1), text-shadow 300ms cubic-bezier(0.16, 1, 0.3, 1); + transition: opacity 300ms cubic-bezier(0.16, 1, 0.3, 1), text-shadow 300ms cubic-bezier(0.16, 1, 0.3, 1), transform 300ms cubic-bezier(0.16, 1, 0.3, 1); } .client-subscription-summary:hover strong { opacity: 1; + transform: translateY(-2px); text-shadow: 0 0 28px oklch(0.7 0.08 151 / 0.42); } @@ -1090,13 +1145,15 @@ code, .mono { .client-server-grid { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: 1fr; gap: 8px; + width: min(100%, 210px); + margin-inline: auto; } .client-server { min-width: 0; - min-height: 56px; + min-height: 50px; display: flex; flex-direction: column; justify-content: center; @@ -1113,9 +1170,13 @@ code, .mono { } .client-server:hover:not(:disabled) { - transform: none; + transform: translateY(-2px); background: transparent; - opacity: 0.82; + opacity: 1; +} + +.client-server:active:not(:disabled) { + transform: translateY(0) scale(0.97); } .client-server.is-selected { @@ -1157,52 +1218,64 @@ code, .mono { .client-proxies { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 24px; - margin-top: 12px; + justify-items: center; + gap: 5px; + width: 220px; + margin-top: 6px; } -.client-proxies button { - min-width: 0; - display: grid; - gap: 4px; - padding: 6px 2px; - border: 0; - background: transparent; - color: var(--client-text); - text-align: left; - cursor: pointer; - opacity: 0.76; - transition: opacity 180ms ease, transform 180ms ease; -} - -.client-proxies button:hover { - transform: translateY(-1px); - opacity: 1; -} - -.client-proxies button.is-copied { - opacity: 1; -} - -.client-proxies span { +.client-proxy-label { color: var(--client-muted); font-size: 9px; font-weight: 700; letter-spacing: 0.08em; + text-transform: uppercase; } -.client-proxies strong { - overflow: hidden; +.client-proxy-address { color: var(--client-text); font-size: 12px; font-weight: 500; - text-overflow: ellipsis; text-shadow: 0 0 12px oklch(0.62 0.05 151 / 0.12); - white-space: nowrap; } -.client-proxies button.is-copied span { +.client-proxies > div { + display: flex; + gap: 8px; + margin-top: 7px; +} + +.client-proxies button { + position: relative; + width: 86px; + padding: 6px 10px; + border: 0; + background: transparent; + color: var(--client-muted); + font: 700 9px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; + letter-spacing: 0.06em; + cursor: pointer; + opacity: 0.76; + transition: color 220ms ease, opacity 220ms ease, transform 220ms cubic-bezier(0.16, 1, 0.3, 1); +} + +.client-copy-check { + position: absolute; + right: 10px; +} + +.client-proxies button:hover { + color: var(--client-text); + opacity: 1; + transform: translateY(-2px); +} + +.client-proxies button:active { + transform: translateY(0) scale(0.98); +} + +.client-proxies button.is-copied { + opacity: 1; color: var(--client-accent); } @@ -1231,21 +1304,23 @@ code, .mono { } .client-panel { + position: static; + grid-template-columns: 1fr; gap: 26px; + min-height: 0; padding: 20px 6px; } - .client-server-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); + .client-form { + position: static; + width: 100%; } - .client-proxies { - grid-template-columns: 1fr; - } } @media (prefers-reduced-motion: reduce) { .client-power, + .client-power svg, .client-server, .client-proxies button, .client-subscription-edit, diff --git a/src/web/utils/clientControls.js b/src/web/utils/clientControls.js index 662e1ef..0a5f385 100644 --- a/src/web/utils/clientControls.js +++ b/src/web/utils/clientControls.js @@ -20,7 +20,12 @@ export function formatConnectionDuration(startedAt, now = Date.now()) { } export function subscriptionDomain(subscriptionHost) { - return String(subscriptionHost || '').split('/')[0]; + const value = String(subscriptionHost || ''); + try { + return new URL(value).host; + } catch { + return value.split('/')[0]; + } } export function localProxyUrls(port = 8082) {