From 6b9dc3f0a266fc4f695fcdbdc4176c4dd22f6a13 Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Mon, 24 Aug 2026 00:56:16 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20=ED=95=98?= =?UTF-8?q?=EB=8B=A8=20=EB=B2=84=ED=8A=BC=EC=9D=84=20=ED=95=9C=20=EC=A4=84?= =?UTF-8?q?=EC=97=90=20=EC=84=B8=EC=9A=B0=EA=B3=A0,=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=EC=9D=84=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=A7=8C=EB=93=A0=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 릴리즈 편집 맨 아래에서 저장과 공개가 서로 다른 높이에 서 있었다. `.studio-secondary-button` 는 목록 행에서 쓰려고 전역으로 `margin-top: 24px` 를 갖는데 하단 푸터에서 primary 만 그 여백을 풀어 주고 있었다 — 그래서 저장은 위에, 공개와 공개에서 내리기는 24px 아래에 앉았다. 로그인은 화면이 없었다. `ui-page` 위에 제목 한 줄과 버튼 하나가 전부라, 보호된 주소를 열었을 때 페이지처럼 보이지 않았다. 카드 하나로 세우고 eyebrow·제목·설명·행동을 갖춘 뒤, 로그인하면 어디로 돌아오는지까지 적는다 — 그것이 이 화면이 답해야 할 질문이다. 오류 화면과 다른 모양이어야 한다. 같은 모양이면 작성자는 무언가 고장 난 줄로 읽는다. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XEHXspz4rv5pB5wiiSsVDu --- .../tech-log/presentation/styles/globals.css | 96 +++++++++++++++++++ .../tech-log/presentation/styles/studio.css | 10 +- src/presentation/i18n/catalog.ts | 4 + src/presentation/routes/app-router.tsx | 69 +++++++------ 4 files changed, 149 insertions(+), 30 deletions(-) diff --git a/src/features/tech-log/presentation/styles/globals.css b/src/features/tech-log/presentation/styles/globals.css index 994afa5..0bc0f65 100644 --- a/src/features/tech-log/presentation/styles/globals.css +++ b/src/features/tech-log/presentation/styles/globals.css @@ -100,6 +100,102 @@ a { same reserved height: with only `main` covered the footer still sat at the viewport bottom during loading and then dropped out of view when the content arrived, which is the whole 0.192 the page scored. */ +/* + 로그인 관문. + + 이 화면은 공개 셸과 Studio 셸 양쪽에서 나온다 — 보호된 주소를 열면 그 주소가 속한 레이아웃 안에 + 이 자리가 대신 그려진다. 그래서 두 곳 모두에 있는 토큰만 쓴다. + + 카드로 세우는 이유는 이것이 "화면을 못 그렸다"가 아니라 "여기서 한 걸음 더 필요하다"이기 + 때문이다. 오류 화면과 같은 모양이면 작성자는 무언가 고장 난 줄로 읽는다. +*/ +.auth-gate { + display: grid; + place-items: center; + padding-block: clamp(64px, 12vh, 140px); +} + +.auth-gate .auth-gate__card { + width: min(100%, 34rem); + padding: clamp(32px, 5vw, 52px); + border: 1px solid var(--line-strong); + border-radius: 10px; + background: var(--paper); +} + +.auth-gate .page-header { + margin: 0; +} + +.auth-gate .page-header__eyebrow { + margin: 0 0 14px; + color: var(--signal); + font-family: "IBM Plex Mono", monospace; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.08em; +} + +.auth-gate .page-header h1 { + margin: 0; + font-size: clamp(27px, 3.2vw, 33px); + font-weight: 660; + letter-spacing: -0.035em; + line-height: 1.25; +} + +.auth-gate .page-header__description { + margin: 16px 0 0; + color: var(--ink-soft); + font-size: 16px; + line-height: 1.72; +} + +.auth-gate .auth-gate__actions { + display: grid; + gap: 14px; + margin-top: 32px; + padding-top: 28px; + border-top: 1px solid var(--line); +} + +/* 이 버튼은 화면에서 유일한 행동이다. 폭을 채워 어디를 눌러야 하는지 묻지 않게 한다. */ +.auth-gate .auth-gate__actions button { + display: inline-flex; + min-height: 48px; + align-items: center; + justify-content: center; + width: 100%; + padding-inline: 20px; + border: 1px solid var(--signal); + border-radius: 6px; + background: var(--signal); + color: #fff; + font: inherit; + font-size: 15px; + font-weight: 650; + cursor: pointer; +} + +.auth-gate .auth-gate__actions button:disabled { + opacity: 0.55; + cursor: wait; +} + +.auth-gate .auth-gate__return { + margin: 0; + color: var(--faint); + font-size: 13px; + line-height: 1.6; + overflow-wrap: anywhere; +} + +.auth-gate .ui-terminal-error { + margin: 20px 0 0; + color: var(--warning); + font-size: 14px; +} + .site-frame > main, .site-frame > .ui-page { /* Takes the slack so the footer stays put whether the route rendered a long diff --git a/src/features/tech-log/presentation/styles/studio.css b/src/features/tech-log/presentation/styles/studio.css index 1c9a240..56b7bd9 100644 --- a/src/features/tech-log/presentation/styles/studio.css +++ b/src/features/tech-log/presentation/styles/studio.css @@ -127,8 +127,14 @@ .studio-app .studio-type-list strong { font-size: 18px; } .studio-app .studio-type-list span, .studio-app .studio-type-list small { color: var(--muted); line-height: 1.6; } -.studio-app .studio-create-footer { display: flex; align-items: center; gap: 20px; margin-top: 28px; } -.studio-app .studio-create-footer .studio-primary-button { margin: 0; } +.studio-app .studio-create-footer { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; margin-top: 28px; } +/* + `.studio-secondary-button` 는 목록 행에서 쓰려고 전역으로 `margin-top: 24px` 를 갖는다. 여기서 + 풀어 주지 않으면 primary(저장)만 위로 붙고 secondary(공개·공개에서 내리기)는 24px 아래로 + 내려앉아, 같은 줄의 버튼들이 서로 다른 높이에 선다. +*/ +.studio-app .studio-create-footer .studio-primary-button, +.studio-app .studio-create-footer .studio-secondary-button { margin: 0; } .studio-app .studio-create-footer p { margin: 0; color: var(--muted); font-size: 13px; } .studio-app .studio-primary-button:disabled { opacity: 0.55; cursor: wait; } diff --git a/src/presentation/i18n/catalog.ts b/src/presentation/i18n/catalog.ts index 695d534..7665f8b 100644 --- a/src/presentation/i18n/catalog.ts +++ b/src/presentation/i18n/catalog.ts @@ -87,6 +87,8 @@ const PLATFORM_KO_MESSAGES = { "route.auth.integration.description": "외부 인증 소유자가 연결되면 이 보호 라우트를 사용할 수 있습니다.", "route.auth.recovering.title": "세션을 복구하고 있습니다.", + "route.auth.eyebrow": "SIGN IN", + "route.auth.returnTo": "로그인하면 {path} 로 돌아옵니다.", "route.auth.required.title": "Studio에 로그인해 주세요.", "route.auth.recovering.description": "이전 로그인이 아직 살아 있는지 확인하고 있습니다. 잠시 뒤에도 이 화면이면 다시 시도해 주세요.", @@ -274,6 +276,8 @@ const PLATFORM_EN_MESSAGES = { "route.auth.integration.description": "This protected route is available after an external authentication owner is connected.", "route.auth.recovering.title": "Recovering the session.", + "route.auth.eyebrow": "SIGN IN", + "route.auth.returnTo": "You return to {path} after signing in.", "route.auth.required.title": "Sign in to Studio.", "route.auth.recovering.description": "Checking whether the previous sign-in is still valid. Try again if this screen stays.", diff --git a/src/presentation/routes/app-router.tsx b/src/presentation/routes/app-router.tsx index 1313dfe..aa8f05a 100644 --- a/src/presentation/routes/app-router.tsx +++ b/src/presentation/routes/app-router.tsx @@ -241,35 +241,48 @@ function ProtectedRoute({ ); } const recovering = decision.action === "wait-for-session"; + /* + Signing in is a screen, not a bare gate. This used to be a title and a button + dropped on `ui-page`, so opening a protected address showed no page at all — + just two stray elements. It is one card now, and it names the address the + visitor came for so they know where signing in returns them. + */ return ( -
- - - {failed ? ( -

- {message("shell.session.actionFailed")} -

- ) : null} +
+
+ +
+ +

+ {message("route.auth.returnTo", { + path: `${location.pathname}${location.search}`, + })} +

+
+ {failed ? ( +

+ {message("shell.session.actionFailed")} +

+ ) : null} +
); }