diff --git a/apps/web/src/pages/session/AllianceCheckpointPrompt.tsx b/apps/web/src/pages/session/AllianceCheckpointPrompt.tsx index c335b8e..510b986 100644 --- a/apps/web/src/pages/session/AllianceCheckpointPrompt.tsx +++ b/apps/web/src/pages/session/AllianceCheckpointPrompt.tsx @@ -225,6 +225,7 @@ export function AllianceCheckpointPrompt({ } const copy = CHECKPOINT_COPY[checkpoint]; + const chosenCount = DIMENSIONS.filter((dimension) => draft[dimension] != null).length; const expanded = checkpoint === "pre" || midExpanded; if (!expanded) { @@ -244,7 +245,13 @@ export function AllianceCheckpointPrompt({ return (
@@ -253,8 +260,19 @@ export function AllianceCheckpointPrompt({

{copy.title}

{copy.body}

- - 3 AXES + {/* 3 AXES 배지는 정보가 없었다. 제출 버튼이 왜 잠겨 있는지 답하는 + 남은 선택 수로 바꾼다. */} + + + {chosenCount}/{DIMENSIONS.length} + + {chosenCount === DIMENSIONS.length ? "선택 완료" : "축 선택함"}
diff --git a/apps/web/src/pages/session/alliance-checkpoint-prompt.css b/apps/web/src/pages/session/alliance-checkpoint-prompt.css index 34c7a27..3d292a1 100644 --- a/apps/web/src/pages/session/alliance-checkpoint-prompt.css +++ b/apps/web/src/pages/session/alliance-checkpoint-prompt.css @@ -1,7 +1,20 @@ +/* ===================================================================== + 회기 자기점검(Alliance checkpoint) + 표현은 세 가지다. + 1) 인라인 스트립 — 로딩 / 실패 / mid 접힘 / 잠금 영수증 + 2) 확장 카드 — mid 펼침. 회기 그리드 한 행 안에 들어가야 하므로 조밀하다. + 3) 게이트 화면 — pre. 첫 발화 전 유일한 화면이라 카드가 화면 중앙에 선다. + 철칙: 카드를 화면 높이만큼 늘려 내부에 빈 공간을 만들지 않는다. 5점 척도의 + 한글 라벨은 어느 폭에서도 자르거나 숨기지 않고 줄바꿈으로 살린다. + ===================================================================== */ + .sx-alliance-checkpoint { --checkpoint-ink: var(--text-strong, #132822); --checkpoint-muted: var(--text-muted, #66736f); --checkpoint-line: color-mix(in srgb, var(--accent, #2f695b) 18%, transparent); + --checkpoint-hair: color-mix(in srgb, var(--checkpoint-ink) 12%, transparent); + --checkpoint-title: clamp(1.02rem, 1.5vw, 1.24rem); + --checkpoint-pad: clamp(16px, 1.6vw, 24px); position: relative; display: flex; align-items: center; @@ -13,6 +26,8 @@ background: color-mix(in srgb, var(--surface, #fff) 94%, var(--accent, #2f695b) 6%); border: 1px solid var(--checkpoint-line); border-radius: 14px; + word-break: keep-all; + overflow-wrap: break-word; } .sx-alliance-checkpoint > div:not(.sx-alliance-checkpoint__axes) { @@ -49,9 +64,11 @@ background: color-mix(in srgb, var(--surface, #fff) 94%, var(--crit-tint, #faeae8) 6%); } +/* ── 확장 카드 공통 ───────────────────────────────────────────────── */ + .sx-alliance-checkpoint.is-expanded { display: block; - padding: clamp(18px, 2vw, 28px); + padding: var(--checkpoint-pad); border-color: color-mix(in srgb, var(--accent, #2f695b) 28%, transparent); box-shadow: 0 18px 50px rgb(18 42 35 / 9%); } @@ -64,11 +81,15 @@ display: flex; align-items: flex-start; justify-content: space-between; - gap: 22px; - padding-bottom: 18px; + gap: 16px 22px; + padding-bottom: 14px; border-bottom: 1px solid var(--checkpoint-line); } +.sx-alliance-checkpoint__head > div { + min-width: 0; +} + .sx-alliance-checkpoint__eyebrow { color: var(--accent, #2f695b); font-size: 0.68rem; @@ -78,97 +99,141 @@ } .sx-alliance-checkpoint__head h2 { - margin: 6px 0 5px; - font-size: clamp(1.05rem, 1.7vw, 1.34rem); - letter-spacing: -0.035em; + margin: 5px 0; + font-size: var(--checkpoint-title); + line-height: 1.3; + letter-spacing: -0.03em; } .sx-alliance-checkpoint__head p { - max-width: 72ch; + max-width: 62ch; margin: 0; color: var(--checkpoint-muted); font-size: 0.86rem; line-height: 1.55; } -.sx-alliance-checkpoint__step { +/* 남은 선택 수는 장식이 아니라 제출 버튼이 왜 잠겨 있는지에 대한 답이다. */ +.sx-alliance-checkpoint__progress { + display: inline-flex; flex: 0 0 auto; - color: color-mix(in srgb, var(--accent, #2f695b) 74%, var(--checkpoint-ink)); - font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; - font-size: 0.68rem; - font-weight: 750; - letter-spacing: 0.12em; + align-items: baseline; + gap: 5px; + align-self: center; + padding: 5px 11px; + color: var(--checkpoint-muted); + font-size: 0.74rem; + font-weight: 650; + white-space: nowrap; + background: color-mix(in srgb, var(--checkpoint-ink) 6%, transparent); + border-radius: 999px; } +.sx-alliance-checkpoint__progress b { + color: var(--checkpoint-ink); + font-size: 0.86rem; + font-variant-numeric: tabular-nums; +} + +.sx-alliance-checkpoint__progress.is-done { + color: var(--accent, #2f695b); + background: color-mix(in srgb, var(--accent, #2f695b) 13%, transparent); +} + +.sx-alliance-checkpoint__progress.is-done b { + color: var(--accent, #2f695b); +} + +/* ── 3축 ─────────────────────────────────────────────────────────── */ + .sx-alliance-checkpoint__axes { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 1px; - margin-top: 18px; - overflow: hidden; - background: var(--checkpoint-line); - border: 1px solid var(--checkpoint-line); - border-radius: 12px; + gap: 0; + margin-top: 2px; } .sx-alliance-checkpoint__axes fieldset { min-width: 0; margin: 0; - padding: 16px; - background: var(--surface, #fff); + padding: 14px 0 15px; border: 0; + border-top: 1px solid var(--checkpoint-hair); +} + +.sx-alliance-checkpoint__axes fieldset:first-of-type { + border-top: 0; } .sx-alliance-checkpoint__axes legend { display: block; width: 100%; - min-height: 54px; padding: 0; } -.sx-alliance-checkpoint__axes legend b, -.sx-alliance-checkpoint__axes legend span { - display: block; -} - .sx-alliance-checkpoint__axes legend b { - margin-bottom: 3px; - font-size: 0.79rem; + color: var(--checkpoint-ink); + font-size: 0.84rem; } .sx-alliance-checkpoint__axes legend span { color: var(--checkpoint-muted); - font-size: 0.74rem; + font-size: 0.82rem; line-height: 1.45; } +/* 축 이름과 질문은 넓은 폭에서 한 줄로 붙어 세로를 아낀다. */ +@media (min-width: 700px) { + .sx-alliance-checkpoint__axes legend b { + margin-right: 10px; + } +} + +@media (max-width: 699px) { + .sx-alliance-checkpoint__axes legend b, + .sx-alliance-checkpoint__axes legend span { + display: block; + } + + .sx-alliance-checkpoint__axes legend b { + margin-bottom: 3px; + } +} + .sx-alliance-checkpoint__scale { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 5px; - margin-top: 12px; + gap: 6px; + margin-top: 10px; } .sx-alliance-checkpoint__scale label { position: relative; display: grid; min-width: 0; - place-items: center; - padding: 8px 2px 7px; + min-height: 50px; + align-content: start; + justify-items: center; + gap: 2px; + padding: 7px 4px; cursor: pointer; - background: color-mix(in srgb, var(--surface, #fff) 91%, var(--checkpoint-ink) 9%); - border: 1px solid transparent; - border-radius: 8px; - transition: background 150ms ease, border-color 150ms ease, transform 150ms ease; + background: var(--bg-surface-2, #f4f2ee); + border: 1px solid var(--checkpoint-hair); + border-radius: 10px; + transition: background 150ms ease, border-color 150ms ease, color 150ms ease; } .sx-alliance-checkpoint__scale label:hover { - transform: translateY(-1px); - border-color: color-mix(in srgb, var(--accent, #2f695b) 28%, transparent); + background: color-mix(in srgb, var(--accent, #2f695b) 8%, var(--bg-surface-2, #f4f2ee)); + border-color: color-mix(in srgb, var(--accent, #2f695b) 42%, transparent); } .sx-alliance-checkpoint__scale label.is-selected { - color: var(--surface, #fff); + color: var(--text-on-accent, #fbfaf8); + background: var(--accent, #2f695b); + border-color: var(--accent, #2f695b); +} + +.sx-alliance-checkpoint__scale label.is-selected:hover { background: var(--accent, #2f695b); } @@ -180,42 +245,62 @@ } .sx-alliance-checkpoint__scale label:focus-within { - outline: 3px solid color-mix(in srgb, var(--accent, #2f695b) 30%, transparent); + outline: 3px solid color-mix(in srgb, var(--accent, #2f695b) 34%, transparent); outline-offset: 2px; } .sx-alliance-checkpoint__scale span { - font-size: 0.81rem; + font-size: 0.84rem; font-weight: 780; + font-variant-numeric: tabular-nums; + line-height: 1.1; } +/* 척도 라벨은 척도의 의미 그 자체다. 자르지도 숨기지도 않고 줄바꿈만 허용한다. + 단 '보통이/다'처럼 어절 안에서 끊기면 안 되므로 어절 경계만 허용한다. */ .sx-alliance-checkpoint__scale small { max-width: 100%; - margin-top: 2px; - overflow: hidden; - font-size: 0.54rem; - text-overflow: ellipsis; - white-space: nowrap; + font-size: 0.68rem; + line-height: 1.25; + text-align: center; + word-break: keep-all; + overflow-wrap: normal; } +fieldset[disabled] .sx-alliance-checkpoint__scale label { + cursor: progress; + opacity: 0.62; +} + +/* ── 커밋 줄 ─────────────────────────────────────────────────────── */ + .sx-alliance-checkpoint__commit { display: flex; align-items: center; justify-content: space-between; - gap: 20px; - margin-top: 18px; + gap: 14px 20px; + margin-top: 14px; + padding-top: 14px; + border-top: 1px solid var(--checkpoint-line); } .sx-alliance-checkpoint__commit > span { display: flex; - align-items: center; + align-items: flex-start; gap: 7px; color: var(--checkpoint-muted); font-size: 0.74rem; + line-height: 1.45; +} + +.sx-alliance-checkpoint__commit > span svg { + flex: 0 0 auto; + margin-top: 1px; } .sx-alliance-checkpoint__commit > div { display: flex; + flex: 0 0 auto; gap: 8px; } @@ -239,16 +324,84 @@ 50% { opacity: 0.42; transform: scale(0.72); } } -@media (max-width: 900px) { - .sx-alliance-checkpoint__axes { - grid-template-columns: 1fr; +/* ── 인라인 mid 카드: 회기 그리드 한 행 안에 머무르도록 압축 ──────── */ + +.sx-alliance-checkpoint.is-mid.is-expanded { + --checkpoint-pad: clamp(14px, 1.2vw, 18px); + --checkpoint-title: 1.02rem; +} + +.sx-alliance-checkpoint.is-mid.is-expanded .sx-alliance-checkpoint__head p { + font-size: 0.82rem; +} + +.sx-alliance-checkpoint.is-mid.is-expanded .sx-alliance-checkpoint__axes fieldset { + padding: 11px 0 12px; +} + +.sx-alliance-checkpoint.is-mid.is-expanded .sx-alliance-checkpoint__scale label { + min-height: 44px; +} + +@media (min-width: 1080px) { + /* mid는 축어록을 밀어내지 않도록 넓은 화면에서만 3열로 눕힌다. */ + .sx-alliance-checkpoint.is-mid.is-expanded .sx-alliance-checkpoint__axes { + grid-template-columns: repeat(3, minmax(0, 1fr)); + column-gap: 22px; } - .sx-alliance-checkpoint__axes legend { - min-height: 0; + .sx-alliance-checkpoint.is-mid.is-expanded .sx-alliance-checkpoint__axes fieldset { + padding: 12px 0 0; + border-top: 0; + } + + .sx-alliance-checkpoint.is-mid.is-expanded .sx-alliance-checkpoint__axes legend b, + .sx-alliance-checkpoint.is-mid.is-expanded .sx-alliance-checkpoint__axes legend span { + display: block; + } + + .sx-alliance-checkpoint.is-mid.is-expanded .sx-alliance-checkpoint__axes legend b { + margin: 0 0 3px; + } + + .sx-alliance-checkpoint.is-mid.is-expanded .sx-alliance-checkpoint__axes legend span { + min-height: 2.9em; + font-size: 0.76rem; } } +/* ── pre 게이트 화면: 카드가 화면 중앙에 서고 높이는 내용이 정한다 ── */ + +.sx-alliance-checkpoint.is-gate { + --checkpoint-title: clamp(1.22rem, 2.1vw, 1.62rem); + --checkpoint-pad: clamp(20px, 2.2vw, 30px); + /* 카드 폭은 5칸 척도가 자연스럽게 146px 안팎이 되는 지점에서 멈춘다. + 더 넓히면 라벨이 빈 상자 안에 뜨고, 제목·질문·척도의 오른쪽 끝이 어긋난다. */ + width: min(820px, calc(100% - 24px)); +} + +.sx-alliance-checkpoint.is-gate .sx-alliance-checkpoint__head { + padding-bottom: 16px; +} + +.sx-alliance-checkpoint.is-gate .sx-alliance-checkpoint__head p { + font-size: 0.9rem; +} + +.sx-alliance-checkpoint.is-gate .sx-alliance-checkpoint__axes fieldset { + padding: 16px 0 17px; +} + +.sx-alliance-checkpoint.is-gate .sx-alliance-checkpoint__scale label { + min-height: 56px; +} + +.sx-alliance-checkpoint.is-gate .sx-alliance-checkpoint__scale small { + font-size: 0.72rem; +} + +/* ── 좁은 화면 ───────────────────────────────────────────────────── */ + @media (max-width: 620px) { .sx-alliance-checkpoint { width: calc(100% - 20px); @@ -261,10 +414,21 @@ flex-wrap: wrap; } - .sx-alliance-checkpoint__head, - .sx-alliance-checkpoint__commit { + /* 좁은 화면에서 진행 칩이 제목 위로 올라오면 위계가 뒤집힌다. + 제목과 설명을 먼저 읽히고 칩은 첫 축 바로 앞에 둔다. */ + .sx-alliance-checkpoint__head { align-items: flex-start; flex-direction: column; + gap: 12px; + } + + .sx-alliance-checkpoint__progress { + align-self: flex-start; + } + + .sx-alliance-checkpoint__commit { + align-items: stretch; + flex-direction: column; } .sx-alliance-checkpoint__commit > div { @@ -274,10 +438,54 @@ .sx-alliance-checkpoint__commit button { width: 100%; + min-height: 46px; + } + + .sx-alliance-checkpoint__scale { + gap: 5px; + } + + .sx-alliance-checkpoint__scale label { + padding: 6px 2px; } .sx-alliance-checkpoint__scale small { - display: none; + font-size: 0.63rem; + } +} + +@media (max-width: 380px) { + .sx-alliance-checkpoint__scale { + gap: 3px; + } + + .sx-alliance-checkpoint__scale label { + padding: 6px 1px; + } + + .sx-alliance-checkpoint__scale small { + font-size: 0.57rem; + } +} + +/* 저높이 폰에서는 게이트 카드가 화면을 넘긴다. 카드 안에서만 스크롤시키고 + 장식 여백을 걷어 다섯 칸 척도와 제출 버튼을 계속 만질 수 있게 둔다. */ +@media (max-height: 660px) { + .sx-alliance-checkpoint.is-gate { + --checkpoint-title: 1.08rem; + --checkpoint-pad: 16px; + } + + .sx-alliance-checkpoint.is-gate .sx-alliance-checkpoint__head p { + font-size: 0.8rem; + } + + .sx-alliance-checkpoint.is-gate .sx-alliance-checkpoint__axes fieldset { + padding: 11px 0 12px; + } + + .sx-alliance-checkpoint.is-gate .sx-alliance-checkpoint__scale label { + min-height: 46px; } } diff --git a/apps/web/src/pages/session/session.css b/apps/web/src/pages/session/session.css index ea885a8..34bbad4 100644 --- a/apps/web/src/pages/session/session.css +++ b/apps/web/src/pages/session/session.css @@ -62,29 +62,35 @@ .sx-page--active:has(.sx-timebar) { grid-template-rows: min-content min-content min-content minmax(0, 1fr) min-content; } -.sx-page--active.sx-alliance-gated { +/* 게이트 상태의 행 정의는 아래 :has() 두 개짜리 규칙(0,3,0)보다 특이도가 낮아 + 시간 만료 바가 뜨는 순간 밀려났다. 그러면 숨긴 축어록·컨트롤바의 빈 트랙이 + 그대로 남아 화면 절반이 비어 보인다. 같은 :has()를 붙여 항상 이기게 한다. */ +.sx-page--active.sx-alliance-gated, +.sx-page--active.sx-alliance-gated:has(> .sx-alliance-checkpoint-slot) { grid-template-rows: min-content minmax(0, 1fr); } +.sx-page--active.sx-alliance-gated:has(> .sx-timebar):has(> .sx-alliance-checkpoint-slot) { + grid-template-rows: min-content min-content minmax(0, 1fr); +} .sx-page--active.sx-alliance-gated .sx-grid { display: none; } .sx-page--active.sx-alliance-gated .sx-controlbar { display: none; } +/* 게이트 카드는 행 높이만큼 늘리지 않는다. 예전에는 stretch + 1fr 축 행이라 + 1920×1080에서 카드의 66%가 빈 공간이었고 축 블록이 허공에 떠 보였다. + 이제 카드는 내용 높이로 화면 중앙에 서고, 넘칠 때만 카드 안에서 스크롤한다. */ .sx-page--active.sx-alliance-gated .sx-alliance-checkpoint { - height: 100%; min-height: 0; - margin-top: 0; - overflow: hidden; - align-self: stretch; + max-height: 100%; + margin: 0 auto; + overflow-y: auto; + overscroll-behavior: contain; + align-self: center; } .sx-page--active.sx-alliance-gated .sx-alliance-checkpoint.is-expanded { - display: grid; - grid-template-rows: auto minmax(0, 1fr) auto auto; -} -.sx-page--active.sx-alliance-gated .sx-alliance-checkpoint__axes { - align-content: start; - overflow-y: auto; + display: block; } [data-theme="light"] .sx-page--active { color-scheme: light; @@ -2556,15 +2562,16 @@ background: rgba(3, 9, 8, 0.62); backdrop-filter: blur(8px); } +/* 패널도 다크 stage 잔재였다. 배경만 하드코딩 다크였고 글자는 테마 토큰이라 + 라이트에서 제목 2.68:1 · 본문 1.14:1로 사실상 보이지 않았다. 불투명 토큰 + 표면으로 되돌린다(스크림 위라 유리 표면 대신 불투명 --bg-app 사용). */ .sx-end-dialog__panel { width: min(100%, 420px); padding: var(--sp-5); - border: 1px solid rgba(203, 227, 220, 0.16); + border: 1px solid var(--glass-border); border-radius: var(--radius-lg); - background: - linear-gradient(180deg, rgba(24, 38, 35, 0.98), rgba(13, 24, 21, 0.98)), - var(--asset-warm-elements) center / cover no-repeat; - box-shadow: 0 26px 70px rgba(0, 0, 0, 0.42); + background: var(--glass-specular), var(--bg-app); + box-shadow: 0 26px 70px rgba(0, 0, 0, 0.34); color: var(--text-body); } .sx-end-dialog__head { @@ -2580,8 +2587,8 @@ display: inline-flex; align-items: center; justify-content: center; - background: rgba(216, 100, 89, 0.15); - color: #f1b2aa; + background: var(--crit-tint); + color: var(--crit-text); } .sx-end-dialog h2 { margin: 0; @@ -2613,17 +2620,17 @@ font-weight: 700; } .sx-end-dialog__secondary { - border: 1px solid rgba(203, 227, 220, 0.16); - background: rgba(255, 255, 255, 0.06); + border: 1px solid var(--border-subtle, var(--hair)); + background: var(--bg-surface-2); color: var(--text-body); } .sx-end-dialog__danger { border: 1px solid transparent; background: var(--crit-solid); - color: #fff; + color: var(--text-on-accent); } .sx-end-dialog__secondary:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.1); + background: var(--bg-tint); color: var(--text-strong); } .sx-end-dialog__danger:hover:not(:disabled) { @@ -4554,6 +4561,10 @@ .sx-page--active:has(> .sx-timebar) { grid-template-rows: min-content min-content minmax(0, 1fr) min-content; } +/* 이 바는 세션이 다크 stage 팔레트를 강제하던 시절의 잔재였다. --bg-panel은 + 어디에도 정의된 적이 없어 항상 하드코딩 다크 폴백으로 칠해졌고, 글자만 + 테마 토큰을 따라가 라이트에서 제목 1.13:1 · 설명 1.76:1까지 떨어졌다. + 두 값 모두 의미색 토큰으로 되돌려 두 테마에서 같은 위계를 갖게 한다. */ .sx-timebar { display: flex; align-items: center; @@ -4562,11 +4573,9 @@ margin: 0 0 10px; padding: 9px 14px; border-radius: var(--radius); - border: 1px solid rgba(228, 179, 99, 0.5); - background: - linear-gradient(0deg, rgba(228, 179, 99, 0.12), rgba(228, 179, 99, 0.12)), - var(--bg-panel, rgba(20, 26, 24, 0.92)); - color: var(--text-strong, #eef4f2); + border: 1px solid color-mix(in srgb, var(--warn-solid) 46%, transparent); + background: var(--warn-tint); + color: var(--text-strong); font-size: 13px; } .sx-timebar b { @@ -4574,7 +4583,7 @@ letter-spacing: -0.01em; } .sx-timebar__desc { - color: var(--text-body, rgba(238, 244, 242, 0.74)); + color: var(--text-body); font-weight: 560; min-width: 0; overflow-wrap: anywhere; @@ -4583,33 +4592,31 @@ width: 8px; height: 8px; border-radius: 50%; - background: #e4b363; - box-shadow: 0 0 0 0 rgba(228, 179, 99, 0.5); + background: var(--warn-solid); + box-shadow: 0 0 0 0 color-mix(in srgb, var(--warn-solid) 50%, transparent); animation: sx-timebar-pulse 1.8s ease-out infinite; } .sx-timebar.is-over { - border-color: rgba(224, 122, 95, 0.55); - background: - linear-gradient(0deg, rgba(224, 122, 95, 0.14), rgba(224, 122, 95, 0.14)), - var(--bg-panel, rgba(20, 26, 24, 0.92)); + border-color: color-mix(in srgb, var(--crit-solid) 48%, transparent); + background: var(--crit-tint); } .sx-timebar.is-over .sx-timebar__dot { - background: #e07a5f; - box-shadow: 0 0 0 0 rgba(224, 122, 95, 0.5); + background: var(--crit-solid); + box-shadow: 0 0 0 0 color-mix(in srgb, var(--crit-solid) 50%, transparent); } .sx-timebar__end { margin-left: auto; padding: 6px 12px; border-radius: calc(var(--radius-sm) - 1px); - border: 1px solid rgba(224, 122, 95, 0.6); - background: rgba(224, 122, 95, 0.16); - color: inherit; + border: 1px solid color-mix(in srgb, var(--crit-solid) 58%, transparent); + background: color-mix(in srgb, var(--crit-solid) 12%, transparent); + color: var(--crit-text); font-size: 12.5px; font-weight: 740; cursor: pointer; } .sx-timebar__end:hover { - background: rgba(224, 122, 95, 0.26); + background: color-mix(in srgb, var(--crit-solid) 22%, transparent); } @keyframes sx-timebar-pulse { 0% { box-shadow: 0 0 0 0 rgba(228, 179, 99, 0.45); } @@ -4811,9 +4818,13 @@ .sx-page--active:has(> .sx-timebar):has(> .sx-alliance-checkpoint-slot) { grid-template-rows: min-content min-content min-content minmax(0, 1fr) min-content; } -.sx-page--active.sx-alliance-gated { +.sx-page--active.sx-alliance-gated, +.sx-page--active.sx-alliance-gated:has(> .sx-alliance-checkpoint-slot) { grid-template-rows: min-content minmax(0, 1fr); } +.sx-page--active.sx-alliance-gated:has(> .sx-timebar):has(> .sx-alliance-checkpoint-slot) { + grid-template-rows: min-content min-content minmax(0, 1fr); +} @media (min-width: 1280px) { .sx-page--active:has(> .sx-alliance-checkpoint-slot) { @@ -4822,9 +4833,13 @@ .sx-page--active:has(> .sx-timebar):has(> .sx-alliance-checkpoint-slot) { grid-template-rows: 80px min-content min-content minmax(0, 1fr) 116px; } - .sx-page--active.sx-alliance-gated { + .sx-page--active.sx-alliance-gated, + .sx-page--active.sx-alliance-gated:has(> .sx-alliance-checkpoint-slot) { grid-template-rows: 80px minmax(0, 1fr); } + .sx-page--active.sx-alliance-gated:has(> .sx-timebar):has(> .sx-alliance-checkpoint-slot) { + grid-template-rows: 80px min-content minmax(0, 1fr); + } } @media (min-width: 1280px) and (max-height: 820px) { @@ -4834,15 +4849,21 @@ .sx-page--active:has(> .sx-timebar):has(> .sx-alliance-checkpoint-slot) { grid-template-rows: 52px min-content min-content minmax(0, 1fr) 82px; } - .sx-page--active.sx-alliance-gated { + .sx-page--active.sx-alliance-gated, + .sx-page--active.sx-alliance-gated:has(> .sx-alliance-checkpoint-slot) { grid-template-rows: 52px minmax(0, 1fr); } + .sx-page--active.sx-alliance-gated:has(> .sx-timebar):has(> .sx-alliance-checkpoint-slot) { + grid-template-rows: 52px min-content minmax(0, 1fr); + } } /* 320×568 같은 저높이 폰에서는 mid 자기점검의 설명/버튼 줄바꿈만으로 축어록 행이 사라진다. G1 상태는 그대로 두고 collapsed 표현만 한 줄로 압축한다. */ @media (max-width: 420px) and (max-height: 620px) { - .sx-page--active:has(> .sx-alliance-checkpoint-slot) { + /* 40px 고정 행은 접힌 mid 스트립 전용이다. pre 게이트에서는 이 카드가 + 화면의 전부라, 같은 규칙이 걸리면 카드가 40px로 눌려 헤더와 축이 겹쳤다. */ + .sx-page--active:not(.sx-alliance-gated):has(> .sx-alliance-checkpoint-slot) { grid-template-rows: min-content 40px minmax(0, 1fr) min-content; } .sx-page--active > .sx-alliance-checkpoint.is-collapsed { @@ -4945,7 +4966,10 @@ background-position: -132px 33%, -148px 88%; transform: scaleX(-1); } -.sx-page--active > * { +/* 보태니컬 ::before/::after 위로 본문을 올리는 규칙이다. 다만 종료 모달은 + 같은 직계 자식이라 이 규칙이 position:fixed를 relative로 덮어썼고, 오버레이가 + 화면을 덮는 대신 그리드 한 칸(1532×32px)으로 눌려 들어갔다. 오버레이는 제외한다. */ +.sx-page--active > *:not(.sx-end-dialog) { position: relative; z-index: 1; } diff --git a/docs/dev_dashboard.html b/docs/dev_dashboard.html index 3bfd204..eee946a 100644 --- a/docs/dev_dashboard.html +++ b/docs/dev_dashboard.html @@ -635,6 +635,7 @@

180차 적용(2026-08-07): current source release-only SHA 6030a677af7e…c611를 격리 NAS vignette-preview-20260807에 승격했다. 최초 전체 gate는 108개 중 5개를 fixture drift 2종과 runtime-critical shell.css 분류 누락으로 차단해 원격을 바꾸지 않았고, G5→G4 무호출·StrictMode retry·learner 모바일 4열 packaging을 focused 6/6과 manifest 회귀로 고정했다. 최종 candidate browser E2E 108/108, postdeploy 실제 SSE→DB review 32.437s, health 3/3, OpenAPI 126·auth 401·G0~G8 route를 통과했다. API/Web 이미지는 52e0…8b2d/6fdb…f215이며 named DB volume과 배포 전 dump를 보존했다. 이 승격은 current runtime 증거이고 G8 receipt-bound 실제 image rollback은 별도 GATE다.

181차 적용(2026-08-07): G8의 마지막 게이트인 실제 receipt-bound image rollback을 격리 NAS에서 실행해 DONE으로 승격했다. 먼저 성립 불가능한 Gate6 계약을 정정했다. 감사 대상 current API 이미지 자체가 com.docker.compose.project=vignette-preview-20260807·service=api image label을 갖고 있어 "helper의 compose label 0개"는 감사되지 않은 다른 이미지를 쓰지 않는 한 성립하지 않는다. 그래서 계약을 key 부재가 아니라 소속으로 바꿔 scripts/launch-nas-preview-g8-helpers.py에 고정했다. image 상속 label을 baseline으로 읽고, container의 모든 compose label이 baseline과 같거나 선언된 격리 override인지 검사하며, 최종 project는 vignette-g8-helper-*·service는 g8-rollback-*이어야 하고, docker run argv에 target project/service label을 주입하면 fake-runner 테스트가 먼저 깨진다(37/37). 실행은 executor 127.0.0.1:18149(host network·read-only rootfs·cap-drop ALL·no-new-privileges·uid1028/gid100·group101)와 별도 lifecycle의 control-plane API 127.0.0.1:8018로 구성했다. 계획했던 Windows SSH 터널은 NAS sshd가 administratively prohibited로 direct-tcpip를 거부해 사용할 수 없었고, sshd 설정을 바꾸는 대신 같은 격리 계약의 NAS-side probe 컨테이너에서 실행해 control-plane(8018)·preview(8088) origin 분리를 그대로 유지했다. 결과는 rollback-old receipt nas-g8-723eeef2…, restore-current receipt nas-g8-2738846c…로 실제 이미지가 52e0/6fdb79ec/c53052e0/6fdb로 왕복했고, release gate·approval 2회 멱등, DB audit.ci_lifecycle_event rollback/executed 2건과 receipt id 결속, silent auto-promotion 0, HMAC journal 6-record 체인 검증, health 3/3·OpenAPI 126·auth 401·Web 200, helper 0·listener 0 복귀와 비밀 env 파기를 확인했다. down·volume rm·prune은 실행하지 않았고 공개 런타임은 건드리지 않았다. G7은 Deepgram live·인증 WSS·명시 동의 물리 마이크 50분·독립 라벨 human pack이 남아 외부 GATE로 유지한다.

182차 적용(2026-08-08): 회기 전 자기점검(pre alliance gate) 화면을 데스크톱·모바일 전 폭에서 다시 짰다. 계측이 먼저였다. gate 상태에서 카드가 align-self:stretch + grid-template-rows: auto minmax(0,1fr) auto auto인데 base flex의 align-items:center가 살아 있어 3축 블록이 1fr 행 한가운데 떠 있었다. 1920×1080 기준 카드의 66.5%가 빈 공간이고 축 위·아래로 각각 277px의 죽은 여백이 생겼다. 척도 라벨은 0.54rem+nowrap+ellipsis라 901~1279px에서 최대 12개가 "전허 아…"로 잘렸고, ≤620px에서는 display:none으로 15개 전부 사라져 숫자 1~5만 남았다. 320×568에서는 @media (max-width:420px) and (max-height:620px)의 collapsed 전용 40px 고정 행이 gate에도 걸려 카드가 40px로 눌리고 헤더와 축이 겹쳐 화면이 사실상 깨져 있었다. 수정은 네 가지다. (1) gate 카드는 행 높이만큼 늘리지 않고 align-self:center+max-height:100%+내부 스크롤로 화면 중앙에 서게 했다. (2) 3열 격자를 버리고 축마다 질문 한 줄 + 전폭 5칸 척도 세로 스택으로 바꿔 어느 폭에서도 라벨을 자르거나 숨기지 않는다(word-break:keep-all+overflow-wrap:normal로 어절 경계만 줄바꿈). (3) 정보가 없던 3 AXES 모노 배지를 제출 버튼이 왜 잠겨 있는지 답하는 0/3 축 선택함 진행 표시(aria-live)로 교체했다. (4) 40px 행 규칙에 :not(.sx-alliance-gated)를 걸어 collapsed mid 전용으로 되돌렸다. 선택 상태 글자색도 반투명 --surface에서 --text-on-accent로 고쳤다(라이트 4.85:1 · 다크 6.36:1, AA). 15개 뷰포트 재계측 결과 빈 공간 비율 0.665→0.127(1920), 라벨 잘림 12→0, 숨김 15→0, 최소 탭 타깃 38×38→46×60, 문서 가로 오버플로 전 구간 0이고, 320×568에서는 카드 내부 스크롤로 제출까지 도달해 실제 잠금·발화 입력 활성까지 확인했다. 인라인 mid 카드는 ≥1080px에서만 3열로 눕혀 축어록을 밀지 않게 두었고 높이는 오히려 약 55px 줄었다. 무회귀: typecheck 통과, session-layout 8/8, alliance-pulse 8/8, pre/mid 잠금 E2E 2/2, layout-visual-gate 22/23(실패 1건 settings는 변경 전 baseline에서도 동일 실패로 확인된 기존 회귀). session-mvp 음성 4건 실패도 baseline 동일이라 이 변경과 무관하다.

+

183차 적용(2026-08-08): 182차 뒤 소유자 스크린샷에서 시간 만료 상태의 게이트 화면이 여전히 깨져 있어 네 개의 별개 결함을 계측으로 특정하고 닫았다. (1) .sx-timebar는 세션이 다크 stage 팔레트를 강제하던 시절의 잔재로, 배경이 어디에도 정의된 적 없는 --bg-panel의 하드코딩 다크 폴백으로 칠해지는데 글자만 테마 토큰을 따라가 라이트에서 제목 1.13:1·설명 1.76:1이었다. 의미색 토큰(--warn-*/--crit-*)으로 되돌려 13.88:1·6.94:1(다크 13.86·9.30)로 올렸다. (2) 같은 이유로 .sx-end-dialog__panel이 어두운 gradient 위 어두운 글자라 제목 2.68:1·본문 1.14:1로 사실상 보이지 않았다. 불투명 --bg-app 표면으로 바꿔 15.79:1·5.19:1(다크 15.18·6.52)이 됐다. (3) 보태니컬 장식 위로 본문을 올리는 .sx-page--active > *{position:relative}가 같은 특이도(0,1,0)로 뒤에 있어 종료 모달의 position:fixed를 덮어썼다. 오버레이가 화면을 덮는 대신 그리드 한 칸(1532×32px)으로 눌려 들어갔다. :not(.sx-end-dialog)로 제외해 fixed 1600×900 뷰포트 전체를 회복했다. (4) 게이트 행 정의 .sx-page--active.sx-alliance-gated(0,2,0)가 :has() 두 개짜리 규칙(0,3,0)보다 특이도가 낮아 시간 만료 바가 뜨는 순간 밀려났다. 숨긴 축어록·컨트롤바의 빈 트랙이 남아 화면 절반이 비었다(80px 59px 628px 0px 116px 5행). 게이트 규칙에도 같은 :has()를 붙여 3행(80px 59px 693px)으로 정정했다. 무회귀: typecheck 통과, session-layout+alliance-pulse+layout-visual-gate 31/31(182차에서 실패했던 settings도 통과), 시간만료 종료 다이얼로그 E2E 2/2, pre/mid 잠금 E2E 2/2, full-sweep-session+self-directed-learning-loop 30 pass. 남은 음성 4건(voice skip·Space mic)은 변경분 stash 후 baseline에서도 동일 실패라 이 변경과 무관한 기존 회귀다.

래스터만 사용이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.
기능 우선메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.
diff --git a/docs/ops/evidence/g1-alliance-mid-chromium-desktop-2026-08-07.png b/docs/ops/evidence/g1-alliance-mid-chromium-desktop-2026-08-07.png index 375d640..00aee00 100644 Binary files a/docs/ops/evidence/g1-alliance-mid-chromium-desktop-2026-08-07.png and b/docs/ops/evidence/g1-alliance-mid-chromium-desktop-2026-08-07.png differ diff --git a/docs/ops/evidence/g1-alliance-mid-chromium-mobile-2026-08-07.png b/docs/ops/evidence/g1-alliance-mid-chromium-mobile-2026-08-07.png index 65c291c..c42a2fb 100644 Binary files a/docs/ops/evidence/g1-alliance-mid-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/g1-alliance-mid-chromium-mobile-2026-08-07.png differ diff --git a/docs/ops/evidence/g1-alliance-pre-chromium-desktop-2026-08-07.png b/docs/ops/evidence/g1-alliance-pre-chromium-desktop-2026-08-07.png index 5697808..fe99812 100644 Binary files a/docs/ops/evidence/g1-alliance-pre-chromium-desktop-2026-08-07.png and b/docs/ops/evidence/g1-alliance-pre-chromium-desktop-2026-08-07.png differ diff --git a/docs/ops/evidence/g1-alliance-pre-chromium-mobile-2026-08-07.png b/docs/ops/evidence/g1-alliance-pre-chromium-mobile-2026-08-07.png index 8718ba6..172da19 100644 Binary files a/docs/ops/evidence/g1-alliance-pre-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/g1-alliance-pre-chromium-mobile-2026-08-07.png differ diff --git a/docs/ops/evidence/self-directed-loop-active-session-320x568-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-active-session-320x568-chromium-mobile-2026-08-07.png index e8518b9..0a3dd20 100644 Binary files a/docs/ops/evidence/self-directed-loop-active-session-320x568-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-active-session-320x568-chromium-mobile-2026-08-07.png differ diff --git a/docs/ops/evidence/self-directed-loop-active-session-chromium-desktop-2026-08-07.png b/docs/ops/evidence/self-directed-loop-active-session-chromium-desktop-2026-08-07.png index a8d6904..b9827ff 100644 Binary files a/docs/ops/evidence/self-directed-loop-active-session-chromium-desktop-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-active-session-chromium-desktop-2026-08-07.png differ diff --git a/docs/ops/evidence/self-directed-loop-active-session-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-active-session-chromium-mobile-2026-08-07.png index 887bbfe..b6703de 100644 Binary files a/docs/ops/evidence/self-directed-loop-active-session-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-active-session-chromium-mobile-2026-08-07.png differ diff --git a/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-mobile-2026-08-07.png index 1385f2f..c2c6831 100644 Binary files a/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-mobile-2026-08-07.png differ