import { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { AppShell } from "../components/shell/AppShell"; import { Button, Icon, Kicker } from "../components/ui"; import { personaApi, sessionApi, type LearnerSessionSummary, type PersonaSummary, } from "../lib/api"; type LoadState = "loading" | "ready" | "error"; const DIFFICULTY_LABEL: Record = { easy: "기초", moderate: "중간", hard: "고난도", }; function firstText(value: unknown): string | null { if (typeof value !== "string") return null; const text = value.trim(); return text || null; } function shortName(displayName: string): string { return displayName.split("·")[0]?.replace(/\(.*?\)/g, "").trim() || displayName.trim(); } function demographicsLine(persona: PersonaSummary | null): string { if (!persona) return "내담자 정보를 불러오는 중"; const parts = [ firstText(persona.demographics.age_band), firstText(persona.demographics.grade), firstText(persona.demographics.job), firstText(persona.demographics.status), ].filter(Boolean); return parts.length ? parts.join(" · ") : "인적 정보 없음"; } function theoryLine(persona: PersonaSummary | null): string { if (!persona) return "-"; return persona.theory_target.length ? persona.theory_target.join(" · ") : "공통"; } function difficultyLine(persona: PersonaSummary | null): string { if (!persona) return "-"; return DIFFICULTY_LABEL[persona.difficulty] ?? persona.difficulty; } function personaInitial(persona: PersonaSummary | null): string { const name = persona ? shortName(persona.display_name) : ""; return (name || persona?.code || "V").slice(0, 1); } function isUsablePersona(persona: PersonaSummary): boolean { return !persona.degraded && persona.source === "database"; } function unavailablePersonaMessage(persona: PersonaSummary): string { if (persona.degraded) { return "카탈로그 원본을 확인하지 못해 현재 연습에 사용할 수 없습니다."; } return "데이터베이스에서 확인된 내담자가 아니어서 현재 연습에 사용할 수 없습니다."; } function sessionStatusLabel(session: LearnerSessionSummary): string { if (session.status === "active") return "진행 중"; if (session.review_ready) return "리뷰 가능"; return "종료"; } function sessionDateLabel(value: string): string { const date = new Date(value); if (Number.isNaN(date.getTime())) return ""; return new Intl.DateTimeFormat("ko-KR", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }).format(date); } export default function LearnerHome() { const navigate = useNavigate(); const [personas, setPersonas] = useState([]); const [sessions, setSessions] = useState([]); const [selectedCode, setSelectedCode] = useState(""); const [loadState, setLoadState] = useState("loading"); const [sessionLoadState, setSessionLoadState] = useState("loading"); useEffect(() => { let alive = true; (async () => { setLoadState("loading"); setSessionLoadState("loading"); const [personaResult, sessionResult] = await Promise.allSettled([ personaApi.list(), sessionApi.list(), ]); if (!alive) return; if (personaResult.status === "fulfilled") { const personaRows = personaResult.value; const firstUsableCode = personaRows.find(isUsablePersona)?.code ?? ""; setPersonas(personaRows); setSelectedCode((cur) => personaRows.some((persona) => persona.code === cur && isUsablePersona(persona)) ? cur : firstUsableCode, ); setLoadState("ready"); } else { console.warn("[learner-home] failed to load personas", personaResult.reason); setPersonas([]); setLoadState("error"); } if (sessionResult.status === "fulfilled") { setSessions(sessionResult.value.sessions); setSessionLoadState("ready"); } else { console.warn("[learner-home] failed to load sessions", sessionResult.reason); setSessions([]); setSessionLoadState("error"); } })(); return () => { alive = false; }; }, []); const usablePersonas = useMemo(() => personas.filter(isUsablePersona), [personas]); const selected = useMemo( () => usablePersonas.find((p) => p.code === selectedCode) ?? usablePersonas[0] ?? null, [selectedCode, usablePersonas], ); const selectedSessions = useMemo( () => sessions .filter((session) => session.persona_code === selected?.code) .sort((a, b) => Date.parse(b.started_at) - Date.parse(a.started_at)) .slice(0, 4), [selected?.code, sessions], ); const activeCount = useMemo( () => sessions.filter((session) => session.status === "active").length, [sessions], ); const reviewCount = useMemo( () => sessions.filter((session) => session.status === "ended" && session.review_ready).length, [sessions], ); const startPractice = () => { if (selected && isUsablePersona(selected)) navigate(`/learn/session/${selected.code}`); }; const historyReady = sessionLoadState === "ready"; const historyStatus = sessionLoadState === "ready" ? `${sessions.length}회` : sessionLoadState === "error" ? "기록 오류" : "기록 확인 중"; const personaCatalogUnavailable = loadState === "ready" && !selected; const hasSessionRecords = sessionLoadState === "ready" && sessions.length > 0; const rootClassName = [ "lh-root", hasSessionRecords ? "has-session-records" : "", personaCatalogUnavailable ? "has-catalog-unavailable" : "", ] .filter(Boolean) .join(" "); return (
상담 연습

오늘의 회기를 준비합니다.

내담자를 고르고, 진행 중 회기는 이어가고, 끝난 회기는 리뷰에서 다시 확인합니다.

{loadState === "ready" ? `${usablePersonas.length}명 · ${historyStatus}` : "확인 중"}
연습 대상 {loadState === "ready" ? "내담자 목록" : "확인 중"}
{loadState === "error" ? (
내담자 목록을 불러오지 못했습니다.

잠시 뒤 다시 시도해 주세요.

) : (
{loadState === "loading" ? Array.from({ length: 3 }).map((_, i) => ( )) : personas.map((persona) => { const usablePersona = isUsablePersona(persona); const selectedPersona = usablePersona && persona.code === selected?.code; const personaMeta = (DIFFICULTY_LABEL[persona.difficulty] ?? persona.difficulty) + " · " + theoryLine(persona); return ( ); })}
)}
{personaCatalogUnavailable ? "catalog" : (selected?.code ?? "...")}

{personaCatalogUnavailable ? "사용 가능한 내담자가 없습니다." : (selected?.display_name ?? "내담자 정보를 불러오는 중")}

{personaCatalogUnavailable ? "데이터베이스에서 확인된 내담자만 연습에 사용할 수 있습니다." : demographicsLine(selected)}

회기 종료 후 저장된 대화 기록으로 리뷰가 생성됩니다.
{personaCatalogUnavailable ? "카탈로그 상태" : "주호소"}

{personaCatalogUnavailable ? "현재 서버 카탈로그가 신뢰 가능한 상태로 확인되지 않아 연습 시작을 막았습니다. 카탈로그가 복구되면 선택할 수 있습니다." : (selected?.presenting_summary ?? "내담자 정보를 확인하고 있습니다.")}

난도
{difficultyLine(selected)}
접근
{theoryLine(selected)}
음성
{selected?.voice_preset ?? "기본"}
{historyReady ? activeCount : "–"} 진행 중 {historyReady ? reviewCount : "–"} 리뷰 가능 {historyReady ? sessions.length : "–"} 누적 회기
기존 회기 {selected ? `${selected.code} · 전체 ${historyReady ? sessions.length : "–"}회` : "-"}
{sessionLoadState === "loading" ? (

저장된 연습 기록을 확인하고 있습니다.

) : sessionLoadState === "error" ? (
연습 기록을 불러오지 못해 횟수를 표시하지 않습니다.
) : personaCatalogUnavailable ? (
사용 가능한 내담자가 없어 기존 회기를 선택할 수 없습니다.
) : selectedSessions.length > 0 ? (
    {selectedSessions.map((session) => (
  • {sessionStatusLabel(session)} {sessionDateLabel(session.started_at)} {session.turn_count}턴 · {session.stage} {session.status === "active" ? ( ) : ( )}
  • ))}
) : (
저장된 기존 회기가 없습니다.

첫 회기를 마치면 실제 저장 기록이 이곳에 쌓입니다.

)}
); } const LH_CSS = ` .lh-root{ width:min(100%,1480px); min-height:calc(100dvh - var(--topbar-h)); margin:0 auto; position:relative; isolation:isolate; display:grid; grid-template-rows:auto minmax(0,1fr); align-content:start; gap:clamp(12px,1.6vw,20px); padding:clamp(14px,2vw,28px); background:var(--bg-app); overflow:hidden; } .lh-root::before{ content:""; position:absolute; z-index:-1; inset:-120px 0 auto auto; width:min(720px,58vw); height:520px; background:var(--asset-warm-elements) center / cover no-repeat; opacity:.07; filter:saturate(.8); pointer-events:none; } .lh-head{ display:flex; align-items:center; justify-content:space-between; gap:var(--sp-4); min-width:0; } .lh-head__copy{min-width:0;} .lh-head__status{ flex:none; min-height:34px; display:inline-flex; align-items:center; gap:8px; padding:0 12px; border:1px solid var(--border-subtle); border-radius:var(--radius); background:var(--bg-surface); color:var(--text-body); font-family:var(--font-num); font-size:var(--fs-xs); font-weight:750; } .lh-title{ margin:5px 0 0; font-size:clamp(22px,2.4vw,30px); font-weight:740; letter-spacing:0; line-height:1.18; color:var(--text-strong); } .lh-sub{ margin:6px 0 0; max-width:760px; color:var(--text-body); font-size:var(--fs-sm); line-height:1.45; } .lh-workspace{ min-width:0; display:grid; grid-template-columns:minmax(300px,360px) minmax(0,1fr); gap:clamp(14px,1.8vw,24px); align-items:start; } .lh-list-pane, .lh-preview{ min-width:0; } .lh-list-pane{ position:sticky; top:calc(var(--topbar-h) + 18px); max-height:calc(100dvh - var(--topbar-h) - 36px); display:grid; grid-template-rows:auto minmax(0,1fr); padding:var(--sp-4); border:1px solid var(--border-subtle); border-radius:var(--radius); background:var(--bg-surface); box-shadow:var(--shadow-sm); overflow:hidden; } .lh-list-pane::after{ content:""; position:absolute; inset:auto -48px -68px auto; width:180px; height:180px; background:var(--asset-warm-elements) center / cover no-repeat; opacity:.05; pointer-events:none; } .lh-pane-head{ display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3); margin-bottom:var(--sp-3); } .lh-pane-head span{ font-size:var(--fs-xs); font-weight:700; color:var(--text-muted); font-family:var(--font-num); white-space:nowrap; } .lh-personas{ min-height:0; display:grid; grid-auto-rows:auto; align-content:start; gap:8px; overflow:auto; scrollbar-gutter:stable; padding-right:3px; } .lh-persona{ position:relative; min-width:0; min-height:92px; display:grid; grid-template-columns:44px minmax(0,1fr); gap:11px; align-items:start; padding:12px 38px 12px 12px; border:1px solid var(--border-subtle); border-radius:var(--radius); background:var(--bg-surface-2); color:var(--text-body); text-align:left; cursor:pointer; transition:background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); } .lh-persona:hover{border-color:var(--border-strong);} .lh-persona:disabled{ cursor:not-allowed; opacity:.68; } .lh-persona.is-selected{ border-color:var(--accent); background:var(--accent-tint); box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 12%,transparent); } .lh-persona__mark{ width:40px; height:40px; display:grid; place-items:center; color:var(--text-strong); background:var(--bg-surface); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-num); font-size:12px; font-weight:800; } .lh-persona__body{ min-width:0; display:grid; gap:5px; } .lh-persona__name{ color:var(--text-strong); font-size:14.5px; font-weight:760; line-height:1.25; overflow:hidden; display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; } .lh-persona__meta{ color:var(--text-muted); font-size:12px; line-height:1.45; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .lh-persona__summary{ min-width:0; color:var(--text-body); font-size:12.5px; line-height:1.45; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; } .lh-persona__check{ position:absolute; top:10px; right:10px; width:22px; height:22px; display:grid; place-items:center; border-radius:50%; background:var(--accent); color:var(--text-on-accent); } .lh-persona.is-skel{ pointer-events:none; } /* 실제 카드 구조(마크 + 이름/메타/요약 2줄)를 모사한 플레이스홀더 셰이머 */ .lh-skel__box, .lh-skel__line{ background:linear-gradient(90deg,var(--bg-surface-2),var(--neutral-100),var(--bg-surface-2)); background-size:180% 100%; animation:lhSkel 1.1s ease-in-out infinite; } .lh-skel__line{ height:11px; border-radius:6px; align-self:center; } .lh-skel__line--name{width:62%;height:13px;} .lh-skel__line--meta{width:44%;height:10px;} .lh-skel__line--sum{width:92%;} .lh-skel__line--sum2{width:76%;} .lh-preview{ display:grid; grid-template-columns:minmax(0,1fr); gap:clamp(14px,1.8vw,24px); align-content:start; align-items:start; } .lh-preview__main{ min-width:0; position:relative; display:grid; gap:var(--sp-4); align-content:start; padding:var(--sp-5); border:1px solid var(--border-subtle); border-radius:var(--radius); background:var(--bg-surface); box-shadow:var(--shadow-sm); overflow:hidden; } .lh-preview__main::after{ content:""; position:absolute; inset:auto -180px -210px auto; width:520px; height:360px; background:var(--asset-warm-elements) center / cover no-repeat; opacity:.08; filter:saturate(.82); pointer-events:none; } .lh-preview__main > *{ position:relative; z-index:1; } .lh-preview__hero{ min-width:0; display:grid; grid-template-columns:minmax(0,1fr) minmax(220px,auto); gap:var(--sp-4); align-items:start; } @media (min-width:1400px) and (max-width:1559px){ .lh-preview__hero{ grid-template-columns:minmax(0,1fr); } .lh-actions{ justify-items:start; } .lh-actions__note{ text-align:left; } } .lh-preview__identity{ min-width:0; display:grid; grid-template-columns:auto minmax(0,1fr); align-items:start; gap:var(--sp-4); } .lh-avatar{ width:64px; height:64px; display:grid; place-items:center; border-radius:50%; background:var(--clay-tint); color:var(--clay-deep); font-size:26px; font-weight:780; box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--clay) 22%,transparent); } .lh-preview__copy{min-width:0;} .lh-preview__code{ display:inline-flex; align-items:center; min-height:24px; padding:0 9px; border-radius:var(--radius-sm); background:var(--accent-tint); color:var(--accent-deep); font-family:var(--font-num); font-size:12px; font-weight:800; } .lh-preview h2{ margin:8px 0 0; color:var(--text-strong); font-size:clamp(22px,2.4vw,30px); line-height:1.2; letter-spacing:0; word-break:keep-all; overflow-wrap:anywhere; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; } .lh-preview__copy p{ margin:7px 0 0; color:var(--text-muted); font-size:var(--fs-sm); line-height:1.45; word-break:keep-all; overflow-wrap:anywhere; } .lh-summary{ min-width:0; padding:var(--sp-4); border-radius:var(--radius); background: linear-gradient(180deg,color-mix(in srgb,var(--clay-tint) 48%,var(--bg-surface-2)),var(--bg-surface-2)); } .lh-summary p{ margin:8px 0 0; color:var(--text-body); font-size:clamp(15px,1.2vw,18px); line-height:1.52; overflow:hidden; display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; } .lh-facts{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; margin:0; } .lh-facts div{ min-width:0; min-height:64px; padding:12px; border:1px solid var(--border-subtle); border-radius:var(--radius); background:var(--bg-surface); } .lh-facts dt{ margin:0; color:var(--text-muted); font-size:12px; font-weight:720; } .lh-facts dd{ margin:7px 0 0; color:var(--text-strong); font-size:14px; font-weight:720; line-height:1.3; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .lh-activity{ display:grid; gap:var(--sp-4); padding:var(--sp-5); border:1px solid var(--border-subtle); border-radius:var(--radius); background:var(--bg-surface); box-shadow:var(--shadow-sm); } .lh-activity__stats{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:8px; } .lh-activity__stats span, .lh-activity__recent{ min-width:0; } .lh-activity__stats span{ min-height:58px; display:grid; align-content:center; gap:4px; padding:10px; border:1px solid var(--border-subtle); border-radius:var(--radius); background:var(--bg-surface-2); } .lh-activity__stats b{ color:var(--text-strong); font-family:var(--font-num); font-size:22px; line-height:1; } .lh-activity__stats small{ color:var(--text-muted); font-size:12px; font-weight:720; } .lh-activity__recent{ min-height:0; } .lh-activity__head{ display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3); margin-bottom:10px; } .lh-activity__head span{ color:var(--text-muted); font-family:var(--font-num); font-size:12px; font-weight:800; white-space:nowrap; } .lh-activity__recent ul{ list-style:none; margin:0; padding:0; display:grid; gap:8px; } .lh-activity__recent li{ min-width:0; display:grid; grid-template-columns:minmax(0,1fr) auto; gap:6px 10px; align-items:center; padding:10px 0; border-top:1px solid var(--hair); } .lh-activity__recent li:first-child{border-top:0;padding-top:0;} .lh-activity__recent li > span:first-child{ min-width:0; display:grid; gap:2px; } .lh-activity__recent b{ color:var(--text-strong); font-size:13px; } .lh-activity__recent small, .lh-activity__recent p{ color:var(--text-muted); font-size:12px; } .lh-activity__recent p{margin:0;line-height:1.45;} .lh-activity__recent em{ grid-column:1; color:var(--text-body); font-family:var(--font-num); font-size:12px; font-style:normal; } .lh-session-actions{ grid-column:2; grid-row:1 / span 2; display:flex; align-items:center; justify-content:flex-end; gap:6px; } .lh-activity__unavailable{ display:flex; align-items:center; gap:8px; color:var(--warn-text); font-size:12px; line-height:1.45; } .lh-activity__empty{ display:grid; grid-template-columns:auto minmax(0,1fr); gap:10px; align-items:start; padding:12px; border:1px solid var(--border-subtle); border-radius:var(--radius); background:var(--bg-surface-2); color:var(--text-body); } .lh-activity__empty svg{ margin-top:2px; color:var(--accent-deep); } .lh-activity__empty b{ display:block; color:var(--text-strong); font-size:13px; } .lh-activity__empty p{ margin:4px 0 0; color:var(--text-muted); font-size:12px; line-height:1.45; } .lh-review-link{ flex:none; border:1px solid var(--border-subtle); border-radius:var(--radius-sm); background:var(--bg-surface); color:var(--accent-deep); font-family:var(--font-sans); font-size:12px; font-weight:720; min-height:30px; padding:5px 9px; cursor:pointer; white-space:nowrap; } .lh-review-link:hover{background:var(--accent-tint);} .lh-review-link--ghost{ color:var(--text-body); } .lh-review-link--ghost:hover{color:var(--accent-deep);} .lh-actions{ min-width:0; display:grid; justify-items:end; gap:10px; } .lh-actions__note{ display:block; min-width:0; max-width:260px; color:var(--text-muted); font-size:12.5px; line-height:1.45; text-align:right; word-break:keep-all; text-wrap:balance; } .lh-error{ display:flex; align-items:flex-start; gap:var(--sp-3); color:var(--crit-text); background:var(--crit-tint); border-radius:var(--radius); padding:var(--sp-4); } .lh-error b{display:block;} .lh-error p{margin:4px 0 0;font-size:var(--fs-sm);line-height:1.5;} @keyframes lhSkel{0%{background-position:120% 0}100%{background-position:-80% 0}} @media (min-width:1400px){ .lh-preview{ grid-template-columns:minmax(440px,1fr) minmax(340px,400px); column-gap:var(--sp-5); } .lh-activity{ position:sticky; top:calc(var(--topbar-h) + 18px); } .lh-activity__stats{ grid-template-columns:1fr; } } @media (max-width:1080px){ .lh-workspace{ grid-template-columns:1fr; } .lh-list-pane{ position:relative; top:auto; max-height:none; overflow:visible; } .lh-root::before{ width:560px; height:420px; opacity:.055; } .lh-personas{ grid-template-columns:repeat(2,minmax(0,1fr)); grid-auto-rows:auto; overflow:visible; padding-right:0; } .lh-preview{ grid-template-columns:1fr; } .lh-activity__stats{ grid-template-columns:repeat(3,minmax(0,1fr)); } } @media (max-width:760px){ .lh-root{ padding:12px; gap:12px; } .lh-root::before, .lh-preview__main::after, .lh-list-pane::after{ display:none; } .lh-head{ align-items:flex-start; } .lh-workspace{ display:flex; flex-direction:column; gap:10px; } .lh-list-pane{ padding:12px; } .lh-pane-head{margin-bottom:10px;} .lh-personas{ display:flex; grid-template-columns:none; gap:8px; overflow-x:auto; overflow-y:hidden; padding-right:0; padding-bottom:2px; scrollbar-gutter:auto; } .lh-persona{ flex:0 0 min(82vw,320px); min-height:64px; grid-template-columns:38px minmax(0,1fr); align-items:center; gap:10px; padding:10px 38px 10px 10px; } .lh-persona__mark{width:34px;height:34px;} .lh-persona__name{font-size:13.5px;-webkit-line-clamp:1;} .lh-persona__meta{font-size:11.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;} .lh-persona__summary{display:none;} .lh-persona__check{top:50%;right:10px;transform:translateY(-50%);} .lh-preview{ gap:12px; } .lh-preview__main, .lh-activity{ padding:14px; } .lh-preview__hero{ grid-template-columns:1fr; gap:12px; } .lh-preview__identity{grid-template-columns:54px minmax(0,1fr);gap:12px;} .lh-avatar{width:54px;height:54px;font-size:22px;} .lh-preview h2{margin-top:6px;font-size:20px;-webkit-line-clamp:2;} .lh-preview__copy p{margin-top:5px;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;} .lh-actions{ justify-items:stretch; } .lh-actions__note{ max-width:none; text-align:left; } .lh-actions .vg-btn{ width:100%; justify-content:center; } .lh-summary{padding:12px;} .lh-summary p{margin-top:6px;font-size:14px;line-height:1.45;-webkit-line-clamp:2;} .lh-facts{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;} .lh-facts div{min-height:58px;padding:10px;} .lh-facts dt{font-size:11px;} .lh-facts dd{font-size:12px;} .lh-activity{gap:12px;} .lh-activity__stats{grid-template-columns:repeat(3,minmax(0,1fr));} .lh-activity__stats span{ min-height:46px; display:grid; place-items:center; gap:2px; padding:8px; } .lh-activity__stats b{font-size:17px;} .lh-activity__stats small{font-size:11px;} .lh-session-actions{gap:4px;} .lh-review-link{padding:4px 7px;} } @media (max-width:560px){ .lh-head__status{display:none;} .lh-head .vg-kicker, .lh-pane-head .vg-kicker, .lh-summary .vg-kicker, .lh-activity__head .vg-kicker{ font-size:11px; gap:6px; } .lh-title{margin-top:3px;font-size:20px;line-height:1.18;} .lh-sub{ margin-top:4px; font-size:12.5px; line-height:1.35; } .lh-head{gap:6px;} .lh-pane-head{margin-bottom:5px;} .lh-persona{ flex:0 0 min(78vw,300px); min-height:56px; width:min(78vw,300px); grid-template-columns:30px minmax(0,1fr); padding:7px 34px 7px 7px; } .lh-persona__mark{width:28px;height:28px;font-size:11px;} .lh-persona__name{font-size:13px;} .lh-persona__meta{font-size:11px;} .lh-preview{gap:10px;} .lh-preview__main{gap:10px;} .lh-preview__identity{grid-template-columns:38px minmax(0,1fr);gap:9px;} .lh-avatar{width:38px;height:38px;font-size:17px;} .lh-preview h2{margin-top:4px;font-size:17px;line-height:1.2;} .lh-preview__code{min-height:20px;font-size:10.5px;padding:0 8px;} .lh-preview__copy p{margin-top:4px;font-size:11.5px;} .lh-actions__note{display:none;} .lh-summary{padding:10px;} .lh-summary p{font-size:12.5px;line-height:1.38;-webkit-line-clamp:2;} .lh-facts{grid-template-columns:1fr;gap:6px;} .lh-facts div{ min-height:0; display:flex; align-items:center; justify-content:space-between; gap:6px; padding:8px 10px; } .lh-facts dt{font-size:10.5px;} .lh-facts dd{ margin-top:0; font-size:11.5px; white-space:nowrap; overflow:hidden; display:block; text-overflow:ellipsis; } .lh-activity{padding:12px;gap:10px;} .lh-activity__stats{gap:6px;} .lh-activity__stats span{ min-height:40px; padding:6px 4px; } .lh-activity__stats b{font-size:16px;} .lh-activity__stats small{font-size:10.5px;} .lh-activity__head{margin-bottom:6px;} .lh-activity__empty{ padding:8px; gap:8px; } .lh-activity__empty b{font-size:12px;} .lh-activity__empty p{ margin-top:2px; font-size:11px; line-height:1.35; overflow:hidden; display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; } .lh-activity__recent li{ display:grid; grid-template-columns:minmax(0,1fr) auto; gap:6px 10px; } .lh-activity__recent li em{display:none;} .lh-session-actions{grid-column:2;grid-row:1 / span 2;flex-direction:column;align-items:stretch;} .lh-actions .vg-btn{ min-height:40px; font-size:14px; padding:0 12px; } } @media (prefers-reduced-motion:reduce){ .lh-skel__box, .lh-skel__line{animation:none;} } `;