평가 및 화면 구조 정리

This commit is contained in:
Yun Chan 2026-06-28 21:46:22 +09:00
parent 1248ae8ca4
commit 391639c1de
44 changed files with 5816 additions and 4501 deletions

View file

@ -376,6 +376,10 @@ test.describe("layout visual gate @single-run", () => {
await gateScreen(page, "persona-studio", async () => {
await expect(page.locator(".ps-layout")).toBeVisible({ timeout: 15_000 });
await expect(page.getByText("내담자 설계·검수 작업면")).toBeVisible();
await page.getByRole("tab", { name: "프롬프트" }).click();
const promptPreview = page.getByLabel("프롬프트 미리보기");
await expect(promptPreview).toBeVisible();
await expect(promptPreview.getByRole("textbox")).toHaveCount(0);
});
});

View file

@ -329,6 +329,108 @@ test.describe("teacher console", () => {
await expectNoHorizontalOverflow(page);
});
test("saves persona studio list rows as structured arrays @single-run", async ({ page }) => {
type PersonaDraftSavePayload = {
ccd: { automatic_thought: string[] };
code: string;
difficulty: string;
display_name: string;
is_synthetic: boolean;
source_provenance: unknown;
theory_target: string;
triggers: { forbidden: string[]; sore_spots: string[] };
};
let savedPayload: PersonaDraftSavePayload | null = null;
await signInAsTeacher(page);
await page.route("**/api/personas", (route) => {
if (route.request().method() !== "GET") return route.fallback();
return route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]) });
});
await page.route("**/api/personas/review", (route) => {
if (route.request().method() !== "GET") return route.fallback();
return route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]) });
});
await page.route("**/api/personas/drafts", async (route) => {
if (route.request().method() !== "POST") return route.fallback();
savedPayload = route.request().postDataJSON() as PersonaDraftSavePayload;
await route.fulfill({
status: 201,
contentType: "application/json",
body: JSON.stringify({
persona_id: "00000000-0000-0000-0000-000000000703",
code: savedPayload.code,
version: 1,
status: "draft",
display_name: savedPayload.display_name,
difficulty: savedPayload.difficulty,
theory_target: savedPayload.theory_target,
source_provenance: savedPayload.source_provenance,
is_synthetic: savedPayload.is_synthetic,
created_at: "2026-06-28T00:00:00Z",
approved_at: null,
}),
});
});
await page.goto("/teach/personas");
await page.getByLabel("표시 이름").fill("항목형 페르소나");
await page.getByRole("tab", { name: "임상" }).click();
const automaticThoughts = page.locator(".ps-list-field").filter({ hasText: "자동사고" });
await automaticThoughts.getByRole("textbox", { name: "자동사고 1", exact: true }).fill("삭제될 자동사고");
await automaticThoughts.getByRole("button", { name: "항목 추가" }).click();
await automaticThoughts.getByRole("textbox", { name: "자동사고 2", exact: true }).fill("남길 자동사고");
await automaticThoughts.getByRole("button", { name: "자동사고 1 삭제" }).click();
await page.getByRole("tab", { name: "안전" }).click();
const forbidden = page.locator(".ps-list-field").filter({ hasText: "상담자 금기" });
await forbidden.getByRole("textbox", { name: "상담자 금기 1", exact: true }).fill("네가 예민한 거라고 단정");
await page.getByRole("button", { name: "초안 저장" }).click();
await expect(page.getByText("P1 v1 초안을 저장했습니다.")).toBeVisible();
expect(savedPayload).toBeTruthy();
expect(savedPayload?.ccd.automatic_thought).toEqual(["남길 자동사고"]);
expect(savedPayload?.triggers.forbidden).toEqual(["네가 예민한 거라고 단정"]);
expect(savedPayload?.triggers.sore_spots).toEqual([]);
await expectNoHorizontalOverflow(page);
});
test("renders persona prompt preview as labeled sections without raw JSON @single-run", async ({ page }) => {
await signInAsTeacher(page);
await page.route("**/api/personas", (route) => {
if (route.request().method() !== "GET") return route.fallback();
return route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]) });
});
await page.route("**/api/personas/review", (route) => {
if (route.request().method() !== "GET") return route.fallback();
return route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]) });
});
await page.goto("/teach/personas");
await page.getByLabel("표시 이름").fill("프롬프트 검토 페르소나");
await page.getByRole("tab", { name: "임상" }).click();
const automaticThoughts = page.locator(".ps-list-field").filter({ hasText: "자동사고" });
await automaticThoughts.getByRole("textbox", { name: "자동사고 1", exact: true }).fill("말하면 더 이상하게 볼 거야");
await page.getByRole("tab", { name: "안전" }).click();
const forbidden = page.locator(".ps-list-field").filter({ hasText: "상담자 금기" });
await forbidden.getByRole("textbox", { name: "상담자 금기 1", exact: true }).fill("네가 예민한 거라고 단정");
await page.getByRole("tab", { name: "프롬프트" }).click();
const promptPreview = page.getByLabel("프롬프트 미리보기");
await expect(promptPreview.getByRole("heading", { name: /L1 페르소나 카드/ })).toBeVisible();
await expect(promptPreview.getByText("자동사고")).toBeVisible();
await expect(promptPreview.getByText("말하면 더 이상하게 볼 거야")).toBeVisible();
await expect(promptPreview.getByText("상담자 금기")).toBeVisible();
await expect(promptPreview.getByText("네가 예민한 거라고 단정")).toBeVisible();
await expect(promptPreview.getByRole("textbox")).toHaveCount(0);
await expect(promptPreview).not.toContainText('"automatic_thought"');
await expect(promptPreview).not.toContainText('"forbidden"');
await expect(promptPreview).not.toContainText("{");
await expectNoHorizontalOverflow(page);
});
test("archives an approved persona from persona studio without layout drift @single-run", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
const personaId = "00000000-0000-0000-0000-000000000702";

View file

@ -10,6 +10,8 @@
/>
<meta name="robots" content="index,follow,max-snippet:160,max-image-preview:large" />
<link rel="canonical" href="https://vignette.chanpaca.net/" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.svg" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="ko_KR" />
<meta property="og:site_name" content="Vignette" />

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Vignette">
<rect width="64" height="64" rx="14" fill="#f6f1e8"/>
<path d="M14 14h36L35.7 50H27L14 14Z" fill="#2f6f73"/>
<path d="M24.2 20h14.9l-7.2 19.1L24.2 20Z" fill="#c46d4a"/>
</svg>

After

Width:  |  Height:  |  Size: 276 B

View file

@ -36,6 +36,7 @@ import { Mouth } from "./Mouth";
import { live2dModel3Path, live2dModelForPersonaCode, live2dMotionForExpression } from "./live2dModel";
import { useExpressionTransition } from "./useExpressionTransition";
import { RasterBust } from "./RasterBust";
import "./client-avatar.css";
/* ( import )
Session.tsx ClientAvatar . */
@ -82,7 +83,6 @@ function prefersReducedMotion(): boolean {
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true
);
}
/** prefers-reduced-motion 을 반응형으로 구독 */
function useReducedMotion(): boolean {
const [reduced, setReduced] = useState<boolean>(prefersReducedMotion);
@ -299,7 +299,6 @@ export function ClientAvatar({
data-realism={realism} /* 사실성은 데이터로만 유지(시각 표식 비노출, §4.6) */
aria-label={`교육용 가상 내담자: ${persona.label}, ${STATE_TEXT[state]}, ${expressionLabel}`}
>
<style>{AVATAR_CSS}</style>
{/* 상단 라벨 — 실존 인물 오인 차단(상시, §4.1) */}
{showCaption ? (
@ -399,60 +398,3 @@ export function ClientAvatar({
</figure>
);
}
const AVATAR_CSS = `
.vg-avatar{display:flex;flex-direction:column;align-items:center;gap:var(--sp-3);margin:0;}
.vg-avatar__label{
display:inline-flex;align-items:center;gap:7px;
font-family:var(--font-num);font-size:var(--fs-xs);font-weight:600;letter-spacing:0.06em;
color:var(--text-muted);text-transform:none;
}
.vg-avatar__label-dot{width:6px;height:6px;border-radius:50%;background:var(--clay);flex:none;}
.vg-avatar__stage{
position:relative;width:100%;border-radius:50%;
display:flex;align-items:center;justify-content:center;overflow:hidden;
background:
radial-gradient(circle at 50% 38%, rgba(251,250,248,.98) 0%, rgba(246,240,236,.9) 42%, rgba(238,244,242,.76) 64%, rgba(145,200,189,.22) 84%, rgba(30,39,36,.18) 100%);
box-shadow:
inset 0 0 0 1px rgba(251,250,248,.28),
inset 0 -24px 40px rgba(28,42,42,.13);
}
.vg-avatar__aura{
position:absolute;inset:-12%;border-radius:50%;pointer-events:none;
animation:vgAuraBreathe 6s var(--ease-in-out) infinite;
}
.vg-avatar__aura.is-reduced{animation:none;}
@keyframes vgAuraBreathe{0%,100%{opacity:.85;transform:scale(1)}50%{opacity:1;transform:scale(1.04)}}
.vg-avatar__svg{position:relative;z-index:1;display:block;transition:opacity var(--dur-base) var(--ease-out);}
/* ── 래스터(Live2D식) 렌더: 레이어 합성 흉상 ── */
.vg-raster{position:absolute;inset:0;z-index:1;pointer-events:none;will-change:transform;}
.vg-raster__layer{
position:absolute;left:50%;top:var(--vg-raster-top,-15%);
height:var(--vg-raster-h,136%);width:auto;max-width:none;
transform:translateX(-50%);object-fit:contain;
user-select:none;-webkit-user-drag:none;opacity:0;
}
.vg-raster__layer--shoulders{opacity:1;z-index:1;}
.vg-raster__layer--neck{opacity:1;z-index:2;}
.vg-raster__layer--hairback{opacity:1;z-index:3;}
.vg-raster__layer--ear{opacity:1;z-index:4;}
.vg-raster__layer--face{opacity:1;z-index:5;}
.vg-raster__layer--forehead{opacity:1;z-index:6;}
.vg-raster__layer--hair{opacity:1;z-index:7;will-change:transform;}
.vg-raster__layer--bangs{opacity:1;z-index:8;will-change:transform;}
.vg-raster__layer--brow{z-index:9;will-change:opacity,transform;}
.vg-raster__layer--eyes{z-index:10;}
.vg-raster__layer--eyelid{z-index:11;}
.vg-raster__layer--nose{opacity:1;z-index:12;}
.vg-raster__layer--mouth{z-index:13;}
.vg-raster__layer--mouthopen{z-index:14;}
.vg-raster__layer--base{opacity:1;z-index:1;}
.vg-raster__layer--upperface{z-index:2;}
.vg-avatar__meta{display:flex;flex-direction:column;align-items:center;gap:3px;text-align:center;}
.vg-avatar__persona{font-size:var(--fs-sm);font-weight:600;color:var(--text-strong);}
.vg-avatar__state{font-size:var(--fs-xs);color:var(--text-muted);}
.vg-avatar__state b{font-weight:600;color:var(--text-body);}
@media (prefers-reduced-motion: reduce){
.vg-avatar__aura{animation:none;}
}
`;

View file

@ -0,0 +1,54 @@
.vg-avatar{display:flex;flex-direction:column;align-items:center;gap:var(--sp-3);margin:0;}
.vg-avatar__label{
display:inline-flex;align-items:center;gap:7px;
font-family:var(--font-num);font-size:var(--fs-xs);font-weight:600;letter-spacing:0.06em;
color:var(--text-muted);text-transform:none;
}
.vg-avatar__label-dot{width:6px;height:6px;border-radius:50%;background:var(--clay);flex:none;}
.vg-avatar__stage{
position:relative;width:100%;border-radius:50%;
display:flex;align-items:center;justify-content:center;overflow:hidden;
background:
radial-gradient(circle at 50% 38%, rgba(251,250,248,.98) 0%, rgba(246,240,236,.9) 42%, rgba(238,244,242,.76) 64%, rgba(145,200,189,.22) 84%, rgba(30,39,36,.18) 100%);
box-shadow:
inset 0 0 0 1px rgba(251,250,248,.28),
inset 0 -24px 40px rgba(28,42,42,.13);
}
.vg-avatar__aura{
position:absolute;inset:-12%;border-radius:50%;pointer-events:none;
animation:vgAuraBreathe 6s var(--ease-in-out) infinite;
}
.vg-avatar__aura.is-reduced{animation:none;}
@keyframes vgAuraBreathe{0%,100%{opacity:.85;transform:scale(1)}50%{opacity:1;transform:scale(1.04)}}
.vg-avatar__svg{position:relative;z-index:1;display:block;transition:opacity var(--dur-base) var(--ease-out);}
/* ── 래스터(Live2D식) 렌더: 레이어 합성 흉상 ── */
.vg-raster{position:absolute;inset:0;z-index:1;pointer-events:none;will-change:transform;}
.vg-raster__layer{
position:absolute;left:50%;top:var(--vg-raster-top,-15%);
height:var(--vg-raster-h,136%);width:auto;max-width:none;
transform:translateX(-50%);object-fit:contain;
user-select:none;-webkit-user-drag:none;opacity:0;
}
.vg-raster__layer--shoulders{opacity:1;z-index:1;}
.vg-raster__layer--neck{opacity:1;z-index:2;}
.vg-raster__layer--hairback{opacity:1;z-index:3;}
.vg-raster__layer--ear{opacity:1;z-index:4;}
.vg-raster__layer--face{opacity:1;z-index:5;}
.vg-raster__layer--forehead{opacity:1;z-index:6;}
.vg-raster__layer--hair{opacity:1;z-index:7;will-change:transform;}
.vg-raster__layer--bangs{opacity:1;z-index:8;will-change:transform;}
.vg-raster__layer--brow{z-index:9;will-change:opacity,transform;}
.vg-raster__layer--eyes{z-index:10;}
.vg-raster__layer--eyelid{z-index:11;}
.vg-raster__layer--nose{opacity:1;z-index:12;}
.vg-raster__layer--mouth{z-index:13;}
.vg-raster__layer--mouthopen{z-index:14;}
.vg-raster__layer--base{opacity:1;z-index:1;}
.vg-raster__layer--upperface{z-index:2;}
.vg-avatar__meta{display:flex;flex-direction:column;align-items:center;gap:3px;text-align:center;}
.vg-avatar__persona{font-size:var(--fs-sm);font-weight:600;color:var(--text-strong);}
.vg-avatar__state{font-size:var(--fs-xs);color:var(--text-muted);}
.vg-avatar__state b{font-weight:600;color:var(--text-body);}
@media (prefers-reduced-motion: reduce){
.vg-avatar__aura{animation:none;}
}

View file

@ -18,6 +18,7 @@ export type IconName =
| "chevron-right"
| "chevron-left"
| "check"
| "plus"
| "share"
| "mic"
| "mic-off"
@ -94,6 +95,12 @@ const PATHS: Record<IconName, ReactNode> = {
"chevron-right": <polyline points="9 6 15 12 9 18" />,
"chevron-left": <polyline points="15 6 9 12 15 18" />,
check: <polyline points="20 6 9 17 4 12" />,
plus: (
<>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</>
),
share: (
<>
<circle cx="18" cy="5" r="3" />

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,10 @@
import { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { Icon } from "../components/ui/Icon";
import { roleHomePath, useAuth, type Role } from "../lib/auth";
import { apiUrl, authApi, type AuthConfigResponse } from "../lib/api";
import { LoginBrand } from "./login/LoginBrand";
import { LoginPanel, type LoginRoleOption } from "./login/LoginPanel";
import "./login/login.css";
const OAUTH_NOT_CONFIGURED_MESSAGE =
"Google 로그인이 아직 연결되지 않았습니다. 관리자에게 OAuth 클라이언트 설정을 요청하세요.";
@ -57,7 +59,7 @@ function oauthMessage(reason: string | null): string | null {
return OAUTH_FAILED_MESSAGE;
}
const ROLE_OPTIONS: { role: Role; label: string; desc: string; dotClass: string }[] = [
const ROLE_OPTIONS: LoginRoleOption[] = [
{ role: "learner", label: "학습자", desc: "연습 공간으로 이동", dotClass: "learner" },
{ role: "teacher", label: "교수자", desc: "담당 학습자 관리", dotClass: "teacher" },
{ role: "admin", label: "관리자", desc: "운영 설정과 감사", dotClass: "admin" },
@ -143,6 +145,11 @@ export default function Login() {
: allowedDomains[0]
? "승인된 Google 계정"
: "관리자 설정 필요";
const oauthStatusMessage = devOAuthUnavailable
? LOCAL_OAUTH_UNAVAILABLE_MESSAGE
: oauthChecking
? "Google 로그인 설정을 확인하는 중입니다."
: "Google 로그인이 아직 연결되지 않았습니다. 관리자에게 OAuth 클라이언트 설정을 요청하세요.";
const startOAuth = () => {
if (!oauthReady) {
@ -182,485 +189,28 @@ export default function Login() {
return (
<main className="lg-root">
<style>{LOGIN_CSS}</style>
<section className="lg-brand" aria-label="Vignette">
<div className="lg-wordmark">
<span className="lg-mark" aria-hidden="true">
<svg viewBox="0 0 26 26" width={26} height={26} fill="none">
<circle cx="13" cy="13" r="11" stroke="currentColor" strokeWidth="2" />
<path
d="M8 14.5c1.4 1.7 3 2.5 5 2.5s3.6-.8 5-2.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
<circle cx="13" cy="9" r="1.6" fill="var(--clay)" />
</svg>
</span>
<span>Vignette</span>
</div>
<div className="lg-copy">
<span className="lg-kicker">
<span className="d" aria-hidden="true" />
</span>
<h1><span className="lg-highlight"> </span>, <br/> .</h1>
<p>
Vignette는 , , .
.
</p>
</div>
<div className="lg-policy">
<span>
<Icon name="shield" size={17} />
</span>
{allowedDomains.length ? (
allowedDomains.map((domain) => <b key={domain}>{domain}</b>)
) : (
<b>{oauthChecking ? "확인 중" : "설정 필요"}</b>
)}
</div>
</section>
<section className="lg-enter" aria-label="로그인">
<div className="lg-panel">
<span className="lg-kicker">
<span className="d" aria-hidden="true" />
</span>
<h2></h2>
<p className="lg-lead">
Google .
</p>
<div className="lg-actions">
<button
className="lg-obtn primary"
type="button"
onClick={startOAuth}
disabled={!oauthReady}
>
<span className="ic">
<Icon name="school" size={19} strokeWidth={1.8} />
</span>
<span className="txt">
Google
<span className="sub">{primaryDomainLabel}</span>
</span>
<Icon name="chevron-right" size={18} strokeWidth={2} />
</button>
<button
className="lg-obtn secondary"
type="button"
onClick={startOAuth}
disabled={!oauthReady}
>
<span className="ic">
<Icon name="google" size={19} />
</span>
<span className="txt">
Google
<span className="sub">{secondaryDomainLabel}</span>
</span>
<Icon name="chevron-right" size={18} strokeWidth={2} />
</button>
</div>
{!oauthReady ? (
<div className="lg-config" role="status">
<Icon name={authConfigError ? "alert" : "info"} size={17} />
<span>
{devOAuthUnavailable
? LOCAL_OAUTH_UNAVAILABLE_MESSAGE
: oauthChecking
? "Google 로그인 설정을 확인하는 중입니다."
: "Google 로그인이 아직 연결되지 않았습니다. 관리자에게 OAuth 클라이언트 설정을 요청하세요."}
</span>
</div>
) : null}
{devLoginReady ? (
<div className="lg-dev">
<div className="lg-devhead">
<span> </span>
<small> </small>
</div>
<div className="lg-rolepick" role="radiogroup" aria-label="로컬 테스트 역할">
{ROLE_OPTIONS.map((opt) => (
<button
key={opt.role}
type="button"
role="radio"
aria-checked={selected === opt.role}
className={`lg-roleopt ${selected === opt.role ? "is-sel" : ""}`}
onClick={() => setSelected(opt.role)}
>
<span className={`d ${opt.dotClass}`} aria-hidden="true" />
<span>
<b>{opt.label}</b>
<small>{opt.desc}</small>
</span>
{selected === opt.role ? <Icon name="check" size={15} /> : null}
</button>
))}
</div>
<button
className="lg-devbtn"
type="button"
onClick={() => void enterDev(selected)}
disabled={pending}
>
{pending ? "테스트 계정 확인 중" : "로컬 테스트 계정으로 계속"}
</button>
{loginError ? (
<p className="lg-error">
{loginError}
{loginErrorReason ? <small> : {loginErrorReason}</small> : null}
</p>
) : null}
</div>
) : null}
{!devLoginReady && loginError ? (
<p className="lg-error">
{loginError}
{loginErrorReason ? <small> : {loginErrorReason}</small> : null}
</p>
) : null}
<p className="lg-note">
. , , .
</p>
</div>
</section>
<LoginBrand allowedDomains={allowedDomains} oauthChecking={oauthChecking} />
<LoginPanel
oauth={{
ready: oauthReady,
primaryDomainLabel,
secondaryDomainLabel,
statusIcon: authConfigError ? "alert" : "info",
statusMessage: oauthStatusMessage,
onStart: startOAuth,
}}
devAccess={{
ready: devLoginReady,
selected,
pending,
options: ROLE_OPTIONS,
onSelect: setSelected,
onEnter: (role) => {
void enterDev(role);
},
}}
error={{ message: loginError, reason: loginErrorReason }}
/>
</main>
);
}
const LOGIN_CSS = `
.lg-root{
min-height:100dvh;
position:relative;
display:grid;
grid-template-columns:minmax(0,1fr) minmax(360px,500px);
background:
linear-gradient(90deg,rgba(7,16,14,.84) 0%,rgba(10,21,19,.72) 48%,rgba(10,21,19,.55) 74%,rgba(10,21,19,.62) 100%),
var(--asset-login-room) center / cover no-repeat;
color:#edf4f2;
overflow-x:hidden;
overflow-y:auto;
}
.lg-root::before{
content:"";
position:absolute;
inset:0;
background:linear-gradient(180deg,rgba(3,9,8,.18),rgba(3,9,8,.42));
pointer-events:none;
}
.lg-brand,
.lg-enter{
position:relative;
z-index:1;
}
.lg-brand{
min-width:0;
width:100%;
position:relative;
display:flex;
flex-direction:column;
justify-content:space-between;
gap:var(--sp-7);
padding:var(--sp-7);
background:transparent;
color:#edf4f2;
overflow:hidden;
}
.lg-brand::after{
display:none;
}
.lg-wordmark{
position:relative;
z-index:1;
display:flex;
align-items:center;
gap:10px;
font-size:19px;
font-weight:700;
letter-spacing:0;
color:#edf4f2;
}
.lg-mark{display:grid;place-items:center;color:var(--accent-bright);}
.lg-copy{max-width:620px;}
.lg-copy,
.lg-policy{
position:relative;
z-index:1;
}
.lg-kicker{
display:inline-flex;
align-items:center;
gap:8px;
font-family:var(--font-num);
font-size:12px;
font-weight:700;
letter-spacing:0;
text-transform:uppercase;
color:var(--accent-bright);
}
.lg-kicker .d{width:6px;height:6px;border-radius:50%;background:currentColor;}
.lg-highlight{
display:inline-block;
position:relative;
z-index:0;
padding:0 4px;
background:linear-gradient(90deg,#59b5a6,#8ee4d6);
-webkit-background-clip:text;
background-clip:text;
-webkit-text-fill-color:transparent;
color:transparent;
}
.lg-copy h1{
margin:var(--sp-4) 0 0;
max-width:640px;
font-size:56px;
line-height:1.12;
letter-spacing:0;
font-weight:760;
}
.lg-copy p{
margin:var(--sp-5) 0 0;
max-width:560px;
color:rgba(237,244,242,.72);
font-size:17px;
line-height:1.75;
}
.lg-policy{
display:flex;
align-items:center;
flex-wrap:wrap;
gap:10px;
width:max-content;
max-width:min(430px,100%);
padding:14px 16px;
border:1px solid rgba(255,255,255,.14);
border-radius:var(--radius-lg);
background:rgba(237,244,242,.08);
backdrop-filter:blur(18px) saturate(1.08);
-webkit-backdrop-filter:blur(18px) saturate(1.08);
color:rgba(237,244,242,.66);
font-size:13px;
}
.lg-policy span,.lg-policy b{
display:inline-flex;
align-items:center;
gap:7px;
}
.lg-policy b{
color:#edf4f2;
background:rgba(255,255,255,.08);
border:1px solid rgba(255,255,255,.12);
border-radius:999px;
padding:5px 10px;
font-weight:650;
}
.lg-enter{
min-width:0;
display:flex;
align-items:center;
justify-content:center;
padding:var(--sp-6);
background:transparent;
}
.lg-panel{
width:100%;
max-width:430px;
background:rgba(11,25,22,.64);
border:1px solid rgba(255,255,255,.16);
border-radius:var(--radius-lg);
box-shadow:0 24px 70px rgba(0,0,0,.34), inset 0 1px 0 rgba(255,255,255,.08);
backdrop-filter:blur(24px) saturate(1.14);
-webkit-backdrop-filter:blur(24px) saturate(1.14);
padding:var(--sp-6);
color:#edf4f2;
}
.lg-panel h2{
margin:var(--sp-3) 0 0;
font-size:28px;
line-height:1.25;
letter-spacing:0;
color:#f7fbf9;
}
.lg-lead{
margin:10px 0 0;
color:rgba(237,244,242,.76);
font-size:14px;
line-height:1.65;
}
.lg-actions{display:flex;flex-direction:column;gap:var(--sp-3);margin-top:var(--sp-6);}
.lg-obtn{
width:100%;
min-height:58px;
display:grid;
grid-template-columns:36px minmax(0,1fr) 18px;
align-items:center;
gap:13px;
border-radius:var(--radius);
padding:11px 14px;
font-family:var(--font-sans);
font-size:15px;
font-weight:650;
text-align:left;
cursor:pointer;
}
.lg-obtn.primary{
background:rgba(89,181,166,.2);
border:1px solid rgba(114,211,197,.44);
color:#f7fbf9;
}
.lg-obtn.primary:hover{background:rgba(89,181,166,.28);border-color:rgba(114,211,197,.62);}
.lg-obtn.secondary{
background:rgba(255,255,255,.08);
border:1px solid rgba(255,255,255,.13);
color:#edf4f2;
}
.lg-obtn.secondary:hover{border-color:rgba(114,211,197,.42);background:rgba(255,255,255,.11);}
.lg-obtn:disabled{
cursor:not-allowed;
opacity:1;
background:rgba(255,255,255,.07);
border-color:rgba(255,255,255,.1);
color:rgba(237,244,242,.58);
}
.lg-obtn:disabled:hover{
background:rgba(255,255,255,.07);
border-color:rgba(255,255,255,.1);
}
.lg-obtn:disabled .ic{
background:rgba(255,255,255,.08);
color:rgba(237,244,242,.58);
}
.lg-obtn:disabled .sub{
color:rgba(237,244,242,.45);
}
.lg-obtn .ic{
width:36px;
height:36px;
display:grid;
place-items:center;
border-radius:8px;
background:rgba(255,255,255,.16);
}
.lg-obtn.secondary .ic{background:rgba(7,17,15,.34);}
.lg-obtn .txt{min-width:0;display:flex;flex-direction:column;gap:1px;}
.lg-obtn .sub{font-size:12px;font-weight:550;color:rgba(237,244,242,.58);}
.lg-obtn.primary .sub{color:rgba(251,250,248,.74);}
.lg-config{
display:flex;
align-items:flex-start;
gap:10px;
margin-top:var(--sp-3);
padding:10px 12px;
border-radius:var(--radius);
background:rgba(154,94,20,.28);
border:1px solid rgba(236,180,91,.16);
color:#f2b75f;
font-size:12.5px;
line-height:1.5;
}
.lg-dev{
margin-top:var(--sp-6);
padding-top:var(--sp-5);
border-top:1px solid rgba(255,255,255,.1);
}
.lg-devhead{
display:flex;
align-items:baseline;
justify-content:space-between;
gap:var(--sp-3);
color:#edf4f2;
font-size:13px;
font-weight:700;
}
.lg-devhead small{color:rgba(237,244,242,.58);font-weight:600;}
.lg-rolepick{display:grid;grid-template-columns:1fr;gap:var(--sp-2);margin-top:var(--sp-3);}
.lg-roleopt{
min-height:50px;
display:grid;
grid-template-columns:10px minmax(0,1fr) 16px;
align-items:center;
gap:10px;
padding:9px 11px;
border:1px solid rgba(255,255,255,.11);
border-radius:var(--radius);
background:rgba(255,255,255,.045);
color:#edf4f2;
text-align:left;
cursor:pointer;
}
.lg-roleopt.is-sel{border-color:rgba(114,211,197,.58);background:rgba(89,181,166,.15);}
.lg-roleopt .d{width:8px;height:8px;border-radius:50%;}
.lg-roleopt .d.learner{background:var(--accent-bright);}
.lg-roleopt .d.teacher{background:#5478c4;}
.lg-roleopt .d.admin{background:#7d818e;}
.lg-roleopt b{display:block;font-size:13px;}
.lg-roleopt small{display:block;margin-top:1px;color:rgba(237,244,242,.58);font-size:12px;}
.lg-devbtn{
width:100%;
min-height:46px;
margin-top:var(--sp-3);
border:1px solid rgba(114,211,197,.58);
border-radius:var(--radius);
background:rgba(89,181,166,.15);
color:#8fe7d9;
font-family:var(--font-sans);
font-size:14px;
font-weight:750;
cursor:pointer;
}
.lg-devbtn:disabled{opacity:.62;cursor:wait;}
.lg-error{
margin:var(--sp-3) 0 0;
color:var(--crit-text);
background:rgba(122,38,38,.28);
border:1px solid rgba(255,145,145,.18);
border-radius:var(--radius);
padding:10px 12px;
font-size:13px;
line-height:1.5;
}
.lg-error small{
display:block;
margin-top:4px;
color:rgba(237,244,242,.56);
font-family:var(--font-num);
font-size:11.5px;
overflow-wrap:anywhere;
}
.lg-note{
margin:var(--sp-5) 0 0;
color:rgba(237,244,242,.52);
font-size:12.5px;
line-height:1.6;
}
@media (max-width:880px){
.lg-root{grid-template-columns:1fr;}
.lg-root::before{display:none;}
.lg-brand{padding:var(--sp-6) var(--sp-5);gap:var(--sp-6);}
.lg-brand::after{display:none;}
.lg-copy h1{font-size:36px;}
.lg-enter{padding:var(--sp-5);}
.lg-panel{max-width:560px;}
}
@media (max-width:480px){
.lg-brand{padding:var(--sp-5);}
.lg-copy h1{font-size:30px;}
.lg-copy p{font-size:15px;}
.lg-enter{padding:var(--sp-4);}
.lg-panel{padding:var(--sp-5);}
}
`;

View file

@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
import { Button } from "../components/ui";
import { roleHomePath, useAuth } from "../lib/auth";
import { apiUrl, userApi, type LegalDocumentsResponse, type UserProfileResponse } from "../lib/api";
import "./onboarding.css";
interface OnboardingForm {
legal_name: string;
@ -146,7 +147,6 @@ export default function Onboarding() {
return (
<>
<style>{ONBOARDING_CSS}</style>
<main className="ob-page">
<section className="ob-shell" aria-label="가입 정보 입력">
<header className="ob-head">
@ -337,259 +337,3 @@ export default function Onboarding() {
</>
);
}
const ONBOARDING_CSS = `
.ob-page{
min-height:100dvh;
width:100%;
background:var(--bg-app);
color:var(--text-body);
padding:clamp(20px,5vw,56px);
}
.ob-shell{
width:100%;
max-width:900px;
margin:0 auto;
display:grid;
gap:var(--sp-6);
}
.ob-head{
display:grid;
gap:8px;
}
.ob-head p{
margin:0;
color:var(--accent-deep);
font-size:12px;
font-weight:800;
}
.ob-head h1{
margin:0;
color:var(--text-strong);
font-size:clamp(28px,4vw,44px);
line-height:1.18;
letter-spacing:0;
}
.ob-form{
min-width:0;
display:grid;
gap:var(--sp-6);
}
.ob-section{
min-width:0;
display:grid;
gap:var(--sp-4);
padding-bottom:var(--sp-5);
border-bottom:1px solid var(--border-subtle);
}
.ob-section__head h2{
margin:0;
color:var(--text-strong);
font-size:18px;
line-height:1.35;
letter-spacing:0;
}
.ob-avatar{
display:grid;
grid-template-columns:auto minmax(0,1fr);
gap:var(--sp-3);
align-items:center;
}
.ob-avatar__preview{
width:72px;
height:72px;
border-radius:50%;
display:grid;
place-items:center;
overflow:hidden;
background:var(--accent-tint);
color:var(--accent-deep);
font-size:28px;
font-weight:800;
border:1px solid var(--border-subtle);
}
.ob-avatar__preview img{
width:100%;
height:100%;
display:block;
object-fit:cover;
}
.ob-avatar__body{
min-width:0;
display:grid;
gap:7px;
}
.ob-avatar__body span{
color:var(--text-strong);
font-size:13px;
font-weight:780;
}
.ob-avatar__body p{
margin:0;
color:var(--text-muted);
font-size:12.5px;
line-height:1.45;
}
.ob-avatar__button{
position:relative;
width:max-content;
min-height:34px;
display:inline-flex;
align-items:center;
justify-content:center;
padding:0 12px;
border:1px solid var(--border-subtle);
border-radius:var(--radius-sm);
background:var(--bg-surface);
color:var(--text-strong);
font-size:12px;
font-weight:760;
cursor:pointer;
}
.ob-avatar__button input{
position:absolute;
inline-size:1px;
block-size:1px;
opacity:0;
pointer-events:none;
}
.ob-field--wide{
grid-column:1 / -1;
}
.ob-fields{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
gap:var(--sp-3);
}
.ob-form label{
min-width:0;
display:grid;
gap:7px;
}
.ob-form label span{
color:var(--text-muted);
font-size:12px;
font-weight:730;
}
.ob-form input[type="text"],
.ob-form input:not([type]){
min-width:0;
}
.ob-form input,
.ob-form textarea{
width:100%;
border:1px solid var(--border-subtle);
border-radius:var(--radius-sm);
background:var(--bg-surface-2);
color:var(--text-strong);
padding:0 12px;
font:500 var(--fs-sm)/1.2 var(--font-sans);
}
.ob-form input{
min-height:42px;
}
.ob-form textarea{
min-height:92px;
padding:11px 12px;
resize:vertical;
line-height:1.45;
}
.ob-form input:focus,
.ob-form textarea:focus{
outline:2px solid color-mix(in srgb,var(--accent) 24%,transparent);
border-color:var(--accent);
}
.ob-checks{
display:grid;
gap:10px;
padding:14px;
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:var(--bg-surface-2);
}
.ob-checks label{
grid-template-columns:auto minmax(0,1fr);
align-items:center;
gap:10px;
}
.ob-checks input{
width:18px;
height:18px;
min-height:18px;
padding:0;
accent-color:var(--accent);
}
.ob-legal{
min-width:0;
display:grid;
gap:8px;
}
.ob-legal details{
min-width:0;
border:1px solid var(--border-subtle);
border-radius:var(--radius-sm);
background:var(--bg-surface-2);
}
.ob-legal summary{
min-width:0;
display:flex;
align-items:center;
justify-content:space-between;
gap:10px;
min-height:42px;
padding:0 12px;
cursor:pointer;
}
.ob-legal summary span{
min-width:0;
color:var(--text-strong);
font-size:13px;
font-weight:760;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.ob-legal summary b{
flex:none;
color:var(--text-muted);
font-size:11px;
font-weight:700;
}
.ob-note,
.ob-error{
margin:0;
color:var(--text-muted);
font-size:12.5px;
line-height:1.5;
}
.ob-doc-text{
max-height:260px;
overflow:auto;
white-space:pre-wrap;
color:var(--text-body);
font-size:12.5px;
line-height:1.55;
padding:0 12px 12px;
}
.ob-error{
color:var(--crit-text);
}
.ob-actions{
display:flex;
justify-content:flex-start;
}
@media (max-width:620px){
.ob-page{
padding:14px;
}
.ob-fields{
grid-template-columns:1fr;
}
.ob-avatar{
grid-template-columns:1fr;
}
.ob-actions .vg-btn{
width:100%;
}
}
`;

View file

@ -2,6 +2,7 @@ import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { Button, Icon } from "../components/ui";
import { roleHomePath, useAuth } from "../lib/auth";
import "./pending-approval.css";
export default function PendingApproval() {
const navigate = useNavigate();
@ -33,7 +34,6 @@ export default function PendingApproval() {
return (
<>
<style>{PENDING_APPROVAL_CSS}</style>
<main className="pa-page" aria-label="계정 승인 대기">
<section className="pa-panel">
<div className="pa-mark" aria-hidden="true">
@ -77,100 +77,3 @@ export default function PendingApproval() {
</>
);
}
const PENDING_APPROVAL_CSS = `
.pa-page{
min-height:100dvh;
display:grid;
place-items:center;
padding:clamp(18px,5vw,56px);
background:var(--bg-app);
color:var(--text-body);
}
.pa-panel{
width:min(100%,620px);
display:grid;
gap:var(--sp-4);
padding:clamp(24px,5vw,44px);
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:color-mix(in srgb,var(--bg-surface) 92%,white 8%);
box-shadow:0 18px 50px rgba(28,43,40,.10);
}
.pa-mark{
width:62px;
height:62px;
display:grid;
place-items:center;
border-radius:var(--radius);
background:var(--accent-tint);
color:var(--accent-deep);
border:1px solid color-mix(in srgb,var(--accent) 20%,transparent);
}
.pa-kicker{
margin:0;
color:var(--accent-deep);
font-size:12px;
font-weight:820;
}
.pa-panel h1{
margin:0;
color:var(--text-strong);
font-size:clamp(30px,5vw,46px);
line-height:1.17;
letter-spacing:0;
}
.pa-copy{
margin:0;
color:var(--text-muted);
font-size:15px;
line-height:1.7;
}
.pa-copy b{
color:var(--text-strong);
font-weight:760;
}
.pa-status{
min-width:0;
display:grid;
grid-template-columns:auto minmax(0,1fr);
gap:12px;
align-items:center;
padding:14px;
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:var(--bg-surface-2);
}
.pa-status > span{
width:10px;
height:10px;
border-radius:50%;
background:#d89a2b;
box-shadow:0 0 0 5px rgba(216,154,43,.14);
}
.pa-status b{
display:block;
color:var(--text-strong);
font-size:14px;
}
.pa-status small{
display:block;
margin-top:3px;
color:var(--text-muted);
font-size:12.5px;
line-height:1.45;
}
.pa-actions{
display:flex;
flex-wrap:wrap;
gap:10px;
}
@media (max-width:560px){
.pa-panel{
border-radius:var(--radius);
}
.pa-actions .vg-btn{
width:100%;
}
}
`;

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,7 @@ import {
type TeacherSafetyAlert,
type TeacherDashboardResponse,
} from "../lib/api";
import "./professor.css";
type LoadState = "loading" | "ready" | "error";
@ -84,7 +85,6 @@ function EmptyState({ title, desc }: { title: string; desc: string }) {
</div>
);
}
export default function Professor() {
const navigate = useNavigate();
const [dashboard, setDashboard] = useState<TeacherDashboardResponse | null>(null);
@ -212,7 +212,6 @@ export default function Professor() {
return (
<AppShell navRole="teacher" wide>
<style>{PF_CSS}</style>
<main className="pf-root">
<header className="pf-head">
<div>
@ -719,920 +718,3 @@ function SafetyAlertRow({ alert }: { alert: TeacherSafetyAlert }) {
</article>
);
}
const PF_CSS = `
.pf-root{
max-width:1280px;
margin:0 auto;
display:flex;
flex-direction:column;
gap:16px;
}
.pf-head{
display:flex;
align-items:flex-end;
justify-content:space-between;
gap:var(--sp-4);
flex-wrap:wrap;
}
.pf-head__actions{
display:flex;
align-items:center;
justify-content:flex-end;
gap:8px;
flex-wrap:wrap;
}
.pf-head h1{
margin:6px 0 0;
color:var(--text-strong);
font-size:var(--fs-h2);
line-height:1.28;
letter-spacing:0;
}
.pf-head p{
margin:6px 0 0;
max-width:760px;
color:var(--text-body);
font-size:var(--fs-xs);
line-height:1.55;
}
.pf-signal-strip{
order:1;
display:grid;
grid-template-columns:1fr;
gap:0;
min-width:0;
}
.pf-triage{
display:grid;
grid-template-columns:auto minmax(0,1fr) auto;
align-items:center;
gap:12px;
min-width:0;
padding:14px 16px;
border:1px solid var(--hair);
border-radius:var(--radius);
background:var(--bg-surface);
box-shadow:var(--shadow-sm);
}
.pf-triage.is-active{
background:color-mix(in srgb,var(--accent-tint) 42%,var(--bg-surface));
}
.pf-triage__copy{
min-width:0;
}
.pf-triage__copy .vg-kicker{
margin-bottom:4px;
}
.pf-triage__copy b{
display:block;
color:var(--text-strong);
font-size:var(--fs-body);
line-height:1.35;
}
.pf-triage__copy span,
.pf-triage__meta small{
color:var(--text-muted);
font-size:var(--fs-xs);
}
.pf-triage__meta{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
gap:0;
min-width:180px;
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:color-mix(in srgb,var(--bg-surface) 86%,transparent);
overflow:hidden;
}
.pf-triage__meta span{
display:grid;
align-content:center;
gap:2px;
padding:9px 12px;
}
.pf-triage__meta span + span{
border-left:1px solid var(--hair);
}
.pf-triage__meta b{
color:var(--text-strong);
font-family:var(--font-num);
font-size:17px;
line-height:1;
}
.pf-error{
display:flex;
align-items:center;
gap:10px;
padding:12px 14px;
border-radius:var(--radius);
background:var(--crit-tint);
color:var(--crit-text);
font-size:var(--fs-sm);
}
.pf-kpis{
display:grid;
grid-template-columns:repeat(6,minmax(0,1fr));
border:1px solid var(--hair);
border-radius:var(--radius);
overflow:hidden;
background:var(--bg-surface);
box-shadow:var(--shadow-sm);
}
.pf-kpi{
position:relative;
min-width:0;
padding:14px 16px;
display:grid;
grid-template-columns:minmax(0,1fr) auto;
gap:4px 10px;
}
.pf-kpi:nth-child(n+2){border-left:1px solid var(--hair);}
.pf-kpi:nth-child(n+4){border-top:0;}
.pf-kpi--primary,
.pf-kpi--warn{
background:color-mix(in srgb,var(--accent-tint) 32%,var(--bg-surface));
}
.pf-kpi--warn{
background:color-mix(in srgb,var(--warn-tint) 36%,var(--bg-surface));
}
.pf-kpi--primary .pf-kpi__ic{
color:var(--text-on-accent);
background:var(--accent);
}
.pf-kpi--warn .pf-kpi__ic{
color:var(--warn-text);
background:var(--warn-tint);
}
.pf-kpi__ic{
grid-column:2;
grid-row:1 / span 3;
width:30px;
height:30px;
display:grid;
place-items:center;
border-radius:var(--radius);
color:var(--accent);
background:var(--accent-tint);
}
.pf-kpi__lab{
display:block;
color:var(--text-muted);
font-size:var(--fs-xs);
font-weight:700;
}
.pf-kpi b{
display:block;
color:var(--text-strong);
font-family:var(--font-num);
font-size:24px;
line-height:1;
}
.pf-kpi small{
color:var(--text-muted);
font-size:12px;
line-height:1.35;
}
.pf-workspace{
order:2;
display:grid;
grid-template-columns:minmax(320px,390px) minmax(0,1fr);
align-items:start;
gap:14px;
min-width:0;
}
.pf-queue-stack{
display:flex;
flex-direction:column;
gap:14px;
min-width:0;
}
.pf-section{
display:flex;
flex-direction:column;
gap:10px;
min-width:0;
}
.pf-section__head{
display:flex;
align-items:flex-end;
justify-content:space-between;
gap:12px;
}
.pf-section__head h2{
margin:4px 0 0;
color:var(--text-strong);
font-size:var(--fs-body);
font-weight:700;
line-height:1.3;
letter-spacing:0;
}
.pf-panel{
padding:0;
overflow:hidden;
}
.pf-studio-card{
display:grid;
grid-template-columns:minmax(0,1fr) auto;
gap:14px;
align-items:center;
padding:14px;
background:linear-gradient(180deg,var(--bg-surface),color-mix(in srgb,var(--accent-tint) 18%,var(--bg-surface)));
}
.pf-studio-card__copy{
min-width:0;
display:grid;
gap:7px;
}
.pf-studio-card__copy b{
color:var(--text-strong);
font-size:var(--fs-body);
}
.pf-studio-card__copy p{
margin:0;
color:var(--text-muted);
font-size:var(--fs-xs);
line-height:1.55;
}
.pf-studio-card__meta{
display:flex;
flex-wrap:wrap;
gap:6px;
}
.pf-studio-card__meta span{
padding:4px 7px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
color:var(--text-body);
background:var(--bg-surface-2);
font-size:11px;
line-height:1.2;
}
.pf-studio-card__actions{
display:flex;
justify-content:flex-end;
}
.pf-section--growth{
order:3;
min-width:0;
}
.pf-growth-panel{
background:linear-gradient(180deg,var(--bg-surface),color-mix(in srgb,var(--accent-tint) 18%,var(--bg-surface)));
}
.pf-growth-list{
max-height:min(420px,44vh);
overflow:auto;
scrollbar-gutter:stable;
display:grid;
grid-template-columns:repeat(3,minmax(0,1fr));
gap:12px;
padding:12px;
}
.pf-growth-card{
min-width:0;
display:flex;
flex-direction:column;
gap:12px;
padding:13px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
background:color-mix(in srgb,var(--bg-surface) 82%,var(--bg-surface-2));
box-shadow:none;
}
.pf-growth-card__top{
display:flex;
align-items:flex-start;
justify-content:space-between;
gap:10px;
min-width:0;
}
.pf-growth-card__id{
min-width:0;
display:grid;
gap:3px;
}
.pf-growth-card__id b{
color:var(--text-strong);
font-size:var(--fs-sm);
line-height:1.35;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-growth-card__id span,
.pf-growth-card__metrics small,
.pf-growth-point span,
.pf-recent__review-state{
color:var(--text-muted);
font-size:12px;
line-height:1.4;
}
.pf-recent__review-state{
display:block;
margin-top:3px;
line-height:1.2;
}
.pf-growth-card__metrics{
display:grid;
grid-template-columns:repeat(3,minmax(0,1fr));
gap:8px;
}
.pf-growth-card__metrics span{
min-width:0;
display:grid;
gap:3px;
padding:9px 10px;
border:1px solid var(--paper-2);
border-radius:var(--radius-sm);
background:var(--bg-surface-2);
}
.pf-growth-card__metrics b{
color:var(--text-strong);
font-family:var(--font-num);
font-size:15px;
line-height:1.15;
white-space:nowrap;
}
.pf-growth-bars{
height:82px;
display:flex;
align-items:flex-end;
gap:6px;
padding:8px 8px 6px;
border:1px solid var(--paper-2);
border-radius:var(--radius-sm);
background:color-mix(in srgb,var(--bg-surface-2) 82%,transparent);
}
.pf-growth-bar{
flex:1 1 0;
min-width:14px;
height:100%;
display:grid;
grid-template-rows:minmax(0,1fr) 14px;
gap:4px;
align-items:end;
}
.pf-growth-bar i{
display:block;
width:100%;
min-height:6px;
border-radius:6px 6px 3px 3px;
background:linear-gradient(180deg,var(--accent),var(--accent-deep));
}
.pf-growth-bar.is-empty i{
background:repeating-linear-gradient(135deg,var(--paper-2),var(--paper-2) 3px,var(--hair) 3px,var(--hair) 6px);
}
.pf-growth-bar small{
color:var(--text-muted);
font-family:var(--font-num);
font-size:10px;
text-align:center;
line-height:1;
}
.pf-growth-card__tags{
min-height:26px;
display:flex;
flex-wrap:wrap;
gap:6px;
align-content:flex-start;
}
.pf-growth-card__tags span{
max-width:100%;
padding:4px 7px;
border:1px solid var(--paper-2);
border-radius:999px;
color:var(--text-body);
background:var(--bg-surface-2);
font-size:11px;
line-height:1.2;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-growth-card__points{
display:grid;
gap:7px;
}
.pf-growth-point{
min-width:0;
display:grid;
grid-template-columns:minmax(86px,.5fr) minmax(0,1fr);
gap:8px;
align-items:center;
}
.pf-growth-point b{
color:var(--text-strong);
font-size:12px;
line-height:1.35;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-growth-point span{
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-empty{
min-height:118px;
display:grid;
place-items:center;
gap:6px;
padding:var(--sp-5);
text-align:center;
}
.pf-empty b{
color:var(--text-strong);
font-size:var(--fs-body);
}
.pf-empty span{
color:var(--text-muted);
font-size:var(--fs-xs);
}
.pf-list{
max-height:min(320px,42vh);
overflow:auto;
scrollbar-gutter:stable;
display:flex;
flex-direction:column;
}
.pf-personas{
max-height:min(320px,42vh);
overflow:auto;
scrollbar-gutter:stable;
display:flex;
flex-direction:column;
}
.pf-persona{
display:grid;
grid-template-columns:minmax(0,1fr);
gap:8px;
align-items:start;
padding:12px;
border-top:1px solid var(--paper-2);
}
.pf-persona:first-child{border-top:0;}
.pf-persona__main{
min-width:0;
display:grid;
grid-template-columns:40px minmax(0,1fr);
gap:10px;
align-items:center;
}
.pf-persona__code{
width:40px;
height:32px;
display:grid;
place-items:center;
border-radius:var(--radius);
color:var(--accent-deep);
background:var(--accent-tint);
font-family:var(--font-num);
font-weight:800;
font-size:12px;
}
.pf-persona__main b{
display:block;
color:var(--text-strong);
font-size:var(--fs-sm);
line-height:1.35;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-persona__main span,
.pf-persona p,
.pf-persona__meta span{
color:var(--text-muted);
font-size:var(--fs-xs);
line-height:1.45;
}
.pf-persona p{
margin:0;
min-width:0;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-persona__meta{
display:flex;
align-items:center;
gap:10px;
justify-content:space-between;
white-space:nowrap;
}
.pf-persona__actions{
display:flex;
justify-content:flex-end;
gap:8px;
min-width:0;
}
.pf-persona__actions .vg-btn{
min-width:72px;
padding-inline:10px;
}
.pf-alerts{
max-height:min(300px,38vh);
overflow:auto;
scrollbar-gutter:stable;
display:flex;
flex-direction:column;
}
.pf-alert{
display:grid;
grid-template-columns:auto minmax(0,1fr) auto;
gap:10px;
align-items:center;
padding:12px;
border-top:1px solid var(--paper-2);
background:color-mix(in srgb,var(--warn-tint) 34%,transparent);
}
.pf-alert:first-child{border-top:0;}
.pf-alert__ic{
width:30px;
height:30px;
display:grid;
place-items:center;
border-radius:var(--radius);
color:var(--warn-text);
background:var(--warn-tint);
}
.pf-alert__main{
min-width:0;
display:grid;
gap:3px;
}
.pf-alert__main b{
color:var(--text-strong);
font-size:var(--fs-sm);
}
.pf-alert__main span,
.pf-alert__main code,
.pf-alert__resource span{
color:var(--text-muted);
font-size:12px;
}
.pf-alert__main code{
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-alert__resource{
display:grid;
gap:2px;
justify-items:end;
min-width:86px;
}
.pf-alert__resource b{
color:var(--warn-text);
font-family:var(--font-num);
font-size:18px;
}
.pf-session{
width:100%;
font:inherit;
text-align:left;
background:transparent;
color:inherit;
display:grid;
grid-template-columns:8px minmax(0,1fr) auto;
gap:8px 10px;
align-items:start;
padding:12px;
border:0;
border-top:1px solid var(--paper-2);
cursor:default;
}
.pf-session:first-child{border-top:0;}
.pf-session--action{
cursor:pointer;
}
.pf-session--action:hover{
background:var(--bg-surface-2);
}
.pf-session--action:focus-visible{
outline:2px solid var(--accent);
outline-offset:-2px;
}
.pf-session__dot{
width:8px;
height:8px;
border-radius:50%;
background:var(--accent);
}
.pf-session__main{
min-width:0;
display:flex;
flex-direction:column;
gap:3px;
}
.pf-session__main b{
color:var(--text-strong);
font-size:var(--fs-sm);
}
.pf-session__main span,.pf-session__main code{
color:var(--text-muted);
font-size:var(--fs-xs);
overflow-wrap:anywhere;
}
.pf-session__main code,.pf-recent__learner code{
font-family:var(--font-num);
overflow:hidden;
text-overflow:ellipsis;
}
.pf-session__meta{
grid-column:2 / 4;
display:flex;
align-items:center;
justify-content:space-between;
gap:8px;
color:var(--text-muted);
font-family:var(--font-num);
font-size:var(--fs-xs);
white-space:normal;
}
.pf-session__open{
grid-column:3;
grid-row:1;
display:inline-flex;
align-items:center;
gap:5px;
align-self:center;
justify-self:end;
min-height:30px;
padding:0 9px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
color:var(--accent-deep);
background:var(--accent-tint);
font-size:var(--fs-xs);
font-weight:760;
white-space:nowrap;
}
.pf-session--action:hover .pf-session__open{
border-color:var(--accent);
}
.pf-recent-list{
max-height:min(620px,calc(100vh - 220px));
overflow:auto;
scrollbar-gutter:stable;
min-width:0;
}
.pf-recent-head,
.pf-recent-row{
display:grid;
grid-template-columns:
minmax(128px,1.25fr)
minmax(58px,.55fr)
minmax(66px,.55fr)
minmax(82px,.85fr)
minmax(32px,.35fr)
minmax(70px,.6fr)
minmax(66px,.55fr)
minmax(86px,.65fr);
align-items:center;
gap:8px;
min-width:0;
}
.pf-recent-head{
position:sticky;
top:0;
z-index:1;
color:var(--text-muted);
font-size:var(--fs-xs);
font-weight:700;
padding:9px 12px;
border-bottom:1px solid var(--hair);
background:var(--bg-surface);
white-space:nowrap;
}
.pf-recent-row{
width:100%;
font:inherit;
text-align:left;
color:inherit;
background:transparent;
padding:10px 12px;
border:0;
border-top:1px solid var(--paper-2);
}
.pf-recent-row--action{
cursor:pointer;
}
.pf-recent-row--action:hover{
background:var(--bg-surface-2);
}
.pf-recent-row--action:focus-visible{
outline:2px solid var(--accent);
outline-offset:-2px;
}
.pf-recent-head + .pf-recent-row{
border-top:0;
}
.pf-recent__learner,
.pf-recent__cell{
min-width:0;
color:var(--text-body);
font-size:var(--fs-sm);
overflow-wrap:anywhere;
}
.pf-recent__cell::before{
display:none;
}
.pf-recent__learner{
min-width:0;
display:flex;
flex-direction:column;
gap:3px;
}
.pf-recent__learner b{
color:var(--text-strong);
font-size:var(--fs-sm);
line-height:1.35;
overflow-wrap:anywhere;
}
.pf-recent__learner code{
max-width:100%;
color:var(--text-muted);
font-size:11px;
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
.pf-recent__cell .vg-badge{
justify-self:start;
}
.pf-recent__cell--open{
display:flex;
justify-content:flex-end;
}
.pf-recent__open{
display:inline-flex;
align-items:center;
justify-content:center;
gap:4px;
min-height:30px;
padding:0 7px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
color:var(--accent-deep);
background:var(--accent-tint);
font-size:var(--fs-xs);
font-weight:760;
white-space:nowrap;
}
.pf-recent-row--action:hover .pf-recent__open{
border-color:var(--accent);
}
@media (max-width:1100px){
.pf-signal-strip,
.pf-workspace{
grid-template-columns:1fr;
}
.pf-triage{
grid-template-columns:auto minmax(0,max-content) auto;
justify-content:start;
}
.pf-kpis{
grid-template-columns:repeat(4,minmax(0,1fr));
}
.pf-kpi:nth-child(n+2){border-left:1px solid var(--hair);}
.pf-kpi:nth-child(n+4){border-top:0;}
.pf-growth-list{
grid-template-columns:repeat(2,minmax(0,1fr));
}
.pf-list,
.pf-personas{
max-height:360px;
}
.pf-recent-list{
max-height:460px;
}
}
@media (max-width:860px){
.pf-head__actions{
width:100%;
justify-content:flex-start;
}
.pf-triage{
grid-template-columns:auto minmax(0,1fr);
}
.pf-triage__meta{
grid-column:1 / -1;
width:100%;
}
.pf-kpis{grid-template-columns:repeat(2,minmax(0,1fr));}
.pf-kpi:nth-child(n+2){border-left:0;}
.pf-kpi:nth-child(even){border-left:1px solid var(--hair);}
.pf-kpi:nth-child(n+3){border-top:1px solid var(--hair);}
.pf-growth-list{
grid-template-columns:1fr;
}
.pf-recent-list{
display:flex;
flex-direction:column;
gap:10px;
padding:10px;
background:var(--bg-surface-2);
overflow-x:hidden;
}
.pf-recent-head{
display:none;
}
.pf-recent-row{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
gap:10px 12px;
padding:12px;
border:1px solid var(--paper-2);
border-radius:var(--radius);
background:var(--bg-surface);
}
.pf-recent__learner{
grid-column:1 / -1;
padding-bottom:10px;
border-bottom:1px solid var(--paper-2);
}
.pf-recent__learner code{
white-space:normal;
overflow-wrap:anywhere;
}
.pf-recent__cell{
display:grid;
grid-template-columns:minmax(74px,.4fr) minmax(0,1fr);
gap:8px;
align-items:center;
}
.pf-recent__cell--open{
justify-content:stretch;
}
.pf-recent__cell::before{
display:block;
content:attr(data-label);
color:var(--text-muted);
font-size:var(--fs-xs);
font-weight:700;
line-height:1.35;
}
.pf-session{
grid-template-columns:8px minmax(0,1fr);
}
.pf-session__meta{
grid-column:2;
justify-content:flex-start;
}
.pf-session__open{
grid-column:2;
grid-row:auto;
justify-self:start;
}
}
@media (max-width:520px){
.pf-kpis{grid-template-columns:repeat(2,minmax(0,1fr));}
.pf-kpi,
.pf-kpi + .pf-kpi{border-left:0;}
.pf-kpi:nth-child(even){border-left:1px solid var(--hair);}
.pf-kpi:nth-child(n+3){border-top:1px solid var(--hair);}
.pf-persona__actions{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
}
.pf-persona__actions .vg-btn{
width:100%;
}
.pf-growth-list{
padding:10px;
}
.pf-growth-card__metrics{
grid-template-columns:1fr;
}
.pf-growth-point{
grid-template-columns:1fr;
gap:2px;
}
.pf-growth-point b,
.pf-growth-point span{
white-space:normal;
}
.pf-alert{
grid-template-columns:auto minmax(0,1fr);
}
.pf-alert__resource{
grid-column:2;
justify-items:start;
}
.pf-recent-list{
padding:8px;
}
.pf-recent-row{
grid-template-columns:1fr;
gap:10px;
}
.pf-recent__cell{
grid-template-columns:minmax(64px,.32fr) minmax(0,1fr);
}
.pf-recent__cell--open .pf-recent__open{
justify-self:start;
}
}
`;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
import { Icon } from "../../components/ui/Icon";
interface LoginBrandProps {
allowedDomains: string[];
oauthChecking: boolean;
}
function VignetteMark() {
return (
<span className="lg-mark" aria-hidden="true">
<svg viewBox="0 0 26 26" width={26} height={26} fill="none">
<circle cx="13" cy="13" r="11" stroke="currentColor" strokeWidth="2" />
<path
d="M8 14.5c1.4 1.7 3 2.5 5 2.5s3.6-.8 5-2.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
<circle cx="13" cy="9" r="1.6" fill="var(--clay)" />
</svg>
</span>
);
}
export function LoginBrand({ allowedDomains, oauthChecking }: LoginBrandProps) {
return (
<section className="lg-brand" aria-label="Vignette">
<div className="lg-wordmark">
<VignetteMark />
<span>Vignette</span>
</div>
<div className="lg-copy">
<span className="lg-kicker">
<span className="d" aria-hidden="true" />
</span>
<h1>
<span className="lg-highlight"> </span>, <br />
.
</h1>
<p>
Vignette는 , , .
.
</p>
</div>
<div className="lg-policy">
<span>
<Icon name="shield" size={17} />
</span>
{allowedDomains.length ? (
allowedDomains.map((domain) => <b key={domain}>{domain}</b>)
) : (
<b>{oauthChecking ? "확인 중" : "설정 필요"}</b>
)}
</div>
</section>
);
}

View file

@ -0,0 +1,169 @@
import { Icon, type IconName } from "../../components/ui/Icon";
import type { Role } from "../../lib/auth";
export interface LoginRoleOption {
role: Role;
label: string;
desc: string;
dotClass: string;
}
interface LoginOAuthView {
ready: boolean;
primaryDomainLabel: string;
secondaryDomainLabel: string;
statusIcon: Extract<IconName, "alert" | "info">;
statusMessage: string;
onStart: () => void;
}
interface LoginDevAccessView {
ready: boolean;
selected: Role;
pending: boolean;
options: LoginRoleOption[];
onSelect: (role: Role) => void;
onEnter: (role: Role) => void;
}
interface LoginErrorView {
message: string | null;
reason: string | null;
}
interface LoginPanelProps {
oauth: LoginOAuthView;
devAccess: LoginDevAccessView;
error: LoginErrorView;
}
function LoginProviderButton({
variant,
icon,
label,
subLabel,
disabled,
onClick,
}: {
variant: "primary" | "secondary";
icon: Extract<IconName, "google" | "school">;
label: string;
subLabel: string;
disabled: boolean;
onClick: () => void;
}) {
return (
<button className={`lg-obtn ${variant}`} type="button" onClick={onClick} disabled={disabled}>
<span className="ic">
<Icon name={icon} size={19} strokeWidth={1.8} />
</span>
<span className="txt">
{label}
<span className="sub">{subLabel}</span>
</span>
<Icon name="chevron-right" size={18} strokeWidth={2} />
</button>
);
}
function LoginErrorNotice({ error }: { error: LoginErrorView }) {
if (!error.message) return null;
return (
<p className="lg-error">
{error.message}
{error.reason ? <small> : {error.reason}</small> : null}
</p>
);
}
function LoginDevAccess({ devAccess, error }: { devAccess: LoginDevAccessView; error: LoginErrorView }) {
if (!devAccess.ready) return null;
return (
<div className="lg-dev">
<div className="lg-devhead">
<span> </span>
<small> </small>
</div>
<div className="lg-rolepick" role="radiogroup" aria-label="로컬 테스트 역할">
{devAccess.options.map((opt) => (
<button
key={opt.role}
type="button"
role="radio"
aria-checked={devAccess.selected === opt.role}
className={`lg-roleopt ${devAccess.selected === opt.role ? "is-sel" : ""}`}
onClick={() => devAccess.onSelect(opt.role)}
>
<span className={`d ${opt.dotClass}`} aria-hidden="true" />
<span>
<b>{opt.label}</b>
<small>{opt.desc}</small>
</span>
{devAccess.selected === opt.role ? <Icon name="check" size={15} /> : null}
</button>
))}
</div>
<button
className="lg-devbtn"
type="button"
onClick={() => devAccess.onEnter(devAccess.selected)}
disabled={devAccess.pending}
>
{devAccess.pending ? "테스트 계정 확인 중" : "로컬 테스트 계정으로 계속"}
</button>
<LoginErrorNotice error={error} />
</div>
);
}
export function LoginPanel({ oauth, devAccess, error }: LoginPanelProps) {
return (
<section className="lg-enter" aria-label="로그인">
<div className="lg-panel">
<span className="lg-kicker">
<span className="d" aria-hidden="true" />
</span>
<h2></h2>
<p className="lg-lead">
Google .
</p>
<div className="lg-actions">
<LoginProviderButton
variant="primary"
icon="school"
label="학교 Google 계정으로 계속"
subLabel={oauth.primaryDomainLabel}
disabled={!oauth.ready}
onClick={oauth.onStart}
/>
<LoginProviderButton
variant="secondary"
icon="google"
label="Google 계정으로 계속"
subLabel={oauth.secondaryDomainLabel}
disabled={!oauth.ready}
onClick={oauth.onStart}
/>
</div>
{!oauth.ready ? (
<div className="lg-config" role="status">
<Icon name={oauth.statusIcon} size={17} />
<span>{oauth.statusMessage}</span>
</div>
) : null}
<LoginDevAccess devAccess={devAccess} error={error} />
{!devAccess.ready ? <LoginErrorNotice error={error} /> : null}
<p className="lg-note">
. , , .
</p>
</div>
</section>
);
}

View file

@ -0,0 +1,484 @@
.lg-root {
min-height: 100dvh;
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(360px, 500px);
background:
linear-gradient(
90deg,
rgba(7, 16, 14, 0.84) 0%,
rgba(10, 21, 19, 0.72) 48%,
rgba(10, 21, 19, 0.55) 74%,
rgba(10, 21, 19, 0.62) 100%
),
var(--asset-login-room) center / cover no-repeat;
color: #edf4f2;
overflow-x: hidden;
overflow-y: auto;
}
.lg-root::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(3, 9, 8, 0.18), rgba(3, 9, 8, 0.42));
pointer-events: none;
}
.lg-brand,
.lg-enter {
position: relative;
z-index: 1;
}
.lg-brand {
min-width: 0;
width: 100%;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: var(--sp-7);
padding: var(--sp-7);
background: transparent;
color: #edf4f2;
overflow: hidden;
}
.lg-brand::after {
display: none;
}
.lg-wordmark {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 10px;
font-size: 19px;
font-weight: 700;
letter-spacing: 0;
color: #edf4f2;
}
.lg-mark {
display: grid;
place-items: center;
color: var(--accent-bright);
}
.lg-copy {
max-width: 620px;
}
.lg-copy,
.lg-policy {
position: relative;
z-index: 1;
}
.lg-kicker {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--font-num);
font-size: 12px;
font-weight: 700;
letter-spacing: 0;
text-transform: uppercase;
color: var(--accent-bright);
}
.lg-kicker .d {
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
}
.lg-highlight {
display: inline-block;
position: relative;
z-index: 0;
padding: 0 4px;
background: linear-gradient(90deg, #59b5a6, #8ee4d6);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.lg-copy h1 {
margin: var(--sp-4) 0 0;
max-width: 640px;
font-size: 56px;
line-height: 1.12;
letter-spacing: 0;
font-weight: 760;
}
.lg-copy p {
margin: var(--sp-5) 0 0;
max-width: 560px;
color: rgba(237, 244, 242, 0.72);
font-size: 17px;
line-height: 1.75;
}
.lg-policy {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
width: max-content;
max-width: min(430px, 100%);
padding: 14px 16px;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: var(--radius-lg);
background: rgba(237, 244, 242, 0.08);
backdrop-filter: blur(18px) saturate(1.08);
-webkit-backdrop-filter: blur(18px) saturate(1.08);
color: rgba(237, 244, 242, 0.66);
font-size: 13px;
}
.lg-policy span,
.lg-policy b {
display: inline-flex;
align-items: center;
gap: 7px;
}
.lg-policy b {
color: #edf4f2;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 999px;
padding: 5px 10px;
font-weight: 650;
}
.lg-enter {
min-width: 0;
display: flex;
align-items: center;
justify-content: center;
padding: var(--sp-6);
background: transparent;
}
.lg-panel {
width: 100%;
max-width: 430px;
background: rgba(11, 25, 22, 0.64);
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: var(--radius-lg);
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.08);
backdrop-filter: blur(24px) saturate(1.14);
-webkit-backdrop-filter: blur(24px) saturate(1.14);
padding: var(--sp-6);
color: #edf4f2;
}
.lg-panel h2 {
margin: var(--sp-3) 0 0;
font-size: 28px;
line-height: 1.25;
letter-spacing: 0;
color: #f7fbf9;
}
.lg-lead {
margin: 10px 0 0;
color: rgba(237, 244, 242, 0.76);
font-size: 14px;
line-height: 1.65;
}
.lg-actions {
display: flex;
flex-direction: column;
gap: var(--sp-3);
margin-top: var(--sp-6);
}
.lg-obtn {
width: 100%;
min-height: 58px;
display: grid;
grid-template-columns: 36px minmax(0, 1fr) 18px;
align-items: center;
gap: 13px;
border-radius: var(--radius);
padding: 11px 14px;
font-family: var(--font-sans);
font-size: 15px;
font-weight: 650;
text-align: left;
cursor: pointer;
}
.lg-obtn.primary {
background: rgba(89, 181, 166, 0.2);
border: 1px solid rgba(114, 211, 197, 0.44);
color: #f7fbf9;
}
.lg-obtn.primary:hover {
background: rgba(89, 181, 166, 0.28);
border-color: rgba(114, 211, 197, 0.62);
}
.lg-obtn.secondary {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.13);
color: #edf4f2;
}
.lg-obtn.secondary:hover {
border-color: rgba(114, 211, 197, 0.42);
background: rgba(255, 255, 255, 0.11);
}
.lg-obtn:disabled {
cursor: not-allowed;
opacity: 1;
background: rgba(255, 255, 255, 0.07);
border-color: rgba(255, 255, 255, 0.1);
color: rgba(237, 244, 242, 0.58);
}
.lg-obtn:disabled:hover {
background: rgba(255, 255, 255, 0.07);
border-color: rgba(255, 255, 255, 0.1);
}
.lg-obtn:disabled .ic {
background: rgba(255, 255, 255, 0.08);
color: rgba(237, 244, 242, 0.58);
}
.lg-obtn:disabled .sub {
color: rgba(237, 244, 242, 0.45);
}
.lg-obtn .ic {
width: 36px;
height: 36px;
display: grid;
place-items: center;
border-radius: 8px;
background: rgba(255, 255, 255, 0.16);
}
.lg-obtn.secondary .ic {
background: rgba(7, 17, 15, 0.34);
}
.lg-obtn .txt {
min-width: 0;
display: flex;
flex-direction: column;
gap: 1px;
}
.lg-obtn .sub {
font-size: 12px;
font-weight: 550;
color: rgba(237, 244, 242, 0.58);
}
.lg-obtn.primary .sub {
color: rgba(251, 250, 248, 0.74);
}
.lg-config {
display: flex;
align-items: flex-start;
gap: 10px;
margin-top: var(--sp-3);
padding: 10px 12px;
border-radius: var(--radius);
background: rgba(154, 94, 20, 0.28);
border: 1px solid rgba(236, 180, 91, 0.16);
color: #f2b75f;
font-size: 12.5px;
line-height: 1.5;
}
.lg-dev {
margin-top: var(--sp-6);
padding-top: var(--sp-5);
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.lg-devhead {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--sp-3);
color: #edf4f2;
font-size: 13px;
font-weight: 700;
}
.lg-devhead small {
color: rgba(237, 244, 242, 0.58);
font-weight: 600;
}
.lg-rolepick {
display: grid;
grid-template-columns: 1fr;
gap: var(--sp-2);
margin-top: var(--sp-3);
}
.lg-roleopt {
min-height: 50px;
display: grid;
grid-template-columns: 10px minmax(0, 1fr) 16px;
align-items: center;
gap: 10px;
padding: 9px 11px;
border: 1px solid rgba(255, 255, 255, 0.11);
border-radius: var(--radius);
background: rgba(255, 255, 255, 0.045);
color: #edf4f2;
text-align: left;
cursor: pointer;
}
.lg-roleopt.is-sel {
border-color: rgba(114, 211, 197, 0.58);
background: rgba(89, 181, 166, 0.15);
}
.lg-roleopt .d {
width: 8px;
height: 8px;
border-radius: 50%;
}
.lg-roleopt .d.learner {
background: var(--accent-bright);
}
.lg-roleopt .d.teacher {
background: #5478c4;
}
.lg-roleopt .d.admin {
background: #7d818e;
}
.lg-roleopt b {
display: block;
font-size: 13px;
}
.lg-roleopt small {
display: block;
margin-top: 1px;
color: rgba(237, 244, 242, 0.58);
font-size: 12px;
}
.lg-devbtn {
width: 100%;
min-height: 46px;
margin-top: var(--sp-3);
border: 1px solid rgba(114, 211, 197, 0.58);
border-radius: var(--radius);
background: rgba(89, 181, 166, 0.15);
color: #8fe7d9;
font-family: var(--font-sans);
font-size: 14px;
font-weight: 750;
cursor: pointer;
}
.lg-devbtn:disabled {
opacity: 0.62;
cursor: wait;
}
.lg-error {
margin: var(--sp-3) 0 0;
color: var(--crit-text);
background: rgba(122, 38, 38, 0.28);
border: 1px solid rgba(255, 145, 145, 0.18);
border-radius: var(--radius);
padding: 10px 12px;
font-size: 13px;
line-height: 1.5;
}
.lg-error small {
display: block;
margin-top: 4px;
color: rgba(237, 244, 242, 0.56);
font-family: var(--font-num);
font-size: 11.5px;
overflow-wrap: anywhere;
}
.lg-note {
margin: var(--sp-5) 0 0;
color: rgba(237, 244, 242, 0.52);
font-size: 12.5px;
line-height: 1.6;
}
@media (max-width: 880px) {
.lg-root {
grid-template-columns: 1fr;
}
.lg-root::before {
display: none;
}
.lg-brand {
padding: var(--sp-6) var(--sp-5);
gap: var(--sp-6);
}
.lg-brand::after {
display: none;
}
.lg-copy h1 {
font-size: 36px;
}
.lg-enter {
padding: var(--sp-5);
}
.lg-panel {
max-width: 560px;
}
}
@media (max-width: 480px) {
.lg-brand {
padding: var(--sp-5);
}
.lg-copy h1 {
font-size: 30px;
}
.lg-copy p {
font-size: 15px;
}
.lg-enter {
padding: var(--sp-4);
}
.lg-panel {
padding: var(--sp-5);
}
}

View file

@ -1,134 +1,253 @@
.ob-root {
width: min(100%, 1040px);
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 16px;
.ob-page{
min-height:100dvh;
width:100%;
background:var(--bg-app);
color:var(--text-body);
padding:clamp(20px,5vw,56px);
}
.ob-head h1 {
margin: 6px 0 0;
color: var(--text-strong);
font-size: var(--fs-h2);
line-height: 1.28;
letter-spacing: 0;
.ob-shell{
width:100%;
max-width:900px;
margin:0 auto;
display:grid;
gap:var(--sp-6);
}
.ob-head p {
margin: 8px 0 0;
color: var(--text-body);
font-size: var(--fs-sm);
line-height: 1.55;
.ob-head{
display:grid;
gap:8px;
}
.ob-alert {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
border-radius: var(--radius);
background: var(--crit-tint);
color: var(--crit-text);
font-size: var(--fs-sm);
.ob-head p{
margin:0;
color:var(--accent-deep);
font-size:12px;
font-weight:800;
}
.ob-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
gap: 14px;
align-items: start;
.ob-head h1{
margin:0;
color:var(--text-strong);
font-size:clamp(28px,4vw,44px);
line-height:1.18;
letter-spacing:0;
}
.ob-panel {
min-width: 0;
border: 1px solid var(--hair);
border-radius: var(--radius);
background: var(--bg-surface);
box-shadow: var(--shadow-sm);
.ob-form{
min-width:0;
display:grid;
gap:var(--sp-6);
}
.ob-form {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
padding: 16px;
.ob-section{
min-width:0;
display:grid;
gap:var(--sp-4);
padding-bottom:var(--sp-5);
border-bottom:1px solid var(--border-subtle);
}
.ob-form .vg-btn,
.ob-check {
grid-column: 1 / -1;
.ob-section__head h2{
margin:0;
color:var(--text-strong);
font-size:18px;
line-height:1.35;
letter-spacing:0;
}
.ob-check {
display: flex;
align-items: center;
gap: 9px;
color: var(--text-body);
font-size: var(--fs-sm);
line-height: 1.4;
.ob-avatar{
display:grid;
grid-template-columns:auto minmax(0,1fr);
gap:var(--sp-3);
align-items:center;
}
.ob-check input {
width: 17px;
height: 17px;
accent-color: var(--accent);
.ob-avatar__preview{
width:72px;
height:72px;
border-radius:50%;
display:grid;
place-items:center;
overflow:hidden;
background:var(--accent-tint);
color:var(--accent-deep);
font-size:28px;
font-weight:800;
border:1px solid var(--border-subtle);
}
.ob-docs {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
.ob-avatar__preview img{
width:100%;
height:100%;
display:block;
object-fit:cover;
}
.ob-docs h2 {
margin: 6px 0 0;
color: var(--text-strong);
font-size: var(--fs-body);
.ob-avatar__body{
min-width:0;
display:grid;
gap:7px;
}
.ob-docs p {
margin: 6px 0 0;
color: var(--text-body);
font-size: var(--fs-xs);
line-height: 1.55;
.ob-avatar__body span{
color:var(--text-strong);
font-size:13px;
font-weight:780;
}
.ob-docs article {
min-width: 0;
max-height: 220px;
overflow: auto;
padding: 12px;
border: 1px solid var(--hair);
border-radius: var(--radius-sm);
background: var(--bg-surface-2);
.ob-avatar__body p{
margin:0;
color:var(--text-muted);
font-size:12.5px;
line-height:1.45;
}
.ob-docs b,
.ob-docs small {
display: block;
.ob-avatar__button{
position:relative;
width:max-content;
min-height:34px;
display:inline-flex;
align-items:center;
justify-content:center;
padding:0 12px;
border:1px solid var(--border-subtle);
border-radius:var(--radius-sm);
background:var(--bg-surface);
color:var(--text-strong);
font-size:12px;
font-weight:760;
cursor:pointer;
}
.ob-docs b {
color: var(--text-strong);
font-size: var(--fs-sm);
.ob-avatar__button input{
position:absolute;
inline-size:1px;
block-size:1px;
opacity:0;
pointer-events:none;
}
.ob-docs small {
margin-top: 2px;
color: var(--text-muted);
font-family: var(--font-num);
font-size: var(--fs-xs);
.ob-field--wide{
grid-column:1 / -1;
}
@media (max-width: 860px) {
.ob-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 560px) {
.ob-form {
grid-template-columns: 1fr;
.ob-fields{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
gap:var(--sp-3);
}
.ob-form label{
min-width:0;
display:grid;
gap:7px;
}
.ob-form label span{
color:var(--text-muted);
font-size:12px;
font-weight:730;
}
.ob-form input[type="text"],
.ob-form input:not([type]){
min-width:0;
}
.ob-form input,
.ob-form textarea{
width:100%;
border:1px solid var(--border-subtle);
border-radius:var(--radius-sm);
background:var(--bg-surface-2);
color:var(--text-strong);
padding:0 12px;
font:500 var(--fs-sm)/1.2 var(--font-sans);
}
.ob-form input{
min-height:42px;
}
.ob-form textarea{
min-height:92px;
padding:11px 12px;
resize:vertical;
line-height:1.45;
}
.ob-form input:focus,
.ob-form textarea:focus{
outline:2px solid color-mix(in srgb,var(--accent) 24%,transparent);
border-color:var(--accent);
}
.ob-checks{
display:grid;
gap:10px;
padding:14px;
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:var(--bg-surface-2);
}
.ob-checks label{
grid-template-columns:auto minmax(0,1fr);
align-items:center;
gap:10px;
}
.ob-checks input{
width:18px;
height:18px;
min-height:18px;
padding:0;
accent-color:var(--accent);
}
.ob-legal{
min-width:0;
display:grid;
gap:8px;
}
.ob-legal details{
min-width:0;
border:1px solid var(--border-subtle);
border-radius:var(--radius-sm);
background:var(--bg-surface-2);
}
.ob-legal summary{
min-width:0;
display:flex;
align-items:center;
justify-content:space-between;
gap:10px;
min-height:42px;
padding:0 12px;
cursor:pointer;
}
.ob-legal summary span{
min-width:0;
color:var(--text-strong);
font-size:13px;
font-weight:760;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.ob-legal summary b{
flex:none;
color:var(--text-muted);
font-size:11px;
font-weight:700;
}
.ob-note,
.ob-error{
margin:0;
color:var(--text-muted);
font-size:12.5px;
line-height:1.5;
}
.ob-doc-text{
max-height:260px;
overflow:auto;
white-space:pre-wrap;
color:var(--text-body);
font-size:12.5px;
line-height:1.55;
padding:0 12px 12px;
}
.ob-error{
color:var(--crit-text);
}
.ob-actions{
display:flex;
justify-content:flex-start;
}
@media (max-width:620px){
.ob-page{
padding:14px;
}
.ob-fields{
grid-template-columns:1fr;
}
.ob-avatar{
grid-template-columns:1fr;
}
.ob-actions .vg-btn{
width:100%;
}
}

View file

@ -0,0 +1,94 @@
.pa-page{
min-height:100dvh;
display:grid;
place-items:center;
padding:clamp(18px,5vw,56px);
background:var(--bg-app);
color:var(--text-body);
}
.pa-panel{
width:min(100%,620px);
display:grid;
gap:var(--sp-4);
padding:clamp(24px,5vw,44px);
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:color-mix(in srgb,var(--bg-surface) 92%,white 8%);
box-shadow:0 18px 50px rgba(28,43,40,.10);
}
.pa-mark{
width:62px;
height:62px;
display:grid;
place-items:center;
border-radius:var(--radius);
background:var(--accent-tint);
color:var(--accent-deep);
border:1px solid color-mix(in srgb,var(--accent) 20%,transparent);
}
.pa-kicker{
margin:0;
color:var(--accent-deep);
font-size:12px;
font-weight:820;
}
.pa-panel h1{
margin:0;
color:var(--text-strong);
font-size:clamp(30px,5vw,46px);
line-height:1.17;
letter-spacing:0;
}
.pa-copy{
margin:0;
color:var(--text-muted);
font-size:15px;
line-height:1.7;
}
.pa-copy b{
color:var(--text-strong);
font-weight:760;
}
.pa-status{
min-width:0;
display:grid;
grid-template-columns:auto minmax(0,1fr);
gap:12px;
align-items:center;
padding:14px;
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:var(--bg-surface-2);
}
.pa-status > span{
width:10px;
height:10px;
border-radius:50%;
background:#d89a2b;
box-shadow:0 0 0 5px rgba(216,154,43,.14);
}
.pa-status b{
display:block;
color:var(--text-strong);
font-size:14px;
}
.pa-status small{
display:block;
margin-top:3px;
color:var(--text-muted);
font-size:12.5px;
line-height:1.45;
}
.pa-actions{
display:flex;
flex-wrap:wrap;
gap:10px;
}
@media (max-width:560px){
.pa-panel{
border-radius:var(--radius);
}
.pa-actions .vg-btn{
width:100%;
}
}

View file

@ -0,0 +1,688 @@
.ps-root{
display:grid;
gap:20px;
}
.ps-head{
display:flex;
justify-content:space-between;
align-items:flex-start;
gap:16px;
}
.ps-head h1{
margin:4px 0 0;
font-size:clamp(24px,3vw,38px);
line-height:1.16;
letter-spacing:0;
color:var(--text-strong);
}
.ps-head__actions{
display:flex;
gap:8px;
flex-wrap:wrap;
justify-content:flex-end;
}
.ps-error{
display:flex;
align-items:center;
gap:8px;
padding:12px 14px;
border:1px solid var(--crit-solid);
border-radius:var(--radius);
color:var(--crit-text);
background:var(--crit-tint);
}
.ps-layout{
display:grid;
grid-template-columns:minmax(220px,300px) minmax(460px,1fr) minmax(240px,320px);
gap:16px;
align-items:start;
}
.ps-rail,
.ps-inspector{
position:sticky;
top:76px;
display:grid;
gap:14px;
min-width:0;
}
.ps-rail__head,
.ps-editor__top,
.ps-workflow,
.ps-inspector__block{
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:var(--bg-surface);
padding:16px;
box-shadow:var(--shadow-sm);
}
.ps-rail__head,
.ps-editor__top{
display:flex;
align-items:flex-start;
justify-content:space-between;
gap:12px;
}
.ps-rail__head b,
.ps-editor__top h2{
display:block;
margin:4px 0 0;
color:var(--text-strong);
}
.ps-editor__top h2{
font-size:22px;
line-height:1.25;
letter-spacing:0;
}
.ps-list,
.ps-approved,
.ps-review-cards,
.ps-check{
display:grid;
gap:8px;
}
.ps-list-row{
width:100%;
display:grid;
grid-template-columns:42px minmax(0,1fr);
gap:10px;
align-items:center;
padding:10px;
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:var(--bg-surface);
color:inherit;
text-align:left;
cursor:pointer;
}
.ps-list-row:hover,
.ps-list-row.is-active{
border-color:var(--accent);
background:var(--accent-tint);
}
.ps-list-row:disabled{
opacity:.55;
cursor:not-allowed;
}
.ps-list-row__code{
display:grid;
place-items:center;
min-width:38px;
min-height:34px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
color:var(--accent-deep);
background:var(--bg-surface-2);
font-weight:760;
font-family:var(--font-num);
}
.ps-list-row__body{
min-width:0;
}
.ps-list-row__body b,
.ps-list-row__body small{
display:block;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.ps-list-row__body small,
.ps-muted,
.ps-approved-row span,
.ps-review span{
color:var(--text-muted);
font-size:var(--fs-xs);
}
.ps-approved{
padding:14px;
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:var(--bg-surface);
}
.ps-workflow{
display:grid;
gap:10px;
}
.ps-workflow-step{
display:grid;
grid-template-columns:30px minmax(0,1fr);
align-items:center;
gap:10px;
padding:10px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
background:var(--bg-surface-2);
color:var(--text-muted);
}
.ps-workflow-step > span{
display:grid;
place-items:center;
width:30px;
height:30px;
border:1px solid var(--border-subtle);
border-radius:50%;
background:var(--bg-surface);
color:var(--text-muted);
font-family:var(--font-num);
font-weight:760;
}
.ps-workflow-step b,
.ps-workflow-step small{
display:block;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.ps-workflow-step b{
color:var(--text-strong);
font-size:var(--fs-sm);
}
.ps-workflow-step small{
margin-top:2px;
font-size:11px;
}
.ps-workflow-step.is-current{
border-color:var(--accent);
background:var(--accent-tint);
color:var(--accent-deep);
}
.ps-workflow-step.is-current > span,
.ps-workflow-step.is-done > span{
border-color:var(--accent);
background:var(--accent);
color:var(--text-on-accent);
}
.ps-approved-row{
display:grid;
grid-template-columns:38px minmax(0,1fr);
gap:8px 10px;
align-items:center;
padding:8px 0;
border-top:1px solid var(--hair);
}
.ps-approved-row:first-of-type{border-top:0;}
.ps-approved-row__code{
font-family:var(--font-num);
font-size:var(--fs-xs);
color:var(--text-muted);
}
.ps-approved-row__body{
min-width:0;
display:grid;
gap:2px;
}
.ps-approved-row b{
min-width:0;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
font-size:var(--fs-xs);
}
.ps-approved-row small{
min-width:0;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
color:var(--text-muted);
font-size:11px;
}
.ps-approved-row__actions{
grid-column:1 / -1;
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
gap:6px;
}
.ps-approved-row__actions .vg-btn{
width:100%;
}
.ps-editor{
display:grid;
gap:14px;
min-width:0;
}
.ps-source-panel{
display:grid;
gap:12px;
padding:16px;
}
.ps-source-panel__head,
.ps-source-panel__actions{
display:flex;
align-items:flex-start;
justify-content:space-between;
gap:12px;
}
.ps-source-panel__head b{
display:block;
margin-top:4px;
color:var(--text-strong);
font-size:var(--fs-body);
}
.ps-source-grid{
display:grid;
grid-template-columns:minmax(220px,.42fr) minmax(280px,.58fr);
gap:12px;
}
.ps-source-grid .ps-field--wide{
grid-column:1 / -1;
}
.ps-upload-card{
display:grid;
grid-template-columns:minmax(0,1fr) auto;
align-items:center;
gap:14px;
padding:16px;
border:1px dashed var(--accent);
border-radius:var(--radius);
background:var(--accent-tint);
}
.ps-upload-card b,
.ps-upload-card span{
display:block;
}
.ps-upload-card b{
margin-top:5px;
color:var(--text-strong);
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.ps-upload-card span{
margin-top:4px;
color:var(--text-muted);
font-size:var(--fs-xs);
line-height:1.45;
}
.ps-file-input{
display:none;
}
.ps-source-panel__actions span{
min-width:0;
color:var(--text-muted);
font-size:var(--fs-xs);
line-height:1.45;
}
.ps-tabs,
.ps-segments{
display:flex;
flex-wrap:wrap;
gap:6px;
}
.ps-tabs button,
.ps-segments button{
min-height:34px;
padding:0 12px;
border:1px solid var(--border-subtle);
border-radius:var(--radius-sm);
background:var(--bg-surface);
color:var(--text-body);
font-weight:700;
cursor:pointer;
}
.ps-tabs button.is-active,
.ps-segments button.is-active{
border-color:var(--accent);
color:var(--accent-deep);
background:var(--accent-tint);
}
.ps-edit-panel{
padding:0;
overflow:hidden;
}
.ps-guidance{
display:grid;
grid-template-columns:minmax(0,.95fr) minmax(260px,.75fr);
gap:16px;
padding:16px;
border-bottom:1px solid var(--hair);
background:var(--bg-surface-2);
}
.ps-guidance b{
display:block;
margin-top:5px;
color:var(--text-strong);
line-height:1.35;
}
.ps-guidance p{
margin:6px 0 0;
color:var(--text-muted);
font-size:var(--fs-xs);
line-height:1.5;
}
.ps-guidance ul{
display:grid;
gap:7px;
margin:0;
padding:0;
list-style:none;
}
.ps-guidance li{
display:grid;
grid-template-columns:8px minmax(0,1fr);
align-items:start;
gap:8px;
color:var(--text-body);
font-size:var(--fs-xs);
line-height:1.45;
}
.ps-guidance li::before{
content:"";
width:6px;
height:6px;
margin-top:.55em;
border-radius:50%;
background:var(--accent);
}
.ps-form-grid{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
gap:14px;
padding:16px;
}
.ps-number-grid{
display:grid;
grid-template-columns:repeat(4,minmax(0,1fr));
gap:10px;
padding:16px;
}
.ps-field,
.ps-numfield{
display:grid;
gap:7px;
min-width:0;
}
.ps-list-field{
display:grid;
gap:9px;
min-width:0;
}
.ps-field--wide{
grid-column:1 / -1;
}
.ps-field span,
.ps-numfield span,
.ps-list-field__head span{
color:var(--text-muted);
font-size:var(--fs-xs);
font-weight:760;
}
.ps-list-field__head{
display:flex;
align-items:center;
justify-content:space-between;
gap:10px;
min-width:0;
}
.ps-list-field__rows{
display:grid;
gap:8px;
}
.ps-list-item{
display:grid;
grid-template-columns:minmax(0,1fr) 34px;
align-items:start;
gap:8px;
}
.ps-field input,
.ps-field select,
.ps-field textarea,
.ps-list-item textarea,
.ps-numfield input{
width:100%;
border:1px solid var(--border-subtle);
border-radius:var(--radius-sm);
background:var(--bg-surface);
color:var(--text-body);
font:inherit;
}
.ps-field input,
.ps-field select,
.ps-numfield input{
min-height:38px;
padding:0 10px;
}
.ps-field textarea,
.ps-list-item textarea{
min-height:92px;
resize:vertical;
padding:10px;
line-height:1.55;
}
.ps-list-item textarea{
min-height:44px;
}
.ps-list-item__remove{
display:grid;
place-items:center;
width:34px;
height:34px;
border:1px solid var(--border-subtle);
border-radius:var(--radius-sm);
background:var(--bg-surface);
color:var(--text-muted);
cursor:pointer;
}
.ps-list-item__remove:hover{
color:var(--danger);
border-color:var(--danger);
}
.ps-field input:focus,
.ps-field select:focus,
.ps-field textarea:focus,
.ps-list-item textarea:focus,
.ps-numfield input:focus{
outline:2px solid var(--accent);
outline-offset:1px;
border-color:var(--accent);
}
.ps-prompt{
padding:16px;
display:grid;
gap:12px;
}
.ps-prompt-section{
display:grid;
gap:10px;
padding:14px;
border:1px solid var(--border-subtle);
border-radius:var(--radius-sm);
background:var(--bg-surface);
}
.ps-prompt-section__head{
display:grid;
gap:4px;
}
.ps-prompt-section__head h3{
margin:0;
color:var(--text-heading);
font-size:var(--fs-md);
line-height:1.25;
}
.ps-prompt-section__head p,
.ps-prompt-body,
.ps-prompt-empty{
margin:0;
color:var(--text-muted);
font-size:var(--fs-sm);
line-height:1.55;
}
.ps-prompt-rows{
display:grid;
gap:7px;
margin:0;
}
.ps-prompt-row{
display:grid;
grid-template-columns:minmax(110px,0.34fr) minmax(0,1fr);
gap:10px;
align-items:start;
padding:8px 0;
border-top:1px solid var(--border-faint);
}
.ps-prompt-row dt{
color:var(--text-muted);
font-size:var(--fs-xs);
font-weight:760;
}
.ps-prompt-row dd{
margin:0;
color:var(--text-body);
font-size:var(--fs-sm);
line-height:1.55;
white-space:pre-wrap;
overflow-wrap:anywhere;
}
.ps-prompt-list{
display:grid;
gap:6px;
margin:0;
padding-left:18px;
color:var(--text-body);
font-size:var(--fs-sm);
line-height:1.5;
}
.ps-prompt-list li{
overflow-wrap:anywhere;
}
.ps-actions{
display:flex;
justify-content:flex-end;
gap:8px;
}
.ps-status{
margin:0;
padding:10px 12px;
border:1px solid var(--accent);
border-radius:var(--radius-sm);
color:var(--accent-deep);
background:var(--accent-tint);
font-size:var(--fs-sm);
font-weight:700;
}
.ps-status.is-error{
border-color:var(--crit-solid);
color:var(--crit-text);
background:var(--crit-tint);
}
.ps-check-row{
display:grid;
grid-template-columns:12px minmax(0,1fr);
gap:8px;
align-items:center;
min-height:20px;
font-size:var(--fs-xs);
line-height:1.45;
color:var(--text-body);
}
.ps-check-row .vg-dot{
justify-self:center;
align-self:center;
}
.ps-check-row--stack{
align-items:start;
}
.ps-check-row--stack .vg-dot{
margin-top:5px;
}
.ps-check-row--stack span{
display:grid;
gap:2px;
}
.ps-check-row--stack b{
color:var(--text-strong);
font-size:var(--fs-xs);
}
.ps-check-row--stack small{
color:var(--text-muted);
font-size:11px;
overflow-wrap:anywhere;
}
.ps-evidence{
display:grid;
gap:5px;
padding:9px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
background:var(--bg-surface-2);
color:var(--text-muted);
font-size:11px;
line-height:1.45;
}
.ps-evidence b{
color:var(--accent-deep);
font-family:var(--font-num);
}
.ps-review{
display:grid;
gap:8px;
padding:10px;
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:var(--bg-surface);
}
.ps-review > div:first-child{
display:flex;
align-items:center;
justify-content:space-between;
gap:8px;
}
.ps-review__actions{
display:grid;
grid-template-columns:repeat(3,minmax(0,1fr));
gap:6px;
}
.ps-review__actions .vg-btn{
width:100%;
}
.ps-empty{
display:grid;
gap:4px;
padding:14px;
border:1px dashed var(--border-subtle);
border-radius:var(--radius);
color:var(--text-muted);
background:var(--bg-surface-2);
}
.ps-empty b{
color:var(--text-strong);
}
@media (max-width:1180px){
.ps-layout{
grid-template-columns:minmax(210px,260px) minmax(0,1fr);
}
.ps-inspector{
position:static;
grid-column:1 / -1;
grid-template-columns:repeat(2,minmax(0,1fr));
}
.ps-source-grid{
grid-template-columns:1fr;
}
.ps-source-panel__actions{
display:grid;
}
}
@media (max-width:860px){
.ps-head{
display:grid;
}
.ps-head__actions{
justify-content:start;
}
.ps-layout{
grid-template-columns:1fr;
}
.ps-rail{
position:static;
}
.ps-inspector{
grid-template-columns:1fr;
}
.ps-form-grid,
.ps-number-grid{
grid-template-columns:1fr;
}
.ps-guidance,
.ps-upload-card{
grid-template-columns:1fr;
}
}

View file

@ -0,0 +1,914 @@
.pf-root{
max-width:1280px;
margin:0 auto;
display:flex;
flex-direction:column;
gap:16px;
}
.pf-head{
display:flex;
align-items:flex-end;
justify-content:space-between;
gap:var(--sp-4);
flex-wrap:wrap;
}
.pf-head__actions{
display:flex;
align-items:center;
justify-content:flex-end;
gap:8px;
flex-wrap:wrap;
}
.pf-head h1{
margin:6px 0 0;
color:var(--text-strong);
font-size:var(--fs-h2);
line-height:1.28;
letter-spacing:0;
}
.pf-head p{
margin:6px 0 0;
max-width:760px;
color:var(--text-body);
font-size:var(--fs-xs);
line-height:1.55;
}
.pf-signal-strip{
order:1;
display:grid;
grid-template-columns:1fr;
gap:0;
min-width:0;
}
.pf-triage{
display:grid;
grid-template-columns:auto minmax(0,1fr) auto;
align-items:center;
gap:12px;
min-width:0;
padding:14px 16px;
border:1px solid var(--hair);
border-radius:var(--radius);
background:var(--bg-surface);
box-shadow:var(--shadow-sm);
}
.pf-triage.is-active{
background:color-mix(in srgb,var(--accent-tint) 42%,var(--bg-surface));
}
.pf-triage__copy{
min-width:0;
}
.pf-triage__copy .vg-kicker{
margin-bottom:4px;
}
.pf-triage__copy b{
display:block;
color:var(--text-strong);
font-size:var(--fs-body);
line-height:1.35;
}
.pf-triage__copy span,
.pf-triage__meta small{
color:var(--text-muted);
font-size:var(--fs-xs);
}
.pf-triage__meta{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
gap:0;
min-width:180px;
border:1px solid var(--border-subtle);
border-radius:var(--radius);
background:color-mix(in srgb,var(--bg-surface) 86%,transparent);
overflow:hidden;
}
.pf-triage__meta span{
display:grid;
align-content:center;
gap:2px;
padding:9px 12px;
}
.pf-triage__meta span + span{
border-left:1px solid var(--hair);
}
.pf-triage__meta b{
color:var(--text-strong);
font-family:var(--font-num);
font-size:17px;
line-height:1;
}
.pf-error{
display:flex;
align-items:center;
gap:10px;
padding:12px 14px;
border-radius:var(--radius);
background:var(--crit-tint);
color:var(--crit-text);
font-size:var(--fs-sm);
}
.pf-kpis{
display:grid;
grid-template-columns:repeat(6,minmax(0,1fr));
border:1px solid var(--hair);
border-radius:var(--radius);
overflow:hidden;
background:var(--bg-surface);
box-shadow:var(--shadow-sm);
}
.pf-kpi{
position:relative;
min-width:0;
padding:14px 16px;
display:grid;
grid-template-columns:minmax(0,1fr) auto;
gap:4px 10px;
}
.pf-kpi:nth-child(n+2){border-left:1px solid var(--hair);}
.pf-kpi:nth-child(n+4){border-top:0;}
.pf-kpi--primary,
.pf-kpi--warn{
background:color-mix(in srgb,var(--accent-tint) 32%,var(--bg-surface));
}
.pf-kpi--warn{
background:color-mix(in srgb,var(--warn-tint) 36%,var(--bg-surface));
}
.pf-kpi--primary .pf-kpi__ic{
color:var(--text-on-accent);
background:var(--accent);
}
.pf-kpi--warn .pf-kpi__ic{
color:var(--warn-text);
background:var(--warn-tint);
}
.pf-kpi__ic{
grid-column:2;
grid-row:1 / span 3;
width:30px;
height:30px;
display:grid;
place-items:center;
border-radius:var(--radius);
color:var(--accent);
background:var(--accent-tint);
}
.pf-kpi__lab{
display:block;
color:var(--text-muted);
font-size:var(--fs-xs);
font-weight:700;
}
.pf-kpi b{
display:block;
color:var(--text-strong);
font-family:var(--font-num);
font-size:24px;
line-height:1;
}
.pf-kpi small{
color:var(--text-muted);
font-size:12px;
line-height:1.35;
}
.pf-workspace{
order:2;
display:grid;
grid-template-columns:minmax(320px,390px) minmax(0,1fr);
align-items:start;
gap:14px;
min-width:0;
}
.pf-queue-stack{
display:flex;
flex-direction:column;
gap:14px;
min-width:0;
}
.pf-section{
display:flex;
flex-direction:column;
gap:10px;
min-width:0;
}
.pf-section__head{
display:flex;
align-items:flex-end;
justify-content:space-between;
gap:12px;
}
.pf-section__head h2{
margin:4px 0 0;
color:var(--text-strong);
font-size:var(--fs-body);
font-weight:700;
line-height:1.3;
letter-spacing:0;
}
.pf-panel{
padding:0;
overflow:hidden;
}
.pf-studio-card{
display:grid;
grid-template-columns:minmax(0,1fr) auto;
gap:14px;
align-items:center;
padding:14px;
background:linear-gradient(180deg,var(--bg-surface),color-mix(in srgb,var(--accent-tint) 18%,var(--bg-surface)));
}
.pf-studio-card__copy{
min-width:0;
display:grid;
gap:7px;
}
.pf-studio-card__copy b{
color:var(--text-strong);
font-size:var(--fs-body);
}
.pf-studio-card__copy p{
margin:0;
color:var(--text-muted);
font-size:var(--fs-xs);
line-height:1.55;
}
.pf-studio-card__meta{
display:flex;
flex-wrap:wrap;
gap:6px;
}
.pf-studio-card__meta span{
padding:4px 7px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
color:var(--text-body);
background:var(--bg-surface-2);
font-size:11px;
line-height:1.2;
}
.pf-studio-card__actions{
display:flex;
justify-content:flex-end;
}
.pf-section--growth{
order:3;
min-width:0;
}
.pf-growth-panel{
background:linear-gradient(180deg,var(--bg-surface),color-mix(in srgb,var(--accent-tint) 18%,var(--bg-surface)));
}
.pf-growth-list{
max-height:min(420px,44vh);
overflow:auto;
scrollbar-gutter:stable;
display:grid;
grid-template-columns:repeat(3,minmax(0,1fr));
gap:12px;
padding:12px;
}
.pf-growth-card{
min-width:0;
display:flex;
flex-direction:column;
gap:12px;
padding:13px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
background:color-mix(in srgb,var(--bg-surface) 82%,var(--bg-surface-2));
box-shadow:none;
}
.pf-growth-card__top{
display:flex;
align-items:flex-start;
justify-content:space-between;
gap:10px;
min-width:0;
}
.pf-growth-card__id{
min-width:0;
display:grid;
gap:3px;
}
.pf-growth-card__id b{
color:var(--text-strong);
font-size:var(--fs-sm);
line-height:1.35;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-growth-card__id span,
.pf-growth-card__metrics small,
.pf-growth-point span,
.pf-recent__review-state{
color:var(--text-muted);
font-size:12px;
line-height:1.4;
}
.pf-recent__review-state{
display:block;
margin-top:3px;
line-height:1.2;
}
.pf-growth-card__metrics{
display:grid;
grid-template-columns:repeat(3,minmax(0,1fr));
gap:8px;
}
.pf-growth-card__metrics span{
min-width:0;
display:grid;
gap:3px;
padding:9px 10px;
border:1px solid var(--paper-2);
border-radius:var(--radius-sm);
background:var(--bg-surface-2);
}
.pf-growth-card__metrics b{
color:var(--text-strong);
font-family:var(--font-num);
font-size:15px;
line-height:1.15;
white-space:nowrap;
}
.pf-growth-bars{
height:82px;
display:flex;
align-items:flex-end;
gap:6px;
padding:8px 8px 6px;
border:1px solid var(--paper-2);
border-radius:var(--radius-sm);
background:color-mix(in srgb,var(--bg-surface-2) 82%,transparent);
}
.pf-growth-bar{
flex:1 1 0;
min-width:14px;
height:100%;
display:grid;
grid-template-rows:minmax(0,1fr) 14px;
gap:4px;
align-items:end;
}
.pf-growth-bar i{
display:block;
width:100%;
min-height:6px;
border-radius:6px 6px 3px 3px;
background:linear-gradient(180deg,var(--accent),var(--accent-deep));
}
.pf-growth-bar.is-empty i{
background:repeating-linear-gradient(135deg,var(--paper-2),var(--paper-2) 3px,var(--hair) 3px,var(--hair) 6px);
}
.pf-growth-bar small{
color:var(--text-muted);
font-family:var(--font-num);
font-size:10px;
text-align:center;
line-height:1;
}
.pf-growth-card__tags{
min-height:26px;
display:flex;
flex-wrap:wrap;
gap:6px;
align-content:flex-start;
}
.pf-growth-card__tags span{
max-width:100%;
padding:4px 7px;
border:1px solid var(--paper-2);
border-radius:999px;
color:var(--text-body);
background:var(--bg-surface-2);
font-size:11px;
line-height:1.2;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-growth-card__points{
display:grid;
gap:7px;
}
.pf-growth-point{
min-width:0;
display:grid;
grid-template-columns:minmax(86px,.5fr) minmax(0,1fr);
gap:8px;
align-items:center;
}
.pf-growth-point b{
color:var(--text-strong);
font-size:12px;
line-height:1.35;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-growth-point span{
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-empty{
min-height:118px;
display:grid;
place-items:center;
gap:6px;
padding:var(--sp-5);
text-align:center;
}
.pf-empty b{
color:var(--text-strong);
font-size:var(--fs-body);
}
.pf-empty span{
color:var(--text-muted);
font-size:var(--fs-xs);
}
.pf-list{
max-height:min(320px,42vh);
overflow:auto;
scrollbar-gutter:stable;
display:flex;
flex-direction:column;
}
.pf-personas{
max-height:min(320px,42vh);
overflow:auto;
scrollbar-gutter:stable;
display:flex;
flex-direction:column;
}
.pf-persona{
display:grid;
grid-template-columns:minmax(0,1fr);
gap:8px;
align-items:start;
padding:12px;
border-top:1px solid var(--paper-2);
}
.pf-persona:first-child{border-top:0;}
.pf-persona__main{
min-width:0;
display:grid;
grid-template-columns:40px minmax(0,1fr);
gap:10px;
align-items:center;
}
.pf-persona__code{
width:40px;
height:32px;
display:grid;
place-items:center;
border-radius:var(--radius);
color:var(--accent-deep);
background:var(--accent-tint);
font-family:var(--font-num);
font-weight:800;
font-size:12px;
}
.pf-persona__main b{
display:block;
color:var(--text-strong);
font-size:var(--fs-sm);
line-height:1.35;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-persona__main span,
.pf-persona p,
.pf-persona__meta span{
color:var(--text-muted);
font-size:var(--fs-xs);
line-height:1.45;
}
.pf-persona p{
margin:0;
min-width:0;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-persona__meta{
display:flex;
align-items:center;
gap:10px;
justify-content:space-between;
white-space:nowrap;
}
.pf-persona__actions{
display:flex;
justify-content:flex-end;
gap:8px;
min-width:0;
}
.pf-persona__actions .vg-btn{
min-width:72px;
padding-inline:10px;
}
.pf-alerts{
max-height:min(300px,38vh);
overflow:auto;
scrollbar-gutter:stable;
display:flex;
flex-direction:column;
}
.pf-alert{
display:grid;
grid-template-columns:auto minmax(0,1fr) auto;
gap:10px;
align-items:center;
padding:12px;
border-top:1px solid var(--paper-2);
background:color-mix(in srgb,var(--warn-tint) 34%,transparent);
}
.pf-alert:first-child{border-top:0;}
.pf-alert__ic{
width:30px;
height:30px;
display:grid;
place-items:center;
border-radius:var(--radius);
color:var(--warn-text);
background:var(--warn-tint);
}
.pf-alert__main{
min-width:0;
display:grid;
gap:3px;
}
.pf-alert__main b{
color:var(--text-strong);
font-size:var(--fs-sm);
}
.pf-alert__main span,
.pf-alert__main code,
.pf-alert__resource span{
color:var(--text-muted);
font-size:12px;
}
.pf-alert__main code{
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.pf-alert__resource{
display:grid;
gap:2px;
justify-items:end;
min-width:86px;
}
.pf-alert__resource b{
color:var(--warn-text);
font-family:var(--font-num);
font-size:18px;
}
.pf-session{
width:100%;
font:inherit;
text-align:left;
background:transparent;
color:inherit;
display:grid;
grid-template-columns:8px minmax(0,1fr) auto;
gap:8px 10px;
align-items:start;
padding:12px;
border:0;
border-top:1px solid var(--paper-2);
cursor:default;
}
.pf-session:first-child{border-top:0;}
.pf-session--action{
cursor:pointer;
}
.pf-session--action:hover{
background:var(--bg-surface-2);
}
.pf-session--action:focus-visible{
outline:2px solid var(--accent);
outline-offset:-2px;
}
.pf-session__dot{
width:8px;
height:8px;
border-radius:50%;
background:var(--accent);
}
.pf-session__main{
min-width:0;
display:flex;
flex-direction:column;
gap:3px;
}
.pf-session__main b{
color:var(--text-strong);
font-size:var(--fs-sm);
}
.pf-session__main span,.pf-session__main code{
color:var(--text-muted);
font-size:var(--fs-xs);
overflow-wrap:anywhere;
}
.pf-session__main code,.pf-recent__learner code{
font-family:var(--font-num);
overflow:hidden;
text-overflow:ellipsis;
}
.pf-session__meta{
grid-column:2 / 4;
display:flex;
align-items:center;
justify-content:space-between;
gap:8px;
color:var(--text-muted);
font-family:var(--font-num);
font-size:var(--fs-xs);
white-space:normal;
}
.pf-session__open{
grid-column:3;
grid-row:1;
display:inline-flex;
align-items:center;
gap:5px;
align-self:center;
justify-self:end;
min-height:30px;
padding:0 9px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
color:var(--accent-deep);
background:var(--accent-tint);
font-size:var(--fs-xs);
font-weight:760;
white-space:nowrap;
}
.pf-session--action:hover .pf-session__open{
border-color:var(--accent);
}
.pf-recent-list{
max-height:min(620px,calc(100vh - 220px));
overflow:auto;
scrollbar-gutter:stable;
min-width:0;
}
.pf-recent-head,
.pf-recent-row{
display:grid;
grid-template-columns:
minmax(128px,1.25fr)
minmax(58px,.55fr)
minmax(66px,.55fr)
minmax(82px,.85fr)
minmax(32px,.35fr)
minmax(70px,.6fr)
minmax(66px,.55fr)
minmax(86px,.65fr);
align-items:center;
gap:8px;
min-width:0;
}
.pf-recent-head{
position:sticky;
top:0;
z-index:1;
color:var(--text-muted);
font-size:var(--fs-xs);
font-weight:700;
padding:9px 12px;
border-bottom:1px solid var(--hair);
background:var(--bg-surface);
white-space:nowrap;
}
.pf-recent-row{
width:100%;
font:inherit;
text-align:left;
color:inherit;
background:transparent;
padding:10px 12px;
border:0;
border-top:1px solid var(--paper-2);
}
.pf-recent-row--action{
cursor:pointer;
}
.pf-recent-row--action:hover{
background:var(--bg-surface-2);
}
.pf-recent-row--action:focus-visible{
outline:2px solid var(--accent);
outline-offset:-2px;
}
.pf-recent-head + .pf-recent-row{
border-top:0;
}
.pf-recent__learner,
.pf-recent__cell{
min-width:0;
color:var(--text-body);
font-size:var(--fs-sm);
overflow-wrap:anywhere;
}
.pf-recent__cell::before{
display:none;
}
.pf-recent__learner{
min-width:0;
display:flex;
flex-direction:column;
gap:3px;
}
.pf-recent__learner b{
color:var(--text-strong);
font-size:var(--fs-sm);
line-height:1.35;
overflow-wrap:anywhere;
}
.pf-recent__learner code{
max-width:100%;
color:var(--text-muted);
font-size:11px;
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
.pf-recent__cell .vg-badge{
justify-self:start;
}
.pf-recent__cell--open{
display:flex;
justify-content:flex-end;
}
.pf-recent__open{
display:inline-flex;
align-items:center;
justify-content:center;
gap:4px;
min-height:30px;
padding:0 7px;
border:1px solid var(--hair);
border-radius:var(--radius-sm);
color:var(--accent-deep);
background:var(--accent-tint);
font-size:var(--fs-xs);
font-weight:760;
white-space:nowrap;
}
.pf-recent-row--action:hover .pf-recent__open{
border-color:var(--accent);
}
@media (max-width:1100px){
.pf-signal-strip,
.pf-workspace{
grid-template-columns:1fr;
}
.pf-triage{
grid-template-columns:auto minmax(0,max-content) auto;
justify-content:start;
}
.pf-kpis{
grid-template-columns:repeat(4,minmax(0,1fr));
}
.pf-kpi:nth-child(n+2){border-left:1px solid var(--hair);}
.pf-kpi:nth-child(n+4){border-top:0;}
.pf-growth-list{
grid-template-columns:repeat(2,minmax(0,1fr));
}
.pf-list,
.pf-personas{
max-height:360px;
}
.pf-recent-list{
max-height:460px;
}
}
@media (max-width:860px){
.pf-head__actions{
width:100%;
justify-content:flex-start;
}
.pf-triage{
grid-template-columns:auto minmax(0,1fr);
}
.pf-triage__meta{
grid-column:1 / -1;
width:100%;
}
.pf-kpis{grid-template-columns:repeat(2,minmax(0,1fr));}
.pf-kpi:nth-child(n+2){border-left:0;}
.pf-kpi:nth-child(even){border-left:1px solid var(--hair);}
.pf-kpi:nth-child(n+3){border-top:1px solid var(--hair);}
.pf-growth-list{
grid-template-columns:1fr;
}
.pf-recent-list{
display:flex;
flex-direction:column;
gap:10px;
padding:10px;
background:var(--bg-surface-2);
overflow-x:hidden;
}
.pf-recent-head{
display:none;
}
.pf-recent-row{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
gap:10px 12px;
padding:12px;
border:1px solid var(--paper-2);
border-radius:var(--radius);
background:var(--bg-surface);
}
.pf-recent__learner{
grid-column:1 / -1;
padding-bottom:10px;
border-bottom:1px solid var(--paper-2);
}
.pf-recent__learner code{
white-space:normal;
overflow-wrap:anywhere;
}
.pf-recent__cell{
display:grid;
grid-template-columns:minmax(74px,.4fr) minmax(0,1fr);
gap:8px;
align-items:center;
}
.pf-recent__cell--open{
justify-content:stretch;
}
.pf-recent__cell::before{
display:block;
content:attr(data-label);
color:var(--text-muted);
font-size:var(--fs-xs);
font-weight:700;
line-height:1.35;
}
.pf-session{
grid-template-columns:8px minmax(0,1fr);
}
.pf-session__meta{
grid-column:2;
justify-content:flex-start;
}
.pf-session__open{
grid-column:2;
grid-row:auto;
justify-self:start;
}
}
@media (max-width:520px){
.pf-kpis{grid-template-columns:repeat(2,minmax(0,1fr));}
.pf-kpi,
.pf-kpi + .pf-kpi{border-left:0;}
.pf-kpi:nth-child(even){border-left:1px solid var(--hair);}
.pf-kpi:nth-child(n+3){border-top:1px solid var(--hair);}
.pf-persona__actions{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
}
.pf-persona__actions .vg-btn{
width:100%;
}
.pf-growth-list{
padding:10px;
}
.pf-growth-card__metrics{
grid-template-columns:1fr;
}
.pf-growth-point{
grid-template-columns:1fr;
gap:2px;
}
.pf-growth-point b,
.pf-growth-point span{
white-space:normal;
}
.pf-alert{
grid-template-columns:auto minmax(0,1fr);
}
.pf-alert__resource{
grid-column:2;
justify-items:start;
}
.pf-recent-list{
padding:8px;
}
.pf-recent-row{
grid-template-columns:1fr;
gap:10px;
}
.pf-recent__cell{
grid-template-columns:minmax(64px,.32fr) minmax(0,1fr);
}
.pf-recent__cell--open .pf-recent__open{
justify-self:start;
}
}