From 826cb45bb072fd175cc9eb4e70aafd7cdc7d65d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=85=BE=E8=AE=AFWorkBuddy?= Date: Thu, 20 Aug 2026 18:03:37 +0800 Subject: [PATCH] =?UTF-8?q?B-248:=20=E6=94=B6=E5=8F=A3=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=80=81=E3=80=81=E7=84=A6=E7=82=B9=E4=B8=8E?= =?UTF-8?q?=E6=A1=8C=E9=9D=A2=E6=80=BB=E5=AE=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_login_frontend.py | 9 +++++++++ tests/test_login_layout.py | 8 +++++++- web/styles.css | 4 +++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/test_login_frontend.py b/tests/test_login_frontend.py index 2ea5f1f..a95e7a1 100644 --- a/tests/test_login_frontend.py +++ b/tests/test_login_frontend.py @@ -40,6 +40,8 @@ class LoginFrontendContractTests(unittest.TestCase): def test_login_css_lives_in_one_section(self) -> None: self.assertIn("minmax(460px, 520px)", self.css) + self.assertIn("max-width: 1180px", self.css) + self.assertIn("margin: 0 auto", self.css) self.assertIn("width: min(440px, 100%)", self.css) self.assertIn("@media (max-width: 1023px)", self.css) self.assertIn("@media (max-width: 720px)", self.css) @@ -55,6 +57,13 @@ class LoginFrontendContractTests(unittest.TestCase): self.assertNotIn(".entry-form {", later) self.assertNotIn(".role-switch {", later) + def test_login_error_and_keyboard_focus_follow_tokens(self) -> None: + self.assertIn(".entry-form #loginError { color: var(--color-danger); }", self.css) + self.assertIn( + ".entry-form .field input:focus-visible { outline: 2px solid var(--color-primary-strong); outline-offset: 3px; }", + self.css, + ) + def test_role_switch_reuses_segmented_rhythm(self) -> None: self.assertIn(".role-switch { display: flex; gap: 4px; padding: 4px;", self.css) self.assertIn("min-height: 36px", self.css) diff --git a/tests/test_login_layout.py b/tests/test_login_layout.py index 17a5944..58281f7 100644 --- a/tests/test_login_layout.py +++ b/tests/test_login_layout.py @@ -43,6 +43,7 @@ MEASURE = r""" if (dx <= 0 || dy <= 0) return 0; return Math.round(dx * dy); }; + const shell = document.querySelector(".entry-shell"); const context = document.querySelector(".entry-context"); const wrap = document.querySelector(".entry-form-wrap"); const form = document.querySelector(".entry-form"); @@ -51,6 +52,7 @@ MEASURE = r""" const brand = document.querySelector("#product-name"); const status = document.querySelector(".entry-status"); const statement = document.querySelector(".entry-statement h1"); + const sh = box(shell); const ctx = box(context); const wr = box(wrap); const fm = box(form); @@ -59,7 +61,9 @@ MEASURE = r""" return { viewport: window.innerWidth, viewportH: window.innerHeight, - grid: getComputedStyle(document.querySelector(".entry-shell")).gridTemplateColumns, + grid: getComputedStyle(shell).gridTemplateColumns, + shellBox: sh, + shellCentered: Boolean(sh && Math.abs((sh.left + sh.right) / 2 - window.innerWidth / 2) <= 1), contextBox: ctx, wrapBox: wr, formBox: fm, @@ -204,6 +208,8 @@ class LoginLayoutTests(unittest.TestCase): self.assertTrue(metrics["changeHidden"], metrics) if mode == "desktop": self.assertTrue(metrics["dualZone"], metrics) + self.assertLessEqual(metrics["shellBox"]["width"], 1180, metrics) + self.assertTrue(metrics["shellCentered"], metrics) self.assertFalse(metrics["stacked"], metrics) self.assertGreaterEqual(metrics["formWidth"], 420, metrics) self.assertLessEqual(metrics["formWidth"], 460, metrics) diff --git a/web/styles.css b/web/styles.css index f58af2c..da878af 100644 --- a/web/styles.css +++ b/web/styles.css @@ -435,7 +435,7 @@ main { width: min(1560px, 100%); margin: 0 auto; padding: 12px 32px 58px; } /* Login */ .entry-page { overflow-x: hidden; } -.entry-shell { width: 100%; max-width: none; min-height: 100vh; display: grid; grid-template-columns: minmax(0, 1fr) minmax(460px, 520px); margin: 0; padding: 0; } +.entry-shell { width: 100%; max-width: 1180px; min-height: 100vh; display: grid; grid-template-columns: minmax(0, 1fr) minmax(460px, 520px); margin: 0 auto; padding: 0; } .entry-context { position: relative; min-width: 0; min-height: 100vh; display: flex; flex-direction: column; justify-content: space-between; padding: clamp(28px, 4vw, 56px) clamp(28px, 4vw, 64px); border-right: 1px solid var(--color-line); background: var(--color-bg-soft); } .entry-brand { display: flex; align-items: center; gap: 12px; min-width: 0; } .entry-brand > span:last-child { display: flex; flex-direction: column; min-width: 0; } @@ -457,7 +457,9 @@ main { width: min(1560px, 100%); margin: 0 auto; padding: 12px 32px 58px; } .entry-form header h2 { font-size: 20px; } .entry-form header p { margin-top: 4px; font-size: 12px; } .entry-form header p, .entry-note { color: var(--color-ink-muted); } +.entry-form #loginError { color: var(--color-danger); } .entry-form .field { gap: 4px; } +.entry-form .field input:focus-visible { outline: 2px solid var(--color-primary-strong); outline-offset: 3px; } .role-switch { display: flex; gap: 4px; padding: 4px; border: 1px solid var(--color-line); border-radius: 13px; background: rgba(0, 0, 0, 0.18); } .role-switch label { position: relative; flex: 1; min-width: 0; cursor: pointer; } .role-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }