From 627df884dd87e091f087944b36927eea57e3b04c Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Sat, 15 Aug 2026 21:20:47 +0900 Subject: [PATCH] feat: port TechLog shells and styles --- src/bootstrap/main.tsx | 6 + .../tech-log/adapters/static/content.ts | 9 +- .../tech-log/contracts/public-site-config.ts | 8 + .../public/components/fatal-error-state.tsx | 37 + .../public/components/search-dialog.tsx | 119 + .../public/components/site-header.tsx | 88 + .../presentation/public/public-shell.tsx | 38 + .../tech-log/presentation/styles/globals.css | 2590 +++++++++++++++++ .../styles/publication-flow.module.css | 533 ++++ .../presentation/styles/studio-editor.css | 92 + .../tech-log/presentation/styles/studio.css | 155 + .../presentation/styles/workflow.module.css | 88 + tests/features/tech-log/public-shell.test.tsx | 227 ++ .../features/tech-log/style-contract.test.ts | 142 + 14 files changed, 4124 insertions(+), 8 deletions(-) create mode 100644 src/features/tech-log/contracts/public-site-config.ts create mode 100644 src/features/tech-log/presentation/public/components/fatal-error-state.tsx create mode 100644 src/features/tech-log/presentation/public/components/search-dialog.tsx create mode 100644 src/features/tech-log/presentation/public/components/site-header.tsx create mode 100644 src/features/tech-log/presentation/public/public-shell.tsx create mode 100644 src/features/tech-log/presentation/styles/globals.css create mode 100644 src/features/tech-log/presentation/styles/publication-flow.module.css create mode 100644 src/features/tech-log/presentation/styles/studio-editor.css create mode 100644 src/features/tech-log/presentation/styles/studio.css create mode 100644 src/features/tech-log/presentation/styles/workflow.module.css create mode 100644 tests/features/tech-log/public-shell.test.tsx create mode 100644 tests/features/tech-log/style-contract.test.ts diff --git a/src/bootstrap/main.tsx b/src/bootstrap/main.tsx index bc5509e..55ef0ea 100644 --- a/src/bootstrap/main.tsx +++ b/src/bootstrap/main.tsx @@ -3,6 +3,12 @@ import { createRoot } from "react-dom/client"; import { recordBootFailure } from "../adapters/diagnostics/bounded-diagnostics.ts"; import { BootErrorShell } from "../presentation/boundaries/boot-error-shell.tsx"; import "../presentation/styles/theme.css"; +import "pretendard/dist/web/variable/pretendardvariable.css"; +import "@fontsource/ibm-plex-mono/400.css"; +import "@fontsource/ibm-plex-mono/500.css"; +import "../features/tech-log/presentation/styles/globals.css"; +import "../features/tech-log/presentation/styles/studio.css"; +import "../features/tech-log/presentation/styles/studio-editor.css"; import { createRuntimeComposition } from "./create-runtime-composition.ts"; import { initializeColorScheme } from "./initialize-color-scheme.ts"; import { ReleaseManifestError } from "./load-release-manifest.ts"; diff --git a/src/features/tech-log/adapters/static/content.ts b/src/features/tech-log/adapters/static/content.ts index f3fbe1e..6923308 100644 --- a/src/features/tech-log/adapters/static/content.ts +++ b/src/features/tech-log/adapters/static/content.ts @@ -1,14 +1,7 @@ import { projects, publicRecords, releases } from "./public-content.ts"; import { getHomeFocusItems, searchPublicContent } from "./public-query.ts"; -export const siteConfig = { - brandTitle: "TechLog", - identityStatement: "문제를 재현하고 검증해 운영 가능한 설계로 연결합니다.", - operator: "동현", - contactLabel: "프로필", - contactPath: "/profile", - latestRelease: "/releases/0.1.0", -} as const; +export { publicSiteConfig as siteConfig } from "../../contracts/public-site-config.ts"; export type { FocusKey, diff --git a/src/features/tech-log/contracts/public-site-config.ts b/src/features/tech-log/contracts/public-site-config.ts new file mode 100644 index 0000000..97a27b4 --- /dev/null +++ b/src/features/tech-log/contracts/public-site-config.ts @@ -0,0 +1,8 @@ +export const publicSiteConfig = Object.freeze({ + brandTitle: "TechLog", + identityStatement: "문제를 재현하고 검증해 운영 가능한 설계로 연결합니다.", + operator: "동현", + contactLabel: "프로필", + contactPath: "/profile", + latestRelease: "/releases/0.1.0", +}); diff --git a/src/features/tech-log/presentation/public/components/fatal-error-state.tsx b/src/features/tech-log/presentation/public/components/fatal-error-state.tsx new file mode 100644 index 0000000..ae61240 --- /dev/null +++ b/src/features/tech-log/presentation/public/components/fatal-error-state.tsx @@ -0,0 +1,37 @@ +import { Link } from "react-router-dom"; + +type FatalErrorStateProps = { + traceId: string; + onRetry?: () => void; + retryHref?: string; +}; + +export function FatalErrorState({ + traceId, + onRetry, + retryHref = "/", +}: FatalErrorStateProps) { + return ( +
+

Service error

+

페이지를 불러오지 못했습니다.

+

+ 잠시 후 다시 시도해 주세요. 문제가 계속되면 아래 Trace ID를 함께 + 전달해 주세요. +

+

+ Trace ID + {traceId} +

+ {onRetry ? ( + + ) : ( + + 다시 시도 + + )} +
+ ); +} diff --git a/src/features/tech-log/presentation/public/components/search-dialog.tsx b/src/features/tech-log/presentation/public/components/search-dialog.tsx new file mode 100644 index 0000000..56e1cfa --- /dev/null +++ b/src/features/tech-log/presentation/public/components/search-dialog.tsx @@ -0,0 +1,119 @@ +import { useId, useRef, useState } from "react"; +import { Link } from "react-router-dom"; + +import { TECH_LOG_FEATURE_ID } from "../../../application/tech-log-feature-input.ts"; +import { useApplication } from "../../../../../presentation/providers/application-provider.tsx"; + +type SearchDialogProps = { + className?: string; + onBeforeOpen?: () => void; +}; + +export function SearchDialog({ + className = "", + onBeforeOpen, +}: SearchDialogProps) { + const dialogId = useId(); + const dialogRef = useRef(null); + const inputRef = useRef(null); + const triggerRef = useRef(null); + const [query, setQuery] = useState(""); + const normalizedQuery = query.trim().toLocaleLowerCase("ko-KR"); + const { publicContent } = useApplication().features.get(TECH_LOG_FEATURE_ID); + const results = publicContent.searchPublicContent(normalizedQuery); + + function open() { + onBeforeOpen?.(); + dialogRef.current?.showModal(); + requestAnimationFrame(() => inputRef.current?.focus()); + } + + function close() { + dialogRef.current?.close(); + } + + return ( + <> + + triggerRef.current?.focus()} + onKeyDown={(event) => { + if (event.key === "Escape") { + event.preventDefault(); + close(); + } + }} + onClick={(event) => { + if (event.target === event.currentTarget) close(); + }} + > +
+
+
+

Public records

+

TechLog 검색

+
+ +
+ +

+ {results.length}개의 공개 기록 +

+ {results.length > 0 ? ( + <> +
    + {results.map((entry) => ( +
  • + + {entry.contentType} + {entry.title} +

    {entry.summary}

    + +
  • + ))} +
+ {normalizedQuery ? ( + + 전체 검색 결과 보기 + + ) : null} + + ) : ( +

일치하는 공개 기록이 없습니다.

+ )} +
+
+ + ); +} diff --git a/src/features/tech-log/presentation/public/components/site-header.tsx b/src/features/tech-log/presentation/public/components/site-header.tsx new file mode 100644 index 0000000..dcedc98 --- /dev/null +++ b/src/features/tech-log/presentation/public/components/site-header.tsx @@ -0,0 +1,88 @@ +import { useRef } from "react"; +import { Link, useLocation } from "react-router-dom"; + +import { publicSiteConfig } from "../../../contracts/public-site-config.ts"; +import { SearchDialog } from "./search-dialog.tsx"; + +const navigation = [ + { label: "탐색", path: "/explore" }, + { label: "프로젝트", path: "/projects" }, + { label: "변경 기록", path: "/releases" }, + { label: "프로필", path: "/profile" }, +] as const; + +export function SiteHeader({ currentPath }: { currentPath?: string }) { + const location = useLocation(); + const activePath = currentPath ?? location.pathname; + const menuRef = useRef(null); + const summaryRef = useRef(null); + + function isCurrent(path: string) { + return ( + activePath === path || + (path !== "/" && activePath.startsWith(path + "/")) + ); + } + + function closeMenu(restoreFocus = false) { + if (!menuRef.current) return; + menuRef.current.open = false; + if (restoreFocus) summaryRef.current?.focus(); + } + + return ( +
+
+
+ + {publicSiteConfig.brandTitle} + +
+ +
+ +
+ closeMenu()} + /> +
+
{ + if (event.key !== "Escape") return; + event.preventDefault(); + closeMenu(true); + }} + > + 메뉴 + +
+
+
+
+ ); +} diff --git a/src/features/tech-log/presentation/public/public-shell.tsx b/src/features/tech-log/presentation/public/public-shell.tsx new file mode 100644 index 0000000..a94646e --- /dev/null +++ b/src/features/tech-log/presentation/public/public-shell.tsx @@ -0,0 +1,38 @@ +import type { ReactNode } from "react"; +import { Link } from "react-router-dom"; + +import { publicSiteConfig } from "../../contracts/public-site-config.ts"; +import { SiteHeader } from "./components/site-header.tsx"; + +type PublicShellProps = { + children: ReactNode; + currentPath?: string; +}; + +export function PublicShell({ children, currentPath }: PublicShellProps) { + return ( +
+ + 본문으로 건너뛰기 + + + + {children} + +
+
+
+

{publicSiteConfig.operator}

+

{publicSiteConfig.identityStatement}

+
+
+ + {publicSiteConfig.contactLabel} + + 최신 Release +
+
+
+
+ ); +} diff --git a/src/features/tech-log/presentation/styles/globals.css b/src/features/tech-log/presentation/styles/globals.css new file mode 100644 index 0000000..c5fb2a2 --- /dev/null +++ b/src/features/tech-log/presentation/styles/globals.css @@ -0,0 +1,2590 @@ + +:root { + --canvas: #f7f7f5; + --paper: #fcfcfb; + --ink: #17181b; + --ink-soft: #3f4249; + --muted: #5a5d64; + --faint: #6c6f76; + --line: #e2e3e1; + --line-strong: #c8cbcf; + --signal: #3e5cc7; + --signal-soft: #eef1fc; + --evidence: #176b5b; + --evidence-soft: #edf7f3; + --warning: #965b18; + --warning-soft: #fff7ed; + --code-canvas: #15181d; + --shell: 1180px; + --body-copy: 42rem; +} + +* { + box-sizing: border-box; +} + +html { + background: var(--canvas); + color: var(--ink); + scroll-behavior: smooth; +} + +body { + margin: 0; + min-width: 320px; + overflow-x: hidden; + background: + linear-gradient(to bottom, rgba(255, 255, 255, 0.45), transparent 420px), + var(--canvas); + color: var(--ink); + font-family: + "Pretendard Variable", + Pretendard, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + font-size: 16px; + font-weight: 400; + line-height: 1.6; + word-break: keep-all; + text-rendering: optimizeLegibility; +} + +a { + color: inherit; + text-decoration: none; +} + +button, +input { + font: inherit; +} + +button, +summary, +a { + -webkit-tap-highlight-color: transparent; +} + +::selection { + background: #dce3ff; + color: var(--ink); +} + +:focus-visible { + outline: 2px solid var(--signal); + outline-offset: 4px; +} + +.site-frame { + min-height: 100vh; +} + +.shell { + width: min(var(--shell), calc(100% - 80px)); + margin-inline: auto; +} + +.skip-link { + position: fixed; + z-index: 100; + top: 12px; + left: 16px; + padding: 10px 14px; + border: 1px solid var(--line-strong); + border-radius: 6px; + background: var(--paper); + color: var(--ink); + font-size: 14px; + font-weight: 600; + transform: translateY(-160%); + transition: transform 120ms ease; +} + +.skip-link:focus { + transform: translateY(0); +} + +.site-header { + border-bottom: 1px solid rgba(226, 227, 225, 0.9); +} + +.header-inner { + display: grid; + min-height: 76px; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 48px; +} + +.header-brand-zone { justify-self: start; } +.header-menu-zone { display: flex; align-items: center; justify-self: end; gap: 18px; } + +.wordmark { + display: inline-flex; + min-height: 44px; + align-items: center; + font-size: 17px; + font-weight: 680; + letter-spacing: -0.025em; +} + +.desktop-nav { + display: flex; + align-items: stretch; + gap: 30px; + color: var(--muted); + font-size: 14px; + font-weight: 500; +} + +.nav-link, +.search-button { + position: relative; + display: inline-flex; + min-height: 44px; + align-items: center; +} + +.nav-link::after { + position: absolute; + right: 0; + bottom: 2px; + left: 0; + height: 1px; + background: var(--ink); + content: ""; + opacity: 0; + transform: scaleX(0.5); + transition: + opacity 120ms ease, + transform 120ms ease; +} + +.nav-link:hover, +.nav-link[aria-current="page"] { + color: var(--ink); +} + +.nav-link:hover::after, +.nav-link[aria-current="page"]::after { + opacity: 1; + transform: scaleX(1); +} + +.search-button, +.header-search-zone button { + padding: 0; + border: 0; + background: transparent; + color: var(--muted); + cursor: pointer; + font-size: 14px; + font-weight: 500; +} + +.search-button:hover, +.header-search-zone button:hover { + color: var(--ink); +} + +.mobile-nav { + display: none; +} + +.context-label, +.section-kicker { + margin: 0; + color: var(--muted); + font-size: 12px; + font-weight: 650; + letter-spacing: 0.105em; + line-height: 1.4; + text-transform: uppercase; +} + +.home-identity { + padding-top: 112px; + padding-bottom: 126px; +} + +.home-identity h1 { + margin: 0 0 24px; + font-size: clamp(48px, 5vw, 56px); + font-weight: 680; + letter-spacing: -0.055em; + line-height: 1.02; +} + +.identity-statement { + max-width: 680px; + margin: 0; + color: var(--ink-soft); + font-size: clamp(23px, 2.2vw, 29px); + font-weight: 430; + letter-spacing: -0.028em; + line-height: 1.48; +} + +.text-link { + display: inline-flex; + min-height: 44px; + align-items: center; + gap: 8px; + color: var(--signal); + font-size: 14px; + font-weight: 600; +} + +.text-link:hover { + text-decoration: underline; + text-decoration-thickness: 1px; + text-underline-offset: 4px; +} + +.identity-link { + margin-top: 38px; +} + +.focus-section { + padding: 34px 0 44px; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); +} + +.focus-tabs-static { + display: flex; + gap: 27px; + margin-top: 18px; + color: var(--faint); + font-size: 14px; + font-weight: 560; +} + +.focus-tabs-static span { + position: relative; + display: inline-flex; + min-height: 36px; + align-items: center; +} + +.focus-tabs-static .is-active { + color: var(--ink); +} + +.focus-tabs-static .is-active::after { + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 2px; + background: var(--signal); + content: ""; +} + +.focus-tabs { + display: flex; + gap: 27px; + margin-top: 18px; +} + +.focus-tabs button { + position: relative; + display: inline-flex; + min-height: 44px; + align-items: center; + padding: 0; + border: 0; + background: transparent; + color: var(--faint); + cursor: pointer; + font-size: 14px; + font-weight: 560; +} + +.focus-tabs button[aria-selected="true"] { + color: var(--ink); +} + +.focus-tabs button[aria-selected="true"]::after { + position: absolute; + right: 0; + bottom: 2px; + left: 0; + height: 2px; + background: var(--signal); + content: ""; +} + +.focus-content { + display: grid; + grid-template-columns: minmax(0, 1.15fr) minmax(320px, 0.85fr); + gap: 90px; + padding-top: 32px; +} + +.focus-summary h2 { + max-width: 700px; + margin: 0; + font-size: clamp(29px, 3vw, 38px); + font-weight: 650; + letter-spacing: -0.04em; + line-height: 1.22; +} + +.focus-summary > p { + max-width: 620px; + margin: 17px 0 17px; + color: var(--ink-soft); + font-size: 18px; + line-height: 1.72; +} + +.focus-details { + margin: 2px 0 0; +} + +.focus-details div { + display: grid; + grid-template-columns: 86px 1fr; + gap: 18px; + padding: 12px 0; + border-bottom: 1px solid var(--line); +} + +.focus-details div:first-child { + padding-top: 0; +} + +.focus-details div:last-child { + border-bottom: 0; +} + +.focus-details dt { + color: var(--faint); + font-size: 13px; + font-weight: 540; +} + +.focus-details dd { + margin: 0; + color: var(--ink-soft); + font-size: 14px; + font-weight: 520; + line-height: 1.55; +} + +.latest-section, +.explore-section { + padding-top: 128px; +} + +.section-heading-row { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 32px; + padding-bottom: 28px; +} + +.section-heading-row h2 { + margin: 11px 0 0; + font-size: 30px; + font-weight: 650; + letter-spacing: -0.045em; + line-height: 1.15; +} + +.section-action { + margin-bottom: 3px; +} + +.latest-list, +.explore-list, +.document-relations ul { + margin: 0; + padding: 0; + list-style: none; +} + +.latest-list { + border-top: 1px solid var(--line-strong); +} + +.latest-list li { + border-bottom: 1px solid var(--line); +} + +.latest-row { + display: grid; + grid-template-columns: 156px minmax(0, 1fr) 160px 24px; + gap: 30px; + align-items: start; + padding: 31px 2px 32px; + transition: color 120ms ease; +} + +.latest-time { + display: flex; + flex-direction: column; + gap: 4px; + padding-top: 3px; + color: var(--muted); + font-size: 13px; + font-weight: 540; + letter-spacing: 0.01em; +} + +.latest-time span { + color: var(--ink); + font-weight: 650; +} + +.latest-copy h3 { + margin: 0; + font-size: 20px; + font-weight: 620; + letter-spacing: -0.025em; + line-height: 1.45; +} + +.latest-copy > p { + max-width: 680px; + margin: 8px 0 0; + color: var(--muted); + font-size: 15px; + line-height: 1.65; +} + +.latest-context { + display: flex; + flex-direction: column; + gap: 3px; + padding-top: 4px; + color: var(--faint); + font-size: 13px; + line-height: 1.5; +} + +.latest-context span:last-child { + color: var(--muted); +} + +.latest-arrow { + padding-top: 2px; + color: var(--faint); + font-size: 16px; + opacity: 0; + transform: translate(-4px, 4px); + transition: + opacity 120ms ease, + transform 120ms ease; +} + +.latest-row:hover .latest-copy h3 { + color: var(--signal); +} + +.latest-row:hover .latest-arrow { + opacity: 1; + transform: translate(0, 0); +} + +.latest-mobile-meta { + display: none; +} + +.latest-state { + min-height: 132px; + margin: 0; + padding: 34px 2px; + border-top: 1px solid var(--line-strong); + border-bottom: 1px solid var(--line); + color: var(--muted); + font-size: 15px; +} + +.latest-state--error { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 24px; +} + +.latest-state--error p { margin: 0; } + +.explore-section { + padding-bottom: 140px; +} + +.explore-heading { + padding-bottom: 27px; +} + +.explore-list { + border-top: 1px solid var(--line-strong); +} + +.explore-list li { + border-bottom: 1px solid var(--line); +} + +.explore-list a { + display: grid; + min-height: 76px; + grid-template-columns: minmax(0, 1fr) 150px 22px; + gap: 24px; + align-items: center; + color: var(--ink-soft); + font-size: 17px; + letter-spacing: -0.015em; +} + +.explore-list strong { + color: var(--ink); + font-size: 14px; + font-weight: 620; +} + +.explore-list a > span:last-child { + color: var(--faint); + opacity: 0; + transform: translateX(-4px); + transition: + opacity 120ms ease, + transform 120ms ease; +} + +.explore-list a:hover { + color: var(--signal); +} + +.explore-list a:hover > span:last-child { + opacity: 1; + transform: translateX(0); +} + +.site-footer { + border-top: 1px solid var(--line); +} + +.footer-inner { + display: flex; + min-height: 172px; + align-items: center; + justify-content: space-between; + gap: 40px; + color: var(--muted); + font-size: 13px; +} + +.footer-inner p { + margin: 0; +} + +.footer-name { + margin-bottom: 5px !important; + color: var(--ink); + font-size: 14px; + font-weight: 640; +} + +.footer-links { + display: flex; + gap: 26px; +} + +.footer-links a:hover { + color: var(--signal); +} + +/* Case */ + +.case-header { + max-width: 1000px; + padding-top: 96px; + padding-bottom: 72px; +} + +.case-breadcrumb { + display: flex; + flex-wrap: wrap; + gap: 9px; + align-items: center; + margin-bottom: 27px; + color: var(--muted); + font-size: 13px; + font-weight: 560; +} + +.case-breadcrumb a:hover { + color: var(--signal); +} + +.case-breadcrumb span { + color: var(--line-strong); +} + +.case-header h1 { + max-width: 930px; + margin: 0; + font-size: clamp(42px, 5vw, 52px); + font-weight: 660; + letter-spacing: -0.052em; + line-height: 1.14; +} + +.case-summary { + max-width: 790px; + margin: 27px 0 0; + color: var(--ink-soft); + font-size: 21px; + font-weight: 430; + letter-spacing: -0.022em; + line-height: 1.62; +} + +.case-snapshot { + display: grid; + grid-template-columns: 1fr 1fr; + margin-top: 56px; + border-top: 1px solid var(--line-strong); + border-bottom: 1px solid var(--line); +} + +.case-snapshot > div { + padding: 30px 38px 32px 0; +} + +.case-snapshot > div + div { + padding-right: 0; + padding-left: 38px; + border-left: 1px solid var(--line); +} + +.case-snapshot p { + margin: 0; +} + +.case-snapshot > div > p:last-child { + margin-top: 11px; + color: var(--ink-soft); + font-size: 17px; + line-height: 1.72; +} + +.snapshot-label { + color: var(--warning); + font-size: 12px; + font-weight: 690; + letter-spacing: 0.09em; +} + +.snapshot-label--answer { + color: var(--evidence); +} + +.case-meta { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 32px; + margin: 28px 0 0; +} + +.case-meta div { + min-width: 0; +} + +.case-meta dt { + margin-bottom: 5px; + color: var(--faint); + font-size: 12px; + font-weight: 580; +} + +.case-meta dd { + margin: 0; + color: var(--muted); + font-size: 13px; + line-height: 1.55; +} + +.document-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 178px; + gap: 72px; + align-items: start; + padding-bottom: 136px; +} + +.document-body { + min-width: 0; + width: min(100%, var(--body-copy)); + justify-self: center; +} + +.document-body section { + scroll-margin-top: 32px; +} + +.document-body section + section:not(.document-relations) { + margin-top: 0; +} + +.document-body h2 { + margin: 3.2em 0 0.9em; + font-size: 30px; + font-weight: 640; + letter-spacing: -0.035em; + line-height: 1.3; +} + +.document-body section:first-child h2 { + margin-top: 0; +} + +.heading-anchor { + display: inline-flex; + align-items: baseline; + gap: 11px; +} + +.heading-anchor > span { + color: var(--line-strong); + font-family: "IBM Plex Mono", monospace; + font-size: 14px; + font-weight: 400; + opacity: 0; + transition: opacity 120ms ease; +} + +.heading-anchor:hover > span, +.heading-anchor:focus-visible > span { + opacity: 1; +} + +.document-body > section > p, +.document-body > section > ol { + margin-top: 0; + margin-bottom: 1.2em; + color: var(--ink-soft); + font-size: 18px; + line-height: 1.78; + overflow-wrap: break-word; +} + +.document-body > section > ol { + padding-left: 1.35em; +} + +.document-body > section > ol li { + padding-left: 0.35em; + margin-bottom: 0.45em; +} + +.document-body code:not(pre code) { + padding: 0.08em 0.3em; + border-radius: 4px; + background: #ececea; + color: #30333a; + font-family: "IBM Plex Mono", monospace; + font-size: 0.86em; + word-break: break-word; +} + +.code-block { + width: min(58rem, calc(100% + 12rem)); + margin: 38px 0 39px 50%; + overflow: hidden; + border: 1px solid #242831; + border-radius: 8px; + background: var(--code-canvas); + color: #e8ebf2; + transform: translateX(-50%); +} + +.code-block figcaption { + display: flex; + min-height: 46px; + align-items: center; + gap: 10px; + padding: 0 18px; + border-bottom: 1px solid #292e38; + color: #9ea5b2; + font-family: "IBM Plex Mono", monospace; + font-size: 11px; + letter-spacing: 0.06em; +} + +.code-block figcaption button { + min-width: 52px; + min-height: 44px; + margin-left: auto; + padding: 0 10px; + border: 1px solid #343a46; + border-radius: 5px; + background: transparent; + color: #c7ceda; + cursor: pointer; + font-family: "Pretendard Variable", Pretendard, sans-serif; + font-size: 12px; + font-weight: 560; + letter-spacing: 0; +} + +.code-block figcaption button:hover { + border-color: #596274; + color: #ffffff; +} + +.code-block figcaption span:first-child { + color: #c7ceda; + font-weight: 500; +} + +.code-block figcaption span + span::before { + margin-right: 10px; + color: #555d6c; + content: "·"; +} + +.code-block pre { + max-width: 100%; + margin: 0; + padding: 23px 24px 25px; + overflow-x: auto; + overscroll-behavior-inline: contain; +} + +.code-block code { + display: block; + min-width: max-content; + background: none; + color: inherit; + font-family: "IBM Plex Mono", monospace; + font-size: 14px; + font-weight: 400; + line-height: 1.72; + tab-size: 4; + white-space: pre; +} + +.data-table-wrap { + width: min(58rem, calc(100% + 12rem)); + margin: 37px 0 40px 50%; + overflow-x: auto; + border-top: 1px solid var(--line-strong); + border-bottom: 1px solid var(--line-strong); + transform: translateX(-50%); +} + +.data-table-wrap table { + width: 100%; + min-width: 780px; + border-collapse: collapse; + color: var(--ink-soft); + font-size: 14px; + text-align: left; +} + +.data-table-wrap caption { + padding: 0 0 13px; + color: var(--muted); + font-size: 12px; + font-weight: 650; + letter-spacing: 0.06em; + text-align: left; + text-transform: uppercase; +} + +.data-table-wrap th, +.data-table-wrap td { + padding: 16px 17px; + border-bottom: 1px solid var(--line); + vertical-align: top; +} + +.data-table-wrap thead th { + background: #f0f0ed; + color: var(--ink); + font-size: 13px; + font-weight: 650; +} + +.data-table-wrap tbody tr:last-child th, +.data-table-wrap tbody tr:last-child td { + border-bottom: 0; +} + +.data-table-wrap tbody th { + color: var(--ink); + font-weight: 580; +} + +.status { + font-size: 13px; + font-weight: 650; +} + +.status--warning { + color: var(--warning); +} + +.status--evidence { + color: var(--evidence); +} + +.callout { + width: min(var(--body-copy), 100%); + margin: 35px auto 39px; + padding: 20px 24px; + border-left: 3px solid var(--warning); + border-radius: 0 6px 6px 0; + background: var(--warning-soft); +} + +.callout p { + margin: 0; + color: var(--ink-soft); + font-size: 16px; + line-height: 1.7; +} + +.callout .callout-label { + margin-bottom: 5px; + color: var(--warning); + font-size: 12px; + font-weight: 700; + letter-spacing: 0.08em; +} + +.evidence-figure { + width: min(61rem, calc(100% + 15rem)); + margin: 43px 0 42px 50%; + transform: translateX(-50%); +} + +.evidence-image-button { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--paper); + cursor: zoom-in; + text-align: left; +} + +.evidence-image-button img { + display: block; + width: 100%; + height: auto; +} + +.evidence-image-button > span { + position: absolute; + right: 14px; + bottom: 14px; + display: inline-flex; + min-height: 34px; + align-items: center; + padding: 0 11px; + border: 1px solid rgba(200, 203, 207, 0.9); + border-radius: 5px; + background: rgba(252, 252, 251, 0.92); + color: var(--muted); + font-size: 12px; + font-weight: 620; + opacity: 0; + transition: opacity 120ms ease; +} + +.evidence-image-button:hover > span, +.evidence-image-button:focus-visible > span { + opacity: 1; +} + +.evidence-figure figcaption { + margin-top: 12px; + color: var(--muted); + font-size: 13px; + line-height: 1.55; +} + +.document-body blockquote { + margin: 42px 0 0; + padding: 26px 0 0; + border-top: 1px solid var(--line-strong); + color: var(--ink); + font-size: 21px; + font-weight: 530; + letter-spacing: -0.018em; + line-height: 1.68; +} + +.document-relations { + margin-top: 112px !important; + padding-top: 34px; + border-top: 1px solid var(--line-strong); +} + +.document-relations h2 { + margin: 10px 0 23px; + font-size: 32px; +} + +.document-relations li { + border-top: 1px solid var(--line); +} + +.document-relations li:last-child { + border-bottom: 1px solid var(--line); +} + +.document-relations a { + display: grid; + grid-template-columns: 150px minmax(0, 1fr) 20px; + gap: 20px; + align-items: center; + min-height: 92px; +} + +.document-relations a > span:first-child { + color: var(--muted); + font-size: 13px; + font-weight: 560; +} + +.document-relations strong { + color: var(--ink); + font-size: 16px; + font-weight: 600; + line-height: 1.5; +} + +.document-relations a > span:last-child { + color: var(--faint); +} + +.document-relations a:hover strong { + color: var(--signal); +} + +.document-toc { + position: sticky; + top: 28px; + padding-top: 3px; +} + +.document-toc > p { + margin: 0 0 14px; + color: var(--faint); + font-size: 12px; + font-weight: 650; + letter-spacing: 0.06em; +} + +.document-toc nav { + display: flex; + flex-direction: column; +} + +.document-toc a { + padding: 7px 0 7px 14px; + border-left: 1px solid var(--line); + color: var(--faint); + font-size: 12.5px; + line-height: 1.45; +} + +.document-toc a:hover { + color: var(--ink); +} + +.document-toc a[aria-current="location"] { + border-left: 2px solid var(--signal); + color: var(--signal); + font-weight: 620; +} + +.mobile-toc-wrap { + display: none; +} + +/* Dialogs */ + +.visually-hidden { + position: absolute !important; + width: 1px !important; + height: 1px !important; + padding: 0 !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; +} + +dialog { + color: var(--ink); +} + +dialog::backdrop { + background: rgba(23, 24, 27, 0.44); + backdrop-filter: blur(2px); +} + +.search-dialog { + position: fixed; + inset: 0; + margin: auto; + width: min(720px, calc(100% - 40px)); + max-height: min(760px, calc(100vh - 56px)); + padding: 0; + overflow: hidden; + border: 1px solid var(--line-strong); + border-radius: 9px; + background: var(--paper); + box-shadow: 0 28px 90px rgba(19, 22, 28, 0.25); +} + +.search-dialog-inner { + display: flex; + max-height: min(760px, calc(100vh - 56px)); + flex-direction: column; + padding: 27px 28px 22px; +} + +.search-dialog header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 28px; +} + +.search-dialog h2 { + margin: 7px 0 0; + font-size: 26px; + font-weight: 650; + letter-spacing: -0.035em; +} + +.dialog-close { + min-width: 52px; + min-height: 44px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: 6px; + background: transparent; + color: var(--muted); + cursor: pointer; + font-size: 13px; + font-weight: 580; +} + +.dialog-close:hover { + border-color: var(--line-strong); + color: var(--ink); +} + +.search-field { + display: flex; + min-height: 56px; + align-items: center; + gap: 11px; + margin-top: 24px; + padding: 0 16px; + border: 1px solid var(--line-strong); + border-radius: 7px; + background: #ffffff; + color: var(--faint); +} + +.search-field:focus-within { + border-color: var(--signal); + box-shadow: 0 0 0 3px var(--signal-soft); +} + +.search-field input { + width: 100%; + min-width: 0; + border: 0; + outline: 0; + background: transparent; + color: var(--ink); + font-size: 16px; +} + +.search-field input::placeholder { + color: var(--faint); +} + +.search-count { + margin: 15px 0 8px; + color: var(--faint); + font-size: 12px; + font-weight: 560; +} + +.search-results { + margin: 0; + padding: 0; + overflow-y: auto; + border-top: 1px solid var(--line); + list-style: none; +} + +.search-results li { + border-bottom: 1px solid var(--line); +} + +.search-results a { + display: grid; + grid-template-columns: 94px minmax(0, 1fr); + gap: 4px 18px; + padding: 17px 3px 18px; +} + +.search-results a > span { + grid-row: 1 / span 2; + padding-top: 3px; + color: var(--faint); + font-size: 11px; + font-weight: 680; + letter-spacing: 0.055em; +} + +.search-results strong { + color: var(--ink); + font-size: 15px; + font-weight: 620; + line-height: 1.45; +} + +.search-results p { + margin: 2px 0 0; + color: var(--muted); + font-size: 13px; + line-height: 1.5; +} + +.search-results a:hover strong { + color: var(--signal); +} + +.search-all-link { + display: flex; + min-height: 50px; + align-items: center; + justify-content: space-between; + margin-top: 14px; + padding: 0 2px; + border-top: 1px solid var(--line); + color: var(--signal); + font-size: 13px; + font-weight: 620; +} + +.search-empty { + margin: 0; + padding: 46px 0 54px; + border-top: 1px solid var(--line); + color: var(--muted); + font-size: 14px; + text-align: center; +} + +.figure-dialog { + width: min(1180px, calc(100% - 48px)); + max-width: none; + padding: 0; + overflow: hidden; + border: 1px solid var(--line-strong); + border-radius: 9px; + background: var(--paper); + box-shadow: 0 30px 100px rgba(19, 22, 28, 0.28); +} + +.figure-dialog > div { + position: relative; + padding: 54px 34px 24px; +} + +.figure-dialog .dialog-close { + position: absolute; + top: 12px; + right: 14px; +} + +.figure-dialog img { + display: block; + width: 100%; + height: auto; +} + +.figure-dialog p { + margin: 12px 0 0; + color: var(--muted); + font-size: 13px; +} + +/* Scope and error support screens */ + +.scope-boundary, +.fatal-state { + max-width: 760px; + min-height: calc(100vh - 249px); + padding-top: 128px; + padding-bottom: 128px; +} + +.scope-boundary h1, +.fatal-state h1 { + max-width: 700px; + margin: 17px 0 22px; + font-size: clamp(38px, 5vw, 52px); + font-weight: 650; + letter-spacing: -0.048em; + line-height: 1.2; +} + +.scope-boundary > p:not(.context-label), +.fatal-state > p:not(.context-label) { + max-width: 650px; + margin: 0; + color: var(--muted); + font-size: 18px; + line-height: 1.75; +} + +.fatal-state .trace-id { + display: flex; + flex-wrap: wrap; + gap: 8px 14px; + margin-top: 24px !important; + font-size: 13px !important; +} + +.fatal-state .trace-id span { + color: var(--faint); + font-weight: 600; +} + +.fatal-state .trace-id code { color: var(--ink-soft); } + +.primary-link { + display: inline-flex; + min-height: 44px; + align-items: center; + margin-top: 34px; + padding: 0 17px; + border: 1px solid var(--line-strong); + border-radius: 6px; + background: var(--paper); + color: var(--ink); + font-size: 14px; + font-weight: 620; +} + +.primary-link:hover { + border-color: var(--signal); + color: var(--signal); +} + +@media (max-width: 1179px) { + .shell { + width: calc(100% - 64px); + } + + .focus-content { + gap: 54px; + } + + .latest-row { + grid-template-columns: 140px minmax(0, 1fr) 24px; + } + + .latest-context { + display: none; + } + + .latest-mobile-meta { + display: block; + color: var(--faint) !important; + font-size: 13px !important; + } + + .case-header { + max-width: calc(100% - 64px); + } + + .document-layout { + display: block; + } + + .document-toc { + display: none; + } + + .code-block, + .data-table-wrap { + width: min(58rem, calc(100vw - 64px)); + margin-left: 50%; + transform: translateX(-50%); + } + + .evidence-figure { + width: min(61rem, calc(100vw - 64px)); + margin-left: 50%; + transform: translateX(-50%); + } + + .mobile-toc-wrap { + display: block; + margin-bottom: 62px; + } + + .mobile-toc { + width: min(100%, var(--body-copy)); + margin-inline: auto; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); + } + + .mobile-toc summary { + min-height: 52px; + padding: 14px 0; + color: var(--muted); + cursor: pointer; + font-size: 13px; + font-weight: 580; + } + + .mobile-toc nav { + display: grid; + gap: 3px; + padding: 0 0 16px; + } + + .mobile-toc a { + min-height: 44px; + padding: 8px 12px; + color: var(--muted); + font-size: 13px; + } + + .mobile-toc a:hover { + color: var(--signal); + } +} + +@media (max-width: 1050px) { + .header-inner { gap: 18px; } + .header-menu-zone .desktop-nav { display: none; } + .header-menu-zone .mobile-nav { position: relative; display: block; } + .header-menu-zone .mobile-nav summary { + display: inline-flex; + min-width: 48px; + min-height: 44px; + align-items: center; + justify-content: flex-end; + color: var(--muted); + cursor: pointer; + font-size: 14px; + font-weight: 580; + list-style: none; + } + .header-menu-zone .mobile-nav summary::-webkit-details-marker { display: none; } + .header-menu-zone .mobile-nav[open] summary { color: var(--ink); } + .header-menu-zone .mobile-nav nav { + position: absolute; + z-index: 20; + top: 48px; + right: 0; + display: grid; + width: min(260px, calc(100vw - 40px)); + padding: 10px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--paper); + } + .header-menu-zone .mobile-nav nav a { + display: flex; + min-height: 44px; + align-items: center; + padding: 0 11px; + color: var(--muted); + font-size: 14px; + } + .header-menu-zone .mobile-nav nav a[aria-current="page"] { border-left: 2px solid var(--signal); color: var(--ink); font-weight: 650; } +} + +@media (max-width: 767px) { + .shell, + .case-header { + width: calc(100% - 40px); + max-width: none; + } + + .header-inner { + min-height: 68px; + grid-template-columns: minmax(0, 1fr) auto; + gap: 16px; + } + + .desktop-nav { + display: none; + } + + .header-search-zone .search-button { min-width: 48px; justify-content: center; } + + .home-identity { + padding-top: 80px; + padding-bottom: 94px; + } + + .home-identity h1 { + margin-top: 15px; + margin-bottom: 19px; + font-size: 38px; + line-height: 1.08; + } + + .identity-statement { + font-size: 20px; + line-height: 1.58; + } + + .identity-link { + margin-top: 27px; + } + + .focus-section { + padding-top: 28px; + padding-bottom: 34px; + } + + .focus-tabs-static, + .focus-tabs { + gap: 21px; + overflow-x: auto; + font-size: 13px; + white-space: nowrap; + } + + .focus-tabs button { + min-height: 44px; + flex: 0 0 auto; + font-size: 13px; + } + + .focus-content { + display: block; + padding-top: 27px; + } + + .focus-summary h2 { + font-size: 28px; + line-height: 1.28; + } + + .focus-summary > p { + margin-top: 13px; + font-size: 16px; + line-height: 1.7; + } + + .focus-details { + margin-top: 24px; + } + + .focus-details div { + grid-template-columns: 76px 1fr; + gap: 14px; + } + + .latest-section, + .explore-section { + padding-top: 92px; + } + + .section-heading-row { + align-items: flex-start; + padding-bottom: 23px; + } + + .section-heading-row h2 { + margin-top: 9px; + font-size: 26px; + } + + .section-action { + display: none; + } + + .latest-row { + display: block; + position: relative; + padding: 25px 28px 27px 0; + } + + .latest-time { + flex-direction: row; + gap: 9px; + padding: 0 0 9px; + font-size: 12px; + } + + .latest-time span::after { + margin-left: 9px; + color: var(--line-strong); + content: "·"; + } + + .latest-copy h3 { + font-size: 18px; + line-height: 1.48; + } + + .latest-copy > p { + margin-top: 7px; + font-size: 14px; + } + + .latest-arrow { + position: absolute; + top: 26px; + right: 2px; + opacity: 1; + transform: none; + } + + .explore-section { + padding-bottom: 100px; + } + + .explore-list a { + min-height: 88px; + grid-template-columns: minmax(0, 1fr) 18px; + gap: 15px; + padding: 14px 0; + font-size: 16px; + line-height: 1.55; + } + + .explore-list strong { + grid-row: 2; + font-size: 12px; + } + + .explore-list a > span:last-child { + grid-row: 1 / span 2; + grid-column: 2; + opacity: 1; + transform: none; + } + + .footer-inner { + min-height: 204px; + flex-direction: column; + align-items: flex-start; + justify-content: center; + gap: 23px; + } + + .footer-links { + flex-direction: column; + gap: 7px; + } + + .case-header { + padding-top: 70px; + padding-bottom: 48px; + } + + .case-breadcrumb { + margin-bottom: 22px; + font-size: 12px; + } + + .case-header h1 { + font-size: 34px; + letter-spacing: -0.045em; + line-height: 1.2; + } + + .case-summary { + margin-top: 20px; + font-size: 18px; + line-height: 1.62; + } + + .case-snapshot { + display: block; + margin-top: 39px; + } + + .case-snapshot > div { + padding: 23px 0 25px; + } + + .case-snapshot > div + div { + padding: 23px 0 25px; + border-top: 1px solid var(--line); + border-left: 0; + } + + .case-snapshot > div > p:last-child { + font-size: 16px; + } + + .case-meta { + display: block; + margin-top: 22px; + } + + .case-meta div { + display: grid; + grid-template-columns: 76px 1fr; + gap: 12px; + margin-bottom: 8px; + } + + .case-meta dt { + margin: 0; + } + + .mobile-toc-wrap { + margin-bottom: 50px; + } + + .document-body { + width: 100%; + } + + .document-body h2 { + margin-top: 3em; + font-size: 26px; + line-height: 1.32; + } + + .document-body > section > p, + .document-body > section > ol { + font-size: 17px; + line-height: 1.72; + } + + .heading-anchor > span { + display: none; + } + + .code-block, + .data-table-wrap, + .evidence-figure { + width: 100%; + margin-right: 0; + margin-left: 0; + transform: none; + } + + .code-block { + margin-top: 30px; + margin-bottom: 31px; + } + + .code-block pre { + padding: 20px; + } + + .code-block code { + font-size: 13.5px; + line-height: 1.65; + } + + .data-table-wrap { + margin-top: 31px; + margin-bottom: 32px; + } + + .callout { + margin-top: 30px; + margin-bottom: 32px; + padding: 18px 19px; + } + + .evidence-figure { + margin-top: 33px; + margin-bottom: 34px; + } + + .evidence-image-button img { + width: 100%; + height: auto; + } + + .evidence-image-button > span { + position: static; + width: 100%; + min-height: 44px; + justify-content: flex-end; + padding-inline: 13px; + border-width: 1px 0 0; + border-radius: 0; + background: var(--paper); + opacity: 1; + } + + .document-body blockquote { + margin-top: 34px; + padding-top: 21px; + font-size: 19px; + line-height: 1.65; + } + + .document-relations { + margin-top: 86px !important; + padding-top: 27px; + } + + .document-relations h2 { + margin-top: 8px; + font-size: 28px; + } + + .document-relations a { + display: block; + position: relative; + min-height: 0; + padding: 20px 28px 21px 0; + } + + .document-relations a > span:first-child { + display: block; + margin-bottom: 5px; + font-size: 12px; + } + + .document-relations strong { + display: block; + font-size: 15px; + } + + .document-relations a > span:last-child { + position: absolute; + top: 23px; + right: 2px; + } + + .document-layout { + padding-bottom: 100px; + } + + .scope-boundary, + .fatal-state { + min-height: calc(100vh - 273px); + padding-top: 88px; + padding-bottom: 88px; + } + + .scope-boundary h1, + .fatal-state h1 { + font-size: 36px; + } + + .scope-boundary > p:not(.context-label), + .fatal-state > p:not(.context-label) { + font-size: 16px; + } + + .search-dialog { + width: calc(100% - 24px); + max-height: calc(100vh - 24px); + } + + .search-dialog-inner { + max-height: calc(100vh - 24px); + padding: 22px 18px 16px; + } + + .search-results a { + display: block; + padding: 15px 2px 16px; + } + + .search-results a > span { + display: block; + padding: 0 0 4px; + } + + .search-results p { + margin-top: 4px; + } + + .figure-dialog { + width: calc(100% - 20px); + } + + .figure-dialog > div { + padding: 54px 12px 16px; + overflow-x: auto; + overscroll-behavior-inline: contain; + } + + .figure-dialog img { + width: 1080px; + max-width: none; + } +} + +.release-index-list { margin: 70px 0 0; padding: 0; border-top: 1px solid var(--line-strong); list-style: none; } +.release-index-list > li { border-bottom: 1px solid var(--line); } +.release-index-list a { display: grid; min-height: 150px; grid-template-columns: 150px minmax(0, 1fr) 24px; gap: 28px; padding: 30px 0 33px; } +.release-index-list a > div:first-child { display: grid; align-content: start; gap: 5px; } +.release-version { font-family: "IBM Plex Mono", monospace; font-size: 13px; font-weight: 620; } +.release-index-list time { color: var(--faint); font-size: 10px; } +.release-index-list h2 { margin: 0; font-size: 23px; font-weight: 640; letter-spacing: -0.03em; } +.release-index-list p { max-width: 760px; margin: 9px 0 0; color: var(--muted); font-size: 14px; } +.release-index-list a > span:last-child { color: var(--faint); } +.release-index-list a:hover h2 { color: var(--signal); } + +.release-page { padding-top: 83px; padding-bottom: 120px; } +.release-page-header { max-width: 940px; } +.release-page-header nav { display: flex; gap: 9px; margin-bottom: 30px; color: var(--faint); font-size: 12px; } +.release-page-header nav a:hover { color: var(--signal); } +.release-page-header h1 { margin: 14px 0 17px; font-size: clamp(42px, 5vw, 52px); font-weight: 660; letter-spacing: -0.052em; line-height: 1.13; } +.release-page-header > p:not(.section-kicker) { max-width: 780px; margin: 0; color: var(--ink-soft); font-size: 19px; line-height: 1.72; } +.release-page-header > time { display: block; margin-top: 28px; color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 10px; } +.release-document { margin-top: 72px; border-top: 1px solid var(--line-strong); } +.release-document > section { display: grid; grid-template-columns: 120px minmax(0, 700px); gap: 42px; padding: 53px 0 58px; border-bottom: 1px solid var(--line); } +.release-section-number { margin: 4px 0 0; color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 10px; } +.release-document h2 { margin: 0 0 20px; font-size: 29px; font-weight: 650; letter-spacing: -0.038em; } +.release-document ul { margin: 0; padding-left: 20px; } +.release-document li { color: var(--ink-soft); font-size: 16px; line-height: 1.8; } +.release-document li + li { margin-top: 8px; } +.release-related-links { padding: 0 !important; border-top: 1px solid var(--line); list-style: none; } +.release-related-links li { margin: 0 !important; border-bottom: 1px solid var(--line); } +.release-related-links a { display: flex; min-height: 62px; align-items: center; justify-content: space-between; gap: 20px; font-size: 14px; font-weight: 590; } +.release-related-links a:hover { color: var(--signal); } + +.profile-page { padding-top: 98px; padding-bottom: 120px; } +.profile-header { max-width: 850px; padding-bottom: 88px; } +.profile-header h1 { margin: 15px 0 20px; font-size: clamp(48px, 5vw, 56px); font-weight: 670; letter-spacing: -0.055em; line-height: 1.05; } +.profile-header > p:last-child { max-width: 760px; margin: 0; color: var(--ink-soft); font-size: clamp(22px, 2.4vw, 28px); line-height: 1.55; letter-spacing: -0.025em; } +.profile-principles, +.profile-projects { display: grid; grid-template-columns: 0.7fr 1.3fr; gap: 80px; padding: 65px 0 70px; border-top: 1px solid var(--line-strong); } +.profile-principles h2, +.profile-projects h2, +.profile-topics h2 { margin: 11px 0 0; font-size: 27px; font-weight: 650; letter-spacing: -0.035em; } +.profile-principles ol, +.profile-projects ul { margin: 0; padding: 0; border-top: 1px solid var(--line); list-style: none; } +.profile-principles li { display: grid; grid-template-columns: 34px 1fr; gap: 16px; padding: 22px 0 25px; border-bottom: 1px solid var(--line); } +.profile-principles li > span { color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 10px; } +.profile-principles h3 { margin: 0; font-size: 17px; font-weight: 630; } +.profile-principles li p { margin: 7px 0 0; color: var(--muted); font-size: 14px; line-height: 1.7; } +.profile-projects li { border-bottom: 1px solid var(--line); } +.profile-projects a { position: relative; display: block; min-height: 98px; padding: 20px 34px 22px 0; } +.profile-projects a > div { display: flex; align-items: center; justify-content: space-between; gap: 20px; } +.profile-projects strong { font-size: 18px; font-weight: 630; } +.profile-projects div span { color: var(--faint); font-size: 10px; } +.profile-projects p { margin: 7px 0 0; color: var(--muted); font-size: 13px; } +.profile-projects a > span:last-child { position: absolute; top: 25px; right: 0; color: var(--faint); } +.profile-projects a:hover strong { color: var(--signal); } +.profile-topics { padding-top: 60px; border-top: 1px solid var(--line-strong); } +.profile-topics ul { display: flex; flex-wrap: wrap; gap: 8px; margin: 24px 0 0; padding: 0; list-style: none; } +.profile-topics li { padding: 8px 12px; border: 1px solid var(--line-strong); border-radius: 999px; color: var(--muted); font-size: 12px; } + +.search-page { padding-top: 92px; padding-bottom: 120px; } +.search-page-form { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px; max-width: 760px; margin-top: 48px; } +.search-page-form label { display: block; } +.search-page-form input { width: 100%; min-height: 52px; padding: 0 16px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--paper); color: var(--ink); font-size: 16px; } +.search-page-form input:focus { border-color: var(--signal); outline: 0; box-shadow: 0 0 0 3px var(--signal-soft); } +.search-page-form button { min-height: 52px; padding: 0 24px; border: 1px solid var(--ink); border-radius: 5px; background: var(--ink); color: #fff; cursor: pointer; font-weight: 620; } +.search-page-results { margin: 0; padding: 0; list-style: none; } +.search-page-results > li { border-bottom: 1px solid var(--line); } +.search-page-results a { display: grid; min-height: 126px; grid-template-columns: 110px minmax(0, 1fr) 24px; gap: 24px; padding: 25px 0 28px; } +.search-page-results a > span:first-child { color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 10px; text-transform: uppercase; } +.search-page-results strong { font-size: 18px; font-weight: 630; } +.search-page-results p { max-width: 720px; margin: 6px 0 5px; color: var(--muted); font-size: 14px; } +.search-page-results small { color: var(--faint); font-size: 11px; } +.search-page-results a:hover strong { color: var(--signal); } + +@media (max-width: 767px) { + .release-index-list { margin-top: 50px; } + .release-index-list a { grid-template-columns: 90px minmax(0, 1fr); gap: 16px; padding: 25px 0 28px; } + .release-index-list a > span:last-child { display: none; } + .release-index-list h2 { font-size: 20px; } + .release-page { padding-top: 57px; padding-bottom: 90px; } + .release-page-header h1 { font-size: 38px; } + .release-page-header > p:not(.section-kicker) { font-size: 17px; } + .release-document { margin-top: 55px; } + .release-document > section { grid-template-columns: 27px minmax(0, 1fr); gap: 11px; padding: 39px 0 43px; } + .release-document h2 { font-size: 26px; } + .release-document li { font-size: 15px; } + .profile-page { padding-top: 66px; padding-bottom: 90px; } + .profile-header { padding-bottom: 62px; } + .profile-header h1 { font-size: 44px; } + .profile-header > p:last-child { font-size: 21px; } + .profile-principles, + .profile-projects { display: block; padding: 48px 0 55px; } + .profile-principles ol, + .profile-projects ul { margin-top: 28px; } + .search-page { padding-top: 60px; padding-bottom: 90px; } + .search-page-form { grid-template-columns: 1fr; margin-top: 38px; } + .search-page-results a { grid-template-columns: 1fr auto; gap: 7px 12px; padding: 22px 0; } + .search-page-results a > span:first-child { grid-column: 1; } + .search-page-results a > div { grid-column: 1 / -1; grid-row: 2; } + .search-page-results a > span:last-child { grid-column: 2; grid-row: 1; } +} + +.project-index-list { margin: 70px 0 0; padding: 0; border-top: 1px solid var(--line-strong); list-style: none; } +.project-index-list > li { border-bottom: 1px solid var(--line); } +.project-index-list > li > a { display: grid; min-height: 210px; grid-template-columns: 42px minmax(0, 1fr) 24px; gap: 24px; padding: 34px 0 37px; } +.project-index-list > li > a > span { color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 10px; } +.project-index-title { display: flex; align-items: center; justify-content: space-between; gap: 24px; } +.project-index-title h2 { margin: 0; font-size: 28px; font-weight: 650; letter-spacing: -0.04em; } +.project-index-title span { padding: 4px 8px; border: 1px solid var(--line-strong); border-radius: 999px; color: var(--muted); font-size: 10px; font-weight: 650; } +.project-index-list p { max-width: 760px; margin: 10px 0 19px; color: var(--muted); } +.project-index-list dl { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin: 0; } +.project-index-list dl div { display: grid; grid-template-columns: 72px 1fr; gap: 10px; font-size: 12px; } +.project-index-list dt { color: var(--faint); } +.project-index-list dd { margin: 0; color: var(--ink-soft); } +.project-index-list a:hover h2 { color: var(--signal); } + +.project-page { padding-top: 85px; padding-bottom: 120px; } +.project-page-header { max-width: 980px; } +.project-breadcrumb { display: flex; gap: 9px; margin: 0 0 27px; color: var(--faint); font-size: 12px; } +.project-breadcrumb a:hover { color: var(--signal); } +.project-page-header h1 { margin: 0; font-size: clamp(42px, 5vw, 52px); font-weight: 660; letter-spacing: -0.052em; line-height: 1.13; } +.project-page-header > p:not(.project-breadcrumb) { max-width: 800px; margin: 17px 0 0; color: var(--ink-soft); font-size: 18px; line-height: 1.72; } +.project-navigation { display: flex; gap: 28px; margin-top: 46px; border-bottom: 1px solid var(--line-strong); } +.project-navigation a { position: relative; display: inline-flex; min-height: 48px; align-items: center; color: var(--muted); font-size: 13px; font-weight: 560; } +.project-navigation a[aria-current="page"] { color: var(--ink); } +.project-navigation a[aria-current="page"]::after { position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; background: var(--signal); content: ""; } + +.project-thesis { max-width: 850px; margin-top: 76px; } +.project-thesis h2 { margin: 12px 0 15px; font-size: 29px; font-weight: 650; letter-spacing: -0.038em; } +.project-thesis > p:last-child { margin: 0; color: var(--ink-soft); font-size: 20px; line-height: 1.72; } +.project-status-grid { display: grid; grid-template-columns: 0.55fr 1.25fr 1.2fr; margin: 58px 0 0; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line); } +.project-status-grid > div { padding: 23px 28px 25px 0; } +.project-status-grid > div + div { padding-left: 28px; border-left: 1px solid var(--line); } +.project-status-grid dt { color: var(--faint); font-size: 11px; } +.project-status-grid dd { margin: 5px 0 0; font-size: 14px; font-weight: 590; } +.project-overview-section { display: grid; grid-template-columns: 1fr 1.4fr; gap: 80px; align-items: start; margin-top: 83px; } +.project-overview-section h2, +.project-topics h2 { margin: 11px 0 0; font-size: 28px; font-weight: 650; letter-spacing: -0.035em; } +.project-stat-links { border-top: 1px solid var(--line-strong); } +.project-stat-links a { display: grid; min-height: 74px; grid-template-columns: 45px 1fr; gap: 18px; align-items: center; border-bottom: 1px solid var(--line); } +.project-stat-links strong { font-family: "IBM Plex Mono", monospace; font-size: 19px; } +.project-stat-links span { color: var(--muted); font-size: 14px; } +.project-stat-links a:hover span { color: var(--signal); } +.project-topics { margin-top: 80px; padding-top: 34px; border-top: 1px solid var(--line); } +.project-topics ul { display: flex; flex-wrap: wrap; gap: 8px; margin: 22px 0 0; padding: 0; list-style: none; } +.project-topics li { padding: 7px 10px; border: 1px solid var(--line-strong); border-radius: 999px; color: var(--muted); font-size: 12px; } + +.project-decision-list, +.project-activity-list { margin: 72px 0 0; padding: 0; list-style: none; } +.project-decision-list > li + li { margin-top: 86px; } +.project-decision-list article { scroll-margin-top: 24px; padding-top: 35px; border-top: 1px solid var(--line-strong); } +.project-decision-list article > header > div { display: flex; gap: 18px; color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 10px; } +.project-decision-list article > header span { color: var(--evidence); font-weight: 650; } +.project-decision-list h2 { max-width: 850px; margin: 14px 0 9px; font-size: 31px; font-weight: 650; letter-spacing: -0.04em; } +.project-decision-list header > p { max-width: 850px; margin: 0; color: var(--ink-soft); font-size: 18px; line-height: 1.7; } +.project-decision-list article > section { display: grid; grid-template-columns: 150px minmax(0, 650px); gap: 40px; margin-top: 33px; padding-top: 26px; border-top: 1px solid var(--line); } +.project-decision-list h3 { margin: 0; color: var(--faint); font-size: 12px; font-weight: 620; } +.project-decision-list section p, +.project-decision-list section ul { margin: 0; color: var(--ink-soft); font-size: 15px; line-height: 1.75; } +.project-decision-list section ul { padding-left: 18px; } +.project-decision-list section a { display: inline-flex; min-height: 44px; align-items: center; color: var(--signal); } + +.project-activity-list { border-top: 1px solid var(--line-strong); } +.project-activity-list > li { border-bottom: 1px solid var(--line); } +.project-activity-list article { display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: 24px; scroll-margin-top: 24px; padding: 28px 0 31px; } +.project-activity-list article > div { display: flex; grid-column: 1; grid-row: 1 / span 3; gap: 16px; color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 10px; white-space: nowrap; } +.project-activity-list article > div span { width: 95px; } +.project-activity-list h2 { grid-column: 2; margin: 0 0 6px; font-size: 20px; font-weight: 630; letter-spacing: -0.025em; } +.project-activity-list p { grid-column: 2; max-width: 750px; margin: 0; color: var(--muted); font-size: 14px; } +.project-activity-list a { display: inline-flex; grid-column: 2; justify-self: start; min-height: 44px; align-items: center; margin-top: 7px; color: var(--signal); font-size: 12px; font-weight: 600; } + +@media (max-width: 767px) { + .project-index-list { margin-top: 50px; } + .project-index-list > li > a { grid-template-columns: 28px minmax(0, 1fr); gap: 10px; padding: 27px 0 30px; } + .project-index-list > li > a > span:last-child { display: none; } + .project-index-title { align-items: flex-start; } + .project-index-title h2 { font-size: 24px; } + .project-index-list dl { display: block; } + .project-index-list dl div + div { margin-top: 7px; } + .project-page { padding-top: 57px; padding-bottom: 90px; } + .project-page-header h1 { font-size: 38px; } + .project-page-header > p:not(.project-breadcrumb) { font-size: 16px; } + .project-navigation { gap: 18px; overflow-x: auto; } + .project-navigation a { flex: 0 0 auto; min-height: 48px; } + .project-thesis { margin-top: 58px; } + .project-thesis > p:last-child { font-size: 18px; } + .project-status-grid { display: block; } + .project-status-grid > div, + .project-status-grid > div + div { padding: 17px 0; border-left: 0; } + .project-status-grid > div + div { border-top: 1px solid var(--line); } + .project-overview-section { display: block; margin-top: 60px; } + .project-stat-links { margin-top: 30px; } + .project-decision-list { margin-top: 55px; } + .project-decision-list h2 { font-size: 27px; } + .project-decision-list article > section { display: block; } + .project-decision-list section > :last-child { margin-top: 10px; } + .project-activity-list article { display: block; padding: 24px 0 27px; } + .project-activity-list article > div { margin: 0 0 11px; } + .project-activity-list h2 { margin: 0 0 6px; } +} + +/* Complete public information architecture */ +.public-index-page, +.public-document-page { + padding-top: 92px; + padding-bottom: 120px; +} + +.public-page-header, +.public-document-header { + max-width: 920px; +} + +.public-page-header h1, +.public-document-header h1 { + margin: 15px 0 18px; + font-size: clamp(42px, 5vw, 52px); + font-weight: 660; + letter-spacing: -0.052em; + line-height: 1.13; +} + +.public-page-header > p:last-child, +.public-document-header > p { + max-width: 760px; + margin: 0; + color: var(--ink-soft); + font-size: 19px; + line-height: 1.72; +} + +.public-filter-form { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)) auto auto; + gap: 14px; + align-items: end; + margin-top: 54px; + padding: 22px 0; + border-top: 1px solid var(--line-strong); + border-bottom: 1px solid var(--line); +} + +.public-filter-form label { + display: grid; + gap: 7px; + color: var(--muted); + font-size: 12px; + font-weight: 620; +} + +.public-filter-form select { + min-height: 44px; + padding: 0 36px 0 12px; + border: 1px solid var(--line-strong); + border-radius: 4px; + background: var(--paper); + color: var(--ink); +} + +.public-filter-form button, +.public-filter-form a { + display: inline-flex; + min-height: 44px; + align-items: center; + justify-content: center; + padding: 0 16px; + border: 1px solid var(--ink); + border-radius: 4px; + background: var(--ink); + color: #fff; + cursor: pointer; + font-size: 13px; + font-weight: 620; +} + +.public-filter-form a { + border-color: var(--line-strong); + background: transparent; + color: var(--ink); +} + +.public-result-heading { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 30px; + margin-top: 70px; + padding-bottom: 17px; + border-bottom: 1px solid var(--line-strong); +} + +.public-result-heading h2 { margin: 0; font-size: 24px; letter-spacing: -0.03em; } +.public-result-heading p { margin: 0; color: var(--faint); font-size: 12px; } + +.public-record-list { margin: 0; padding: 0; list-style: none; } +.public-record-list li { border-bottom: 1px solid var(--line); } +.public-record-list a { + display: grid; + min-height: 126px; + grid-template-columns: 115px minmax(0, 1fr) 90px; + gap: 28px; + align-items: start; + padding: 26px 0 27px; +} +.public-record-list a:hover strong { color: var(--signal); } +.record-kind { padding-top: 3px; color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 11px; text-transform: uppercase; } +.public-record-list strong { display: block; font-size: 18px; font-weight: 630; letter-spacing: -0.02em; } +.public-record-list p { max-width: 700px; margin: 7px 0 6px; color: var(--muted); font-size: 14px; line-height: 1.62; } +.record-context { color: var(--faint); font-size: 11px; } +.public-record-list time { color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 10px; text-align: right; } +.public-empty-state { margin: 0; padding: 64px 0; border-bottom: 1px solid var(--line); color: var(--muted); text-align: center; } +.public-back-link { margin-top: 34px; } + +.public-document-header { margin-bottom: 56px; } +.public-document-header nav { display: flex; flex-wrap: wrap; gap: 9px; margin-bottom: 27px; color: var(--faint); font-size: 12px; } +.public-document-header nav a:hover { color: var(--signal); } +.public-document-header dl, +.document-facts { display: grid; grid-template-columns: repeat(3, 1fr); margin: 38px 0 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); } +.public-document-header dl div, +.document-facts div { padding: 17px 24px 18px 0; } +.public-document-header dl div + div, +.document-facts div + div { padding-left: 24px; border-left: 1px solid var(--line); } +.public-document-header dt, +.document-facts dt { color: var(--faint); font-size: 11px; } +.public-document-header dd, +.document-facts dd { margin: 4px 0 0; color: var(--ink-soft); font-size: 13px; } +.document-snapshot { display: grid; grid-template-columns: 1fr 1fr; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line); } +.document-snapshot > div { padding: 28px 36px 31px 0; } +.document-snapshot > div + div { padding-right: 0; padding-left: 36px; border-left: 1px solid var(--line); } +.document-snapshot p:last-child { margin: 11px 0 0; font-size: 17px; line-height: 1.75; } +.document-facts { grid-template-columns: 1fr 1fr; margin-top: 0; border-top: 0; } + +.public-document-body, +.reference-purpose, +.document-relations { + width: min(100%, var(--body-copy)); + margin: 82px auto 0; +} +.public-document-body > section + section { margin-top: 76px; padding-top: 70px; border-top: 1px solid var(--line); } +.public-document-body h2, +.reference-purpose h2, +.document-relations h2 { margin: 0 0 20px; font-size: 29px; font-weight: 650; letter-spacing: -0.038em; } +.public-document-body h3 { margin: 0; font-size: 18px; font-weight: 630; } +.public-document-body p, +.public-document-body li, +.reference-purpose > p:last-child { color: var(--ink-soft); font-size: 17px; line-height: 1.82; } +.public-document-body ul { padding-left: 21px; } +.reference-purpose { padding: 27px 0 31px; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line); } +.reference-purpose h2 { margin: 12px 0 10px; } +.reference-purpose > p:last-child { margin: 0; } +.reference-rules, +.question-options { margin: 0; padding: 0; list-style: none; } +.reference-rules li { display: grid; grid-template-columns: 34px 1fr; gap: 16px; padding: 22px 0; border-bottom: 1px solid var(--line); } +.reference-rules li > span { color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 10px; } +.reference-rules p, +.question-options p { margin: 5px 0 0; } +.verified-at { color: var(--faint) !important; font-size: 12px !important; } +.question-status { width: fit-content; margin: -34px 0 50px; padding: 5px 10px; border: 1px solid var(--line-strong); border-radius: 999px; color: var(--muted); font-size: 11px; font-weight: 650; } +.question-status--open, +.question-status--investigating { border-color: #ead8be; background: var(--warning-soft); color: var(--warning); } +.question-status--resolved { border-color: #c8e2d9; background: var(--evidence-soft); color: var(--evidence); } +.question-resolution { display: flex; align-items: center; justify-content: space-between; gap: 30px; padding: 22px 24px; border: 1px solid #c8e2d9; background: var(--evidence-soft); } +.question-resolution strong { max-width: 780px; font-size: 14px; } +.question-resolution a { display: inline-flex; min-height: 44px; align-items: center; flex: 0 0 auto; color: var(--evidence); font-size: 13px; font-weight: 650; } +.question-options li { padding: 23px 0; border-bottom: 1px solid var(--line); } +.next-validation { padding: 28px 30px 31px !important; border: 1px solid #d7dceb !important; background: var(--signal-soft); } +.next-validation h2 { margin: 10px 0 8px; } + +.document-relations { padding-top: 68px; border-top: 1px solid var(--line-strong); } +.document-relations h2 { margin-top: 12px; } +.document-relations ul { margin: 30px 0 0; padding: 0; border-top: 1px solid var(--line); list-style: none; } +.document-relations li { border-bottom: 1px solid var(--line); } +.document-relations a { display: grid; min-height: 76px; grid-template-columns: 150px 1fr auto; gap: 18px; align-items: center; } +.document-relations a > span:first-child { color: var(--faint); font-size: 11px; } +.document-relations strong { font-size: 14px; font-weight: 600; } +.document-relations a:hover strong { color: var(--signal); } + +@media (max-width: 900px) { + .public-filter-form { grid-template-columns: 1fr 1fr 1fr; } + .public-filter-form button, + .public-filter-form a { grid-row: 2; } +} + +@media (max-width: 767px) { + .public-index-page, + .public-document-page { padding-top: 60px; padding-bottom: 90px; } + .public-page-header h1, + .public-document-header h1 { font-size: 38px; } + .public-page-header > p:last-child, + .public-document-header > p { font-size: 17px; } + .public-filter-form { grid-template-columns: 1fr; margin-top: 40px; } + .public-filter-form button, + .public-filter-form a { grid-row: auto; } + .public-record-list a { grid-template-columns: 1fr auto; gap: 8px 16px; padding: 22px 0; } + .record-kind { grid-column: 1; } + .public-record-list a > div { grid-column: 1 / -1; grid-row: 2; } + .public-record-list time { grid-column: 2; grid-row: 1; } + .document-snapshot { display: block; } + .document-snapshot > div, + .document-snapshot > div + div { padding: 24px 0; border-left: 0; } + .document-snapshot > div + div { border-top: 1px solid var(--line); } + .public-document-header dl, + .document-facts { display: block; } + .public-document-header dl div, + .public-document-header dl div + div, + .document-facts div, + .document-facts div + div { padding: 13px 0; border-left: 0; } + .public-document-header dl div + div, + .document-facts div + div { border-top: 1px solid var(--line); } + .public-document-body, + .reference-purpose, + .document-relations { margin-top: 58px; } + .public-document-body > section + section { margin-top: 55px; padding-top: 50px; } + .public-document-body p, + .public-document-body li, + .reference-purpose > p:last-child { font-size: 16px; } + .question-resolution { display: block; } + .question-resolution a { margin-top: 11px; } + .document-relations a { grid-template-columns: 1fr auto; gap: 4px 12px; padding: 15px 0; } + .document-relations a > span:first-child { grid-column: 1; } + .document-relations strong { grid-column: 1; } + .document-relations a > span:last-child { grid-column: 2; grid-row: 1 / span 2; } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + } +} + +/* Shared Studio editing patterns. */ + +.studio-index-intro { padding-top: 112px; padding-bottom: 94px; } +.studio-index-intro h1 { max-width: 780px; margin: 20px 0 25px; font-size: clamp(44px, 5vw, 56px); font-weight: 660; letter-spacing: -0.054em; line-height: 1.08; } +.studio-index-intro > p:last-child { max-width: 760px; margin: 0; color: var(--ink-soft); font-size: 21px; letter-spacing: -0.022em; line-height: 1.68; } +.studio-index-list { padding-bottom: 140px; } +.studio-index-count { margin: 0 0 4px; color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 12px; } +.studio-index-list ol { margin: 0; padding: 0; border-top: 1px solid var(--line-strong); list-style: none; } +.studio-index-list li { border-bottom: 1px solid var(--line); } +.studio-index-list li > a { display: grid; grid-template-columns: 70px minmax(0, 1fr) 22px; gap: 22px; align-items: center; padding: 25px 2px 26px; } +.studio-index-step { color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 12px; } +.studio-index-copy { display: grid; gap: 5px; } +.studio-index-copy strong { font-size: 18px; font-weight: 630; letter-spacing: -0.022em; } +.studio-index-copy > span { color: var(--muted); font-size: 14px; } +.studio-index-list li > a > span:last-child { color: var(--faint); opacity: 0; transform: translateX(-4px); transition: opacity 120ms ease, transform 120ms ease; } +.studio-index-list li > a:hover strong { color: var(--signal); } +.studio-index-list li > a:hover > span:last-child { opacity: 1; transform: translateX(0); } +.studio-screen-main { min-height: calc(100vh - 188px); } +.studio-screen-header { padding-top: 75px; padding-bottom: 54px; } +.studio-screen-header nav { display: flex; gap: 9px; margin-bottom: 31px; color: var(--faint); font-size: 12px; } +.studio-screen-header nav a:hover { color: var(--signal); } +.studio-screen-header .section-kicker { margin-bottom: 13px; } +.studio-screen-header h1 { margin: 0; font-size: clamp(38px, 4vw, 48px); font-weight: 650; letter-spacing: -0.049em; line-height: 1.14; } +.studio-screen-header > p:not(.section-kicker) { max-width: 720px; margin: 18px 0 0; color: var(--ink-soft); font-size: 18px; line-height: 1.7; } +.studio-screen-header dl { display: grid; grid-template-columns: 1.5fr 1fr 1fr; gap: 30px; margin: 38px 0 0; padding-top: 21px; border-top: 1px solid var(--line); } +.studio-screen-header dt { margin-bottom: 4px; color: var(--faint); font-size: 11px; font-weight: 600; } +.studio-screen-header dd { margin: 0; color: var(--muted); font-size: 13px; } +.studio-screen-surface { min-width: 0; padding-bottom: 78px; } + +.studio-editor-toolbar { display: flex; min-height: 78px; align-items: center; justify-content: space-between; gap: 28px; padding: 13px 0; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line); } +.studio-editor-toolbar p { margin: 0; font-size: 12px; } +.studio-editor-toolbar p:first-child { color: var(--ink); font-weight: 620; } +.studio-editor-toolbar p:last-child { margin-top: 2px; color: var(--muted); } +.studio-editor-toolbar .is-dirty::before { margin-right: 8px; color: var(--warning); content: "●"; } +.studio-editor-toolbar .is-saved::before { margin-right: 8px; color: var(--evidence); content: "✓"; } +.studio-toolbar-actions, .studio-mode-tabs { display: flex; align-items: center; gap: 6px; } +.studio-mode-tabs { margin-right: 8px; padding: 3px; border: 1px solid var(--line); border-radius: 6px; background: var(--paper); } +.studio-mode-tabs button { min-height: 44px; padding: 0 13px; border: 0; border-radius: 4px; background: transparent; color: var(--muted); cursor: pointer; font-size: 12px; font-weight: 580; } +.studio-mode-tabs button[aria-selected="true"] { background: var(--signal-soft); color: var(--signal); } +.studio-editor-layout { display: grid; grid-template-columns: minmax(0, 1fr) 190px; gap: 72px; align-items: start; padding-top: 55px; } +.studio-editor-form, .studio-immediate-preview { width: min(100%, 760px); } +.studio-form-section { scroll-margin-top: 30px; } +.studio-form-section + .studio-form-section { margin-top: 72px; padding-top: 67px; border-top: 1px solid var(--line); } +.studio-section-heading { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; gap: 14px; align-items: start; margin-bottom: 28px; } +.studio-section-heading .section-kicker { padding-top: 7px; color: var(--faint); font-family: "IBM Plex Mono", monospace; } +.studio-section-heading h2 { margin: 0; font-size: 26px; font-weight: 640; letter-spacing: -0.035em; } +.studio-section-heading div > p { margin: 6px 0 0; color: var(--muted); font-size: 13px; } +.studio-field { display: grid; gap: 8px; margin-top: 20px; color: var(--ink-soft); font-size: 13px; font-weight: 600; } +.studio-control { width: 100%; min-height: 48px; padding: 11px 13px; border: 1px solid var(--line-strong); border-radius: 5px; outline: 0; background: #fff; color: var(--ink); font-size: 15px; font-weight: 430; } +textarea.studio-control { min-height: 118px; resize: vertical; line-height: 1.65; } +.studio-control:focus, .studio-markdown:focus { border-color: var(--signal); box-shadow: 0 0 0 3px var(--signal-soft); } +.studio-control--mono, .studio-path-preview { font-family: "IBM Plex Mono", monospace; } +.studio-path-preview { margin: 7px 0 0; color: var(--faint); font-size: 11px; } +.studio-summary-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; } +.studio-classification-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 28px; } +.studio-classification-grid .studio-field:last-child { grid-column: 1 / -1; } +.studio-text-action { min-height: 44px; padding: 0; border: 0; background: transparent; color: var(--signal); cursor: pointer; font-size: 12px; font-weight: 600; } +.studio-markdown { display: block; width: 100%; min-height: 520px; padding: 25px 26px; border: 1px solid var(--line-strong); border-radius: 5px; outline: 0; background: #fff; color: var(--ink); font-family: "IBM Plex Mono", monospace; font-size: 14px; line-height: 1.72; resize: vertical; } +.studio-relations-editor { margin: 0; padding: 0; border-top: 1px solid var(--line-strong); list-style: none; } +.studio-relations-editor li { display: grid; min-height: 68px; grid-template-columns: 120px minmax(0, 1fr) auto; gap: 18px; align-items: center; border-bottom: 1px solid var(--line); } +.studio-relations-editor span { color: var(--faint); font-size: 11px; } +.studio-relations-editor strong { font-size: 13px; font-weight: 590; } +.studio-relations-editor button, .studio-issue button { min-height: 44px; border: 0; background: transparent; color: var(--signal); cursor: pointer; font-size: 12px; font-weight: 600; } + +.studio-status-rail { position: sticky; top: 28px; padding-top: 5px; } +.studio-readiness-list { margin: 18px 0 0; padding: 0; border-top: 1px solid var(--line-strong); list-style: none; } +.studio-readiness-list li { display: grid; grid-template-columns: 24px 1fr; gap: 2px 7px; padding: 15px 0 16px; border-bottom: 1px solid var(--line); } +.studio-readiness-list span { grid-row: 1 / span 2; padding-top: 2px; color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 9px; } +.studio-readiness-list strong { font-size: 12px; font-weight: 610; } +.studio-readiness-list small { color: var(--faint); font-size: 10px; line-height: 1.45; } +.studio-readiness-list .is-complete span, .studio-readiness-list .is-complete strong { color: var(--evidence); } +.studio-readiness-list .needs-attention span, .studio-readiness-list .needs-attention strong { color: var(--warning); } +.studio-visibility-summary { margin: 22px 0; } +.studio-visibility-summary div { display: flex; justify-content: space-between; gap: 12px; margin-top: 7px; font-size: 11px; } +.studio-visibility-summary dt { color: var(--faint); } +.studio-visibility-summary dd { margin: 0; color: var(--ink); font-weight: 620; } +.studio-preview-note { display: flex; justify-content: space-between; gap: 24px; margin-bottom: 49px; padding: 15px 0; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line); font-size: 12px; } +.studio-preview-note span { color: var(--muted); } +.studio-immediate-preview > h2 { margin: 15px 0 20px; font-size: 38px; font-weight: 650; letter-spacing: -0.047em; line-height: 1.17; } +.studio-preview-lead { color: var(--ink-soft); font-size: 18px; line-height: 1.7; } +.studio-immediate-preview section { margin-top: 58px; } +.studio-immediate-preview h3, .studio-public-preview h3 { margin: 0 0 15px; font-size: 25px; font-weight: 630; letter-spacing: -0.032em; } +.studio-immediate-preview section p, .studio-immediate-preview li, .studio-public-preview section p { font-size: 17px; line-height: 1.78; } + +.studio-single-review, .studio-conflict { width: min(100%, 900px); margin-inline: auto; padding: 53px 0 20px; border-top: 1px solid var(--line-strong); } +.studio-single-review > header h2, .studio-conflict > header h2 { margin: 14px 0 15px; font-size: 34px; font-weight: 650; letter-spacing: -0.043em; } +.studio-single-review > header > p:last-child, .studio-conflict > header > p:last-child { max-width: 680px; margin: 0; color: var(--muted); font-size: 16px; } +.studio-validation-summary { display: grid; grid-template-columns: 1fr 1fr; margin-top: 51px; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line); } +.studio-validation-summary div { padding: 24px 30px 25px 0; } +.studio-validation-summary div + div { padding-right: 0; padding-left: 30px; border-left: 1px solid var(--line); } +.studio-validation-summary span { display: block; color: var(--faint); font-size: 11px; } +.studio-validation-summary strong { display: block; margin-top: 6px; font-size: 19px; } +.studio-validation-summary div:first-child strong { color: #b42318; } +.studio-validation-summary div:last-child strong { color: var(--warning); } +.studio-validation-summary p { margin: 5px 0 0; color: var(--muted); font-size: 12px; } +.studio-issue-group { margin-top: 57px; } +.studio-issue-group h3 { margin: 0 0 12px; font-size: 13px; font-weight: 650; } +.studio-issue { display: flex; align-items: center; justify-content: space-between; gap: 30px; padding: 21px 20px; border-top: 1px solid var(--line); background: var(--warning-soft); } +.studio-issue + .studio-issue { border-top-color: #f0dfc8; } +.studio-issue--error { background: #fef3f2; } +.studio-issue span { color: var(--warning); font-size: 10px; font-weight: 650; letter-spacing: 0.04em; text-transform: uppercase; } +.studio-issue--error span { color: #b42318; } +.studio-issue strong { display: block; margin-top: 5px; font-size: 14px; } +.studio-issue p { margin: 3px 0 0; color: var(--muted); font-size: 12px; } +.studio-issue > a { flex: 0 0 auto; color: var(--signal); font-size: 12px; font-weight: 600; } + +.studio-public-preview { overflow: hidden; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line); background: var(--paper); } +.studio-public-preview-banner { display: flex; min-height: 68px; align-items: center; justify-content: space-between; gap: 28px; padding: 12px 24px; border-bottom: 1px solid #d7dceb; background: var(--signal-soft); font-size: 11px; } +.studio-public-preview-banner div { display: grid; gap: 2px; } +.studio-public-preview-banner span, .studio-public-preview-banner p { margin: 0; color: var(--muted); } +.studio-preview-case-header { width: min(100% - 80px, 920px); padding-top: 70px; } +.studio-preview-case-header h2 { margin: 0; font-size: clamp(38px, 5vw, 48px); font-weight: 660; letter-spacing: -0.052em; line-height: 1.14; } +.studio-preview-document { padding-bottom: 90px; } +.studio-preview-document section + section { margin-top: 62px; } + +.studio-decision-sheet { width: min(100%, 640px); margin-inline: auto; padding: 50px 48px 43px; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line); background: var(--paper); } +.studio-decision-sheet h2 { margin: 15px 0 17px; font-size: 37px; font-weight: 650; letter-spacing: -0.047em; } +.studio-decision-intro { margin: 0; color: var(--ink-soft); font-size: 16px; line-height: 1.7; } +.studio-publish-comparison { margin: 38px 0 0; border-top: 1px solid var(--line-strong); } +.studio-publish-comparison div { display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: 24px; padding: 14px 0; border-bottom: 1px solid var(--line); font-size: 13px; } +.studio-publish-comparison dt { color: var(--faint); } +.studio-publish-comparison dd { margin: 0; color: var(--ink); font-weight: 600; word-break: break-all; } +.studio-change-summary { margin-top: 36px; } +.studio-change-summary h3 { margin: 0 0 11px; font-size: 14px; } +.studio-change-summary ul { margin: 0; padding-left: 19px; color: var(--muted); font-size: 13px; line-height: 1.9; } +.studio-warning-check { display: flex; gap: 12px; margin-top: 33px; padding: 17px 18px; border: 1px solid #ead8be; background: var(--warning-soft); cursor: pointer; } +.studio-warning-check input { width: 18px; height: 18px; margin-top: 2px; accent-color: var(--signal); } +.studio-warning-check span { display: grid; gap: 3px; } +.studio-warning-check strong { font-size: 13px; } +.studio-warning-check small { color: var(--muted); font-size: 11px; } +.studio-decision-actions, .studio-conflict-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 40px; } +.studio-preserved-note, .studio-unsaved-summary { margin-top: 37px; padding: 21px 0; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line); } +.studio-preserved-note strong, .studio-unsaved-summary strong { display: block; font-size: 15px; } +.studio-preserved-note p, .studio-unsaved-summary span { margin: 5px 0 0; color: var(--muted); font-size: 12px; } +.studio-unsaved-summary span { display: block; margin: 0 0 5px; } +.studio-decision-actions--three { justify-content: space-between; } +.studio-text-danger { min-height: 44px; padding: 0 12px; border: 0; background: transparent; color: #b42318; cursor: pointer; font-size: 13px; font-weight: 600; } + +.studio-diff-heading { display: flex; justify-content: space-between; gap: 24px; margin-top: 48px; padding-bottom: 13px; border-bottom: 1px solid var(--line-strong); font-size: 12px; } +.studio-diff-heading span { color: #b42318; font-weight: 620; } +.studio-diff-grid { display: grid; grid-template-columns: 1fr 1fr; } +.studio-diff-grid section { min-height: 270px; padding: 27px 31px 31px 0; } +.studio-diff-grid section + section { padding-right: 0; padding-left: 31px; border-left: 1px solid var(--line); } +.studio-diff-grid section > p:first-child { margin: 0 0 20px; color: var(--faint); font-size: 11px; font-weight: 650; } +.studio-diff-grid h3 { margin: 0; font-size: 18px; font-weight: 630; line-height: 1.5; } +.studio-diff-grid h3 + p { margin: 22px 0 0; color: var(--muted); font-size: 14px; line-height: 1.75; } +@media (max-width: 1179px) { + .studio-editor-layout { display: block; } + .studio-editor-form, .studio-immediate-preview { margin-inline: auto; } + .studio-status-rail { position: static; width: min(100%, 760px); margin: 72px auto 0; padding-top: 36px; border-top: 1px solid var(--line-strong); } + .studio-readiness-list { display: grid; grid-template-columns: 1fr 1fr; } + .studio-readiness-list li:nth-child(odd) { padding-right: 22px; } + .studio-readiness-list li:nth-child(even) { padding-left: 22px; border-left: 1px solid var(--line); } +} + +@media (max-width: 767px) { + .studio-index-intro { padding-top: 78px; padding-bottom: 72px; } + .studio-index-intro h1 { margin-top: 15px; font-size: 38px; } + .studio-index-intro > p:last-child { font-size: 17px; } + .studio-index-list { padding-bottom: 100px; } + .studio-index-list li > a { grid-template-columns: 38px minmax(0, 1fr) 16px; gap: 13px; padding: 22px 0 23px; } + .studio-index-copy strong { font-size: 16px; } + .studio-index-copy > span { font-size: 13px; line-height: 1.55; } + .studio-index-list li > a > span:last-child { opacity: 1; transform: none; } + .studio-screen-header { padding-top: 55px; padding-bottom: 42px; } + .studio-screen-header nav { margin-bottom: 24px; } + .studio-screen-header h1 { font-size: 34px; } + .studio-screen-header > p:not(.section-kicker) { font-size: 16px; } + .studio-screen-header dl { display: block; margin-top: 28px; } + .studio-screen-header dl div { display: grid; grid-template-columns: 70px 1fr; gap: 10px; margin-top: 8px; } + .studio-screen-header dt { margin: 0; } + .studio-editor-toolbar { position: sticky; z-index: 10; top: 0; display: block; padding: 12px 0 14px; background: var(--canvas); } + .studio-toolbar-actions { justify-content: space-between; margin-top: 11px; } + .studio-mode-tabs { min-width: 0; margin: 0; } + .studio-mode-tabs button { min-height: 44px; padding-inline: 10px; } + .studio-editor-layout { padding-top: 39px; } + .studio-form-section + .studio-form-section { margin-top: 54px; padding-top: 50px; } + .studio-section-heading { grid-template-columns: 25px minmax(0, 1fr); gap: 10px; } + .studio-section-heading > .studio-text-action { grid-column: 2; width: fit-content; } + .studio-section-heading h2 { font-size: 23px; } + .studio-control { font-size: 16px; } + .studio-summary-fields, .studio-classification-grid { display: block; } + .studio-markdown { min-height: 440px; padding: 19px 17px; font-size: 13px; } + .studio-relations-editor li { grid-template-columns: 1fr auto; gap: 3px 10px; padding: 15px 0; } + .studio-relations-editor li span, .studio-relations-editor li strong { grid-column: 1; } + .studio-relations-editor li strong { line-height: 1.55; } + .studio-relations-editor li button { grid-row: 1 / span 2; grid-column: 2; } + .studio-readiness-list { display: block; } + .studio-readiness-list li:nth-child(odd), .studio-readiness-list li:nth-child(even) { padding-right: 0; padding-left: 0; border-left: 0; } + .studio-preview-note { display: grid; gap: 3px; margin-bottom: 39px; } + .studio-immediate-preview > h2 { font-size: 31px; } + .studio-preview-lead { font-size: 16px; } + .studio-immediate-preview section p, .studio-immediate-preview li, .studio-public-preview section p { font-size: 16px; } + .studio-single-review, .studio-conflict { padding-top: 40px; } + .studio-single-review > header h2, .studio-conflict > header h2 { font-size: 30px; } + .studio-validation-summary { display: block; margin-top: 39px; } + .studio-validation-summary div, .studio-validation-summary div + div { padding: 20px 0; border-left: 0; } + .studio-validation-summary div + div { border-top: 1px solid var(--line); } + .studio-issue { display: block; padding: 19px 17px; } + .studio-issue > a, .studio-issue > button { display: inline-flex; min-height: 44px; align-items: center; margin-top: 10px; } + .studio-public-preview-banner { display: block; padding: 14px 16px; } + .studio-public-preview-banner p { margin-top: 7px; line-height: 1.45; } + .studio-preview-case-header { width: calc(100% - 40px); padding-top: 50px; } + .studio-preview-case-header h2 { font-size: 32px; } + .studio-preview-document { padding-inline: 20px; padding-bottom: 70px; } + .studio-decision-sheet { width: 100%; padding: 39px 0 31px; background: transparent; } + .studio-decision-sheet h2 { font-size: 32px; } + .studio-publish-comparison div { grid-template-columns: 110px minmax(0, 1fr); gap: 14px; } + .studio-decision-actions, .studio-conflict-actions { flex-wrap: wrap; } + .studio-decision-actions--three { display: grid; grid-template-columns: 1fr 1fr; } + .studio-diff-heading { display: grid; gap: 4px; } + .studio-diff-grid { display: block; } + .studio-diff-grid section, .studio-diff-grid section + section { min-height: 0; padding: 23px 0; border-left: 0; } + .studio-diff-grid section + section { border-top: 1px solid var(--line); } +} diff --git a/src/features/tech-log/presentation/styles/publication-flow.module.css b/src/features/tech-log/presentation/styles/publication-flow.module.css new file mode 100644 index 0000000..d077231 --- /dev/null +++ b/src/features/tech-log/presentation/styles/publication-flow.module.css @@ -0,0 +1,533 @@ +.page, +.snapshotPage { + min-width: 0; +} + +.heading { + max-width: 780px; + padding-bottom: 44px; + border-bottom: 1px solid var(--line-strong); +} + +.heading h1, +.routeState h1 { + margin: 0 0 16px; + font-size: clamp(36px, 6vw, 62px); + line-height: 1.08; + letter-spacing: -0.045em; + overflow-wrap: anywhere; +} + +.heading > p:last-child, +.routeState > p { + color: var(--muted); + line-height: 1.75; +} + +.eyebrow { + margin: 0 0 13px; + color: var(--muted); + font-family: "IBM Plex Mono", monospace; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.loading { + min-height: 220px; + color: var(--muted); +} + +.routeState, +.errorState, +.emptyState { + max-width: 740px; + padding-block: 48px; +} + +.routeState a, +.routeState button, +.errorState button { + display: inline-flex; + min-height: 44px; + align-items: center; + justify-content: center; + margin-top: 18px; + padding-inline: 16px; + border: 1px solid var(--line-strong); + border-radius: 5px; + background: var(--paper); + color: var(--signal); + font-weight: 650; +} + +.summary { + margin-top: 30px; + border-block: 1px solid var(--line); +} + +.summary dl { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + margin: 0; +} + +.summary dl > div { + min-width: 0; + padding: 20px; + border-right: 1px solid var(--line); +} + +.summary dl > div:last-child { + border-right: 0; +} + +.summary dt, +.snapshotToolbar dt { + color: var(--muted); + font-size: 11px; +} + +.summary dd, +.snapshotToolbar dd { + margin: 7px 0 0; + font-size: 14px; + overflow-wrap: anywhere; +} + +.changeSummary, +.publishAction, +.blocked, +.gateReady { + max-width: 850px; + padding-block: 42px; + border-bottom: 1px solid var(--line); +} + +.changeSummary h2, +.publishAction h2, +.blocked h2, +.gateReady h2, +.errorState h2, +.emptyState h2 { + margin: 0 0 12px; + font-size: 27px; + line-height: 1.3; + overflow-wrap: anywhere; +} + +.changeSummary p, +.publishAction > p, +.blocked p, +.gateReady p, +.errorState p, +.emptyState p { + color: var(--muted); + line-height: 1.75; +} + +.blocked { + margin-top: 30px; + padding-inline: 22px; + border-left: 3px solid #9a611e; + background: #fff9f0; +} + +.blocked a, +.gateReady a { + display: inline-flex; + min-height: 44px; + align-items: center; + color: var(--signal); + font-weight: 650; +} + +.gateReady { + margin-top: 30px; + padding-inline: 22px; + border-left: 3px solid #2d7257; + background: #f4fbf7; +} + +.warningGroup { + margin: 26px 0 0; + padding: 0; + border: 0; + border-top: 1px solid var(--line-strong); +} + +.warningGroup legend { + padding: 22px 0 0; + font-size: 17px; + font-weight: 700; +} + +.warningGroup > p, +.noWarnings { + margin: 8px 0 16px; + color: var(--muted); + font-size: 13px; +} + +.warningList { + border-bottom: 1px solid var(--line); +} + +.warningItem { + display: grid; + grid-template-columns: 24px minmax(0, 1fr); + gap: 13px; + min-height: 66px; + align-items: start; + padding-block: 16px; + border-top: 1px solid var(--line); + cursor: pointer; +} + +.warningItem input { + width: 20px; + height: 20px; + margin-top: 2px; + accent-color: var(--signal); +} + +.warningItem span { + display: grid; + min-width: 0; + gap: 4px; +} + +.warningItem strong { + font-family: "IBM Plex Mono", monospace; + font-size: 12px; + overflow-wrap: anywhere; +} + +.warningItem small { + color: var(--muted); + line-height: 1.6; +} + +.actions, +.dialogActions, +.rowActions { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.actions { + justify-content: flex-end; + margin-top: 28px; +} + +.actions a, +.actions button, +.rowActions a, +.rowActions button, +.filters button, +.dialogActions button { + display: inline-flex; + min-height: 44px; + align-items: center; + justify-content: center; + padding-inline: 15px; + border: 1px solid var(--line-strong); + border-radius: 5px; + background: var(--paper); + color: var(--ink); + font: inherit; + font-size: 13px; + font-weight: 650; +} + +.actions button, +.filters button { + border-color: var(--signal); + background: var(--signal); + color: #fff; +} + +.actions button:disabled, +.dialogActions button:disabled { + opacity: 0.52; + cursor: not-allowed; +} + +.error { + margin-top: 18px; + padding: 13px 15px; + border-left: 3px solid #a13b31; + background: #fff7f4; + color: #75271f !important; +} + +.success { + margin-top: 22px; + padding: 13px 15px; + border-left: 3px solid #2d7257; + background: #f4fbf7; + color: #215943; +} + +.filters { + display: grid; + grid-template-columns: minmax(260px, 1fr) 190px auto; + gap: 14px; + align-items: end; + padding-block: 28px; + border-bottom: 1px solid var(--line-strong); +} + +.filters label { + display: grid; + min-width: 0; + gap: 7px; + color: var(--muted); + font-size: 12px; + font-weight: 650; +} + +.filters input, +.filters select { + width: 100%; + min-width: 0; + min-height: 44px; + padding-inline: 12px; + border: 1px solid var(--line-strong); + border-radius: 5px; + background: var(--paper); + color: var(--ink); + font: inherit; +} + +.historyList { + margin: 28px 0 0; + padding: 0; + border-top: 1px solid var(--line-strong); + list-style: none; +} + +.historyRow { + display: grid; + grid-template-columns: 100px minmax(260px, 1fr) 180px minmax(160px, auto); + gap: 20px; + align-items: start; + padding-block: 25px; + border-bottom: 1px solid var(--line); +} + +.eventType, +.kind { + margin: 3px 0 0; + color: var(--muted); + font-family: "IBM Plex Mono", monospace; + font-size: 11px; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.eventMain { + min-width: 0; +} + +.eventMain h2 { + margin: 5px 0 0; + font-size: 17px; + line-height: 1.5; + overflow-wrap: anywhere; +} + +.eventMain > p:last-child, +.historyRow time { + margin: 6px 0 0; + color: var(--muted); + font-size: 12px; + line-height: 1.6; + overflow-wrap: anywhere; +} + +.rowActions { + justify-content: flex-end; +} + +.rowActions a, +.rowActions button { + color: var(--signal); +} + +.dialog { + width: min(580px, calc(100% - 32px)); + max-height: calc(100vh - 32px); + padding: 0; + overflow: auto; + border: 1px solid var(--line-strong); + border-radius: 8px; + background: var(--paper); + color: var(--ink); +} + +.dialog::backdrop { + background: rgba(23, 24, 27, 0.5); +} + +.dialogBody { + padding: 30px; +} + +.dialogBody h2 { + margin: 0 0 12px; + font-size: 28px; +} + +.dialogBody > p { + line-height: 1.7; +} + +.preservedNote { + margin-top: 22px; + padding: 17px; + border-left: 3px solid #2d7257; + background: #f4fbf7; +} + +.preservedNote p { + margin: 6px 0 0; + color: var(--muted); + line-height: 1.6; +} + +.dialogActions { + justify-content: flex-end; + margin-top: 28px; +} + +.dialogActions button:last-child { + border-color: #9b342d; + background: #9b342d; + color: #fff; +} + +.snapshotToolbar { + display: grid; + grid-template-columns: minmax(220px, 1fr) minmax(420px, 1.4fr) auto; + gap: 24px; + align-items: center; + padding-bottom: 28px; + border-bottom: 1px solid var(--line-strong); +} + +.snapshotTitle { + margin: 0; + font-size: 21px; + font-weight: 700; +} + +.snapshotToolbar dl { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + margin: 0; +} + +.snapshotToolbar a { + display: inline-flex; + min-height: 44px; + align-items: center; + color: var(--signal); + font-size: 13px; + font-weight: 650; +} + +.snapshotDocument { + min-width: 0; + padding-top: 30px; +} + +.snapshotDocument :global(.public-record-embedded) { + width: 100%; +} + +@media (max-width: 980px) { + .summary dl { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .summary dl > div:nth-child(2) { + border-right: 0; + } + + .summary dl > div:nth-child(-n + 2) { + border-bottom: 1px solid var(--line); + } + + .historyRow { + grid-template-columns: 84px minmax(0, 1fr) 170px; + } + + .rowActions { + grid-column: 2 / -1; + } + + .snapshotToolbar { + grid-template-columns: minmax(0, 1fr) auto; + } + + .snapshotToolbar dl { + grid-column: 1 / -1; + grid-row: 2; + } +} + +@media (max-width: 767px) { + .heading { + padding-bottom: 32px; + } + + .summary dl, + .filters, + .historyRow, + .snapshotToolbar, + .snapshotToolbar dl { + grid-template-columns: minmax(0, 1fr); + } + + .summary dl > div, + .summary dl > div:nth-child(2) { + border-right: 0; + border-bottom: 1px solid var(--line); + } + + .summary dl > div:last-child { + border-bottom: 0; + } + + .filters button, + .actions a, + .actions button, + .rowActions a, + .rowActions button, + .dialogActions button { + width: 100%; + } + + .historyRow { + gap: 8px; + } + + .rowActions, + .snapshotToolbar dl { + grid-column: 1; + } + + .rowActions, + .actions, + .dialogActions { + align-items: stretch; + flex-direction: column; + } + + .dialogBody { + padding: 24px 20px; + } +} diff --git a/src/features/tech-log/presentation/styles/studio-editor.css b/src/features/tech-log/presentation/styles/studio-editor.css new file mode 100644 index 0000000..d042843 --- /dev/null +++ b/src/features/tech-log/presentation/styles/studio-editor.css @@ -0,0 +1,92 @@ +.studio-app .studio-editor-page, +.studio-app .studio-editor-layout, +.studio-app .studio-editor-workspace, +.studio-app .studio-editor-workspace > [role="tabpanel"] { min-width: 0; } + +.studio-app .studio-editor-loading { min-height: 240px; padding-block: 56px; color: var(--muted); } +.studio-app .studio-editor-tabs { display: flex; gap: 28px; border-bottom: 1px solid var(--line-strong); } +.studio-app .studio-editor-tabs button { position: relative; min-width: 112px; min-height: 44px; padding: 0; border: 0; background: transparent; color: var(--muted); font: inherit; font-weight: 650; } +.studio-app .studio-editor-tabs button[aria-selected="true"] { color: var(--ink); } +.studio-app .studio-editor-tabs button[aria-selected="true"]::after { position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; background: var(--signal); content: ""; } + +.studio-app .studio-editor-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(240px, 280px); gap: 64px; align-items: start; padding-top: 40px; } +.studio-app .studio-editor-heading { padding-bottom: 36px; border-bottom: 1px solid var(--line-strong); } +.studio-app .studio-editor-heading h1 { margin: 0; font-size: clamp(38px, 5vw, 62px); line-height: 1.05; letter-spacing: -0.045em; } +.studio-app .studio-editor-heading > p:last-child { max-width: 780px; margin: 18px 0 0; color: var(--muted); font-size: 17px; line-height: 1.65; overflow-wrap: anywhere; } +.studio-app .studio-editor-section { padding: 48px 0; border-bottom: 1px solid var(--line); } +.studio-app .studio-editor-section-heading { margin-bottom: 24px; } +.studio-app .studio-editor-section-heading .studio-eyebrow { margin-bottom: 8px; } +.studio-app .studio-editor-section-heading h2, +.studio-app .studio-document-status-rail h2, +.studio-app .studio-preview-error h2 { margin: 0; font-size: 25px; letter-spacing: -0.03em; } + +.studio-app .studio-field-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; } +.studio-app .studio-field, +.studio-app .studio-ordered-item > label, +.studio-app .studio-relation-item > label { display: grid; min-width: 0; gap: 8px; color: var(--muted); font-size: 12px; font-weight: 650; } +.studio-app .studio-field--wide { grid-column: 1 / -1; } +.studio-app .studio-field input, +.studio-app .studio-field select, +.studio-app .studio-field textarea, +.studio-app .studio-ordered-item input, +.studio-app .studio-ordered-item textarea, +.studio-app .studio-relation-item input, +.studio-app .studio-relation-item select { width: 100%; min-width: 0; min-height: 44px; padding: 10px 12px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--paper); color: var(--ink); font: inherit; font-size: 15px; font-weight: 450; line-height: 1.55; } +.studio-app .studio-field textarea, +.studio-app .studio-ordered-item textarea { min-height: 112px; resize: vertical; } +.studio-app .studio-field .studio-markdown-field { min-height: 420px; font-family: "IBM Plex Mono", monospace; font-size: 13px; } + +.studio-app .studio-ordered-list, +.studio-app .studio-resolution-fields { min-width: 0; margin: 34px 0 0; padding: 0; border: 0; } +.studio-app .studio-ordered-list legend, +.studio-app .studio-resolution-fields legend { margin-bottom: 14px; font-size: 17px; font-weight: 700; } +.studio-app .studio-ordered-list > p { margin: 0 0 16px; color: var(--muted); font-size: 13px; } +.studio-app .studio-ordered-item, +.studio-app .studio-relation-item { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; padding: 22px 0; border-top: 1px solid var(--line); } +.studio-app .studio-item-actions { display: flex; flex-wrap: wrap; gap: 8px; } +.studio-app .studio-item-actions button, +.studio-app .studio-add-item, +.studio-app .studio-document-status-rail button { min-height: 44px; padding-inline: 13px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--paper); color: var(--ink); font-weight: 650; } +.studio-app .studio-item-actions button:disabled, +.studio-app .studio-add-item:disabled { opacity: 0.45; } +.studio-app .studio-add-item { margin-top: 12px; } +.studio-app .studio-resolution-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; } +.studio-app .studio-resolution-fields legend { grid-column: 1 / -1; } + +.studio-app .studio-document-status-rail { position: sticky; top: 28px; min-width: 0; padding: 24px 0; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line-strong); } +.studio-app .studio-document-status-rail .studio-eyebrow { margin-bottom: 8px; } +.studio-app .studio-editor-status { margin: 20px 0; font-weight: 700; } +.studio-app .studio-editor-status--dirty, +.studio-app .studio-editor-status--conflict, +.studio-app .studio-editor-conflict { color: #8f2f27; } +.studio-app .studio-editor-status--clean { color: #166748; } +.studio-app .studio-document-status-rail dl { display: grid; gap: 12px; margin: 0 0 20px; } +.studio-app .studio-document-status-rail dl div { display: flex; justify-content: space-between; gap: 16px; } +.studio-app .studio-document-status-rail dt { color: var(--muted); font-size: 12px; } +.studio-app .studio-document-status-rail dd { margin: 0; font-size: 13px; } +.studio-app .studio-document-status-rail button { width: 100%; border-color: var(--signal); background: var(--signal); color: #fff; } +.studio-app .studio-document-status-rail button:disabled { border-color: var(--line-strong); background: var(--paper); color: var(--muted); } +.studio-app .studio-document-status-rail > p:last-child { margin: 16px 0 0; color: var(--muted); font-size: 12px; line-height: 1.65; } + +.studio-app .studio-instant-preview { min-width: 0; overflow: clip; border: 1px solid var(--line); } +.studio-app .studio-instant-preview .public-record-embedded { width: 100%; max-width: none; margin: 0; padding: clamp(24px, 5vw, 56px); } +.studio-app .studio-preview-error { padding: 48px 0; border-bottom: 1px solid var(--line); } +.studio-app .studio-preview-error ul { margin: 20px 0 0; padding-left: 20px; color: #8f2f27; line-height: 1.7; overflow-wrap: anywhere; } + +@media (max-width: 1024px) { + .studio-app .studio-editor-layout { grid-template-columns: minmax(0, 1fr); gap: 40px; } + .studio-app .studio-document-status-rail { position: static; } +} + +@media (max-width: 767px) { + .studio-app .studio-editor-heading { padding-bottom: 28px; } + .studio-app .studio-editor-tabs { gap: 18px; } + .studio-app .studio-editor-tabs button { flex: 1; min-width: 0; } + .studio-app .studio-editor-layout { padding-top: 28px; } + .studio-app .studio-field-grid, + .studio-app .studio-resolution-fields { grid-template-columns: minmax(0, 1fr); } + .studio-app .studio-field--wide, + .studio-app .studio-resolution-fields legend { grid-column: auto; } + .studio-app .studio-item-actions { display: grid; grid-template-columns: minmax(0, 1fr); } + .studio-app .studio-instant-preview .public-record-embedded { padding: 22px 16px; } +} diff --git a/src/features/tech-log/presentation/styles/studio.css b/src/features/tech-log/presentation/styles/studio.css new file mode 100644 index 0000000..f1c0d13 --- /dev/null +++ b/src/features/tech-log/presentation/styles/studio.css @@ -0,0 +1,155 @@ +.studio-app { min-height: 100vh; color: var(--ink); } +.studio-app .studio-skip-link { position: fixed; z-index: 100; top: 12px; left: 16px; padding: 10px 14px; border: 1px solid var(--line-strong); border-radius: 6px; background: var(--paper); transform: translateY(-160%); } +.studio-app .studio-skip-link:focus { transform: translateY(0); } +.studio-app .studio-header { border-bottom: 1px solid var(--line); background: rgba(252, 252, 251, 0.94); } +.studio-app .studio-header-inner, +.studio-app .studio-mobile-navigation, +.studio-app .studio-main { width: min(1180px, calc(100% - 80px)); margin-inline: auto; } +.studio-app .studio-header-inner { display: flex; min-height: 76px; align-items: center; justify-content: space-between; gap: 40px; } +.studio-app .studio-wordmark { display: inline-flex; min-height: 44px; align-items: center; font-size: 17px; font-weight: 700; letter-spacing: -0.02em; } +.studio-app .studio-wordmark span { margin-left: 5px; color: var(--signal); font-family: "IBM Plex Mono", monospace; font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; } +.studio-app .studio-desktop-navigation nav, +.studio-app .studio-mobile-navigation nav { display: flex; align-items: center; gap: 26px; } +.studio-app .studio-desktop-navigation a, +.studio-app .studio-mobile-navigation a { display: inline-flex; min-height: 44px; align-items: center; color: var(--muted); font-size: 14px; } +.studio-app nav a[aria-current="page"] { color: var(--ink); font-weight: 650; } +.studio-app .studio-menu-trigger { display: none; min-height: 44px; padding-inline: 12px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--paper); color: var(--ink); } +.studio-app .studio-mobile-navigation { padding-bottom: 16px; } +.studio-app .studio-main { padding-block: 72px 120px; } +.studio-app .studio-page-heading { max-width: 760px; padding-bottom: 52px; } +.studio-app .studio-page-heading h1, +.studio-app .studio-route-state h1 { margin: 0 0 18px; font-size: clamp(36px, 6vw, 64px); line-height: 1.08; letter-spacing: -0.045em; } +.studio-app .studio-eyebrow { margin: 0 0 14px; color: var(--muted); font-family: "IBM Plex Mono", monospace; font-size: 12px; letter-spacing: 0.08em; } +.studio-app .studio-overview-list { border-top: 1px solid var(--line-strong); } +.studio-app .studio-overview-list section { display: grid; grid-template-columns: 130px minmax(180px, 0.8fr) minmax(260px, 1.4fr) auto; align-items: baseline; gap: 24px; padding-block: 28px; border-bottom: 1px solid var(--line); } +.studio-app .studio-overview-list h2, +.studio-app .studio-overview-list p { margin: 0; } +.studio-app .studio-overview-list a, +.studio-app .studio-route-state > a { color: var(--signal); font-weight: 650; } +.studio-app .studio-route-state { max-width: 720px; padding-block: 48px; } +.studio-app .studio-route-state button { min-height: 44px; margin-top: 18px; padding-inline: 16px; border: 1px solid var(--signal); border-radius: 5px; background: var(--signal); color: #fff; } +.studio-app .studio-loading { min-height: 180px; } +.studio-app .studio-visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } +.studio-app .studio-unsaved-dialog { width: min(560px, calc(100% - 32px)); padding: 0; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--paper); color: var(--ink); } +.studio-app .studio-unsaved-dialog::backdrop { background: rgba(23, 24, 27, 0.48); } +.studio-app .studio-dialog-body { padding: 30px; } +.studio-app .studio-dialog-body h2 { margin: 0 0 12px; font-size: 26px; } +.studio-app .studio-dialog-error { color: #8f2f27; } +.studio-app .studio-dialog-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 28px; } +.studio-app .studio-dialog-actions button { min-height: 44px; padding-inline: 14px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--paper); color: var(--ink); } +.studio-app .studio-dialog-actions button:last-child { border-color: var(--signal); background: var(--signal); color: #fff; } + +.studio-app .studio-page-top { display: flex; align-items: flex-end; justify-content: space-between; gap: 36px; border-bottom: 1px solid var(--line-strong); } +.studio-app .studio-page-top .studio-page-heading { padding-bottom: 42px; } +.studio-app .studio-primary-action, +.studio-app .studio-primary-button, +.studio-app .studio-secondary-button, +.studio-app .studio-document-tools button, +.studio-app .studio-empty-state a { display: inline-flex; min-height: 44px; align-items: center; justify-content: center; padding-inline: 16px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--paper); color: var(--ink); font-weight: 650; } +.studio-app .studio-primary-action, +.studio-app .studio-primary-button { margin-bottom: 42px; border-color: var(--signal); background: var(--signal); color: #fff; } +.studio-app .studio-summary-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin-top: 28px; border-block: 1px solid var(--line); } +.studio-app .studio-summary-grid div { display: grid; gap: 9px; padding: 22px; border-right: 1px solid var(--line); } +.studio-app .studio-summary-grid div:last-child { border-right: 0; } +.studio-app .studio-summary-grid span { color: var(--muted); font-size: 12px; } +.studio-app .studio-summary-grid strong { font-size: 24px; letter-spacing: -0.03em; } +.studio-app .studio-work-section { padding-top: 54px; } +.studio-app .studio-section-title { display: flex; min-height: 52px; align-items: center; justify-content: space-between; gap: 20px; border-bottom: 1px solid var(--line-strong); } +.studio-app .studio-section-title h2 { margin: 0; font-size: 24px; } +.studio-app .studio-section-title a { display: inline-flex; min-height: 44px; align-items: center; color: var(--signal); font-size: 13px; font-weight: 650; } +.studio-app .studio-work-list, +.studio-app .studio-document-list { border-bottom: 1px solid var(--line); } +.studio-app .studio-work-row { display: grid; grid-template-columns: 110px minmax(0, 1fr) 130px; gap: 24px; align-items: start; padding-block: 24px; border-top: 1px solid var(--line); } +.studio-app .studio-work-list > :first-child { border-top: 0; } +.studio-app .studio-row-label { margin: 3px 0 0; color: var(--muted); font-family: "IBM Plex Mono", monospace; font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase; } +.studio-app .studio-work-row h3 { margin: 0; font-size: 17px; line-height: 1.5; overflow-wrap: anywhere; } +.studio-app .studio-work-row h3 a { display: inline-flex; min-height: 44px; align-items: flex-start; } +.studio-app .studio-work-row div p, +.studio-app .studio-work-row time { margin: 5px 0 0; color: var(--muted); font-size: 13px; } +.studio-app .studio-empty-inline { margin: 0; padding-block: 26px; color: var(--muted); } +.studio-app .studio-screen-error { margin-top: 26px; padding: 16px; border-left: 3px solid #a13b31; background: #fff7f4; color: #75271f; } + +.studio-app .studio-document-tools { display: grid; grid-template-columns: minmax(280px, 1fr) 180px 180px; gap: 16px; padding-block: 28px; border-bottom: 1px solid var(--line-strong); } +.studio-app .studio-document-tools form, +.studio-app .studio-document-tools label { display: grid; gap: 8px; color: var(--muted); font-size: 12px; font-weight: 650; } +.studio-app .studio-document-tools form div { display: flex; gap: 8px; } +.studio-app .studio-document-tools input, +.studio-app .studio-document-tools select { width: 100%; min-width: 0; min-height: 44px; padding-inline: 12px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--paper); color: var(--ink); } +.studio-app .studio-document-tools input { flex: 1; } +.studio-app .studio-result-count { margin: 24px 0 10px; color: var(--muted); font-size: 13px; } +.studio-app .studio-document-row { display: grid; grid-template-columns: 105px minmax(240px, 1fr) minmax(360px, 0.9fr); gap: 24px; align-items: start; padding-block: 26px; border-top: 1px solid var(--line); } +.studio-app .studio-document-list > :first-child { border-top: 0; } +.studio-app .studio-document-title h2 { margin: 0; font-size: 17px; line-height: 1.5; overflow-wrap: anywhere; } +.studio-app .studio-document-title h2 a { display: inline-flex; min-height: 44px; align-items: flex-start; } +.studio-app .studio-document-title p { margin: 4px 0 0; color: var(--muted); font-size: 13px; } +.studio-app .studio-document-row dl { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; margin: 0; } +.studio-app .studio-document-row dt { color: var(--muted); font-size: 11px; } +.studio-app .studio-document-row dd { margin: 5px 0 0; font-size: 13px; overflow-wrap: anywhere; } +.studio-app .studio-secondary-button { margin-top: 24px; } +.studio-app .studio-empty-state { padding-block: 56px; border-bottom: 1px solid var(--line); } +.studio-app .studio-empty-state h2 { margin: 0; font-size: 25px; } +.studio-app .studio-empty-state p { margin: 12px 0 22px; color: var(--muted); } + +.studio-app .studio-type-list { margin: 0; padding: 0; border: 0; border-top: 1px solid var(--line-strong); } +.studio-app .studio-type-list legend { padding: 0 0 16px; font-size: 20px; font-weight: 700; } +.studio-app .studio-type-list label { display: grid; grid-template-columns: 32px 140px minmax(260px, 1fr) minmax(220px, 0.8fr); gap: 18px; min-height: 88px; align-items: center; padding-block: 20px; border-bottom: 1px solid var(--line); cursor: pointer; } +.studio-app .studio-type-list input { width: 20px; height: 20px; margin: 0; } +.studio-app .studio-type-list strong { font-size: 18px; } +.studio-app .studio-type-list span, +.studio-app .studio-type-list small { color: var(--muted); line-height: 1.6; } +.studio-app .studio-create-footer { display: flex; align-items: center; gap: 20px; margin-top: 28px; } +.studio-app .studio-create-footer .studio-primary-button { margin: 0; } +.studio-app .studio-create-footer p { margin: 0; color: var(--muted); font-size: 13px; } +.studio-app .studio-primary-button:disabled { opacity: 0.55; cursor: wait; } + +@media (max-width: 900px) { + .studio-app .studio-overview-list section { grid-template-columns: 110px minmax(0, 1fr); } + .studio-app .studio-overview-list section > p:nth-of-type(2), + .studio-app .studio-overview-list section > a { grid-column: 2; } + .studio-app .studio-summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .studio-app .studio-summary-grid div:nth-child(2) { border-right: 0; } + .studio-app .studio-summary-grid div:nth-child(-n + 2) { border-bottom: 1px solid var(--line); } + .studio-app .studio-document-tools { grid-template-columns: minmax(0, 1fr) repeat(2, 160px); } + .studio-app .studio-document-row { grid-template-columns: 90px minmax(0, 1fr); } + .studio-app .studio-document-row dl { grid-column: 2; } +} + +@media (max-width: 767px) { + .studio-app .studio-header-inner, + .studio-app .studio-mobile-navigation, + .studio-app .studio-main { width: min(100% - 32px, 1180px); } + .studio-app .studio-header-inner { min-height: 68px; gap: 16px; } + .studio-app .studio-desktop-navigation { display: none; } + .studio-app .studio-menu-trigger { display: inline-flex; align-items: center; } + .studio-app .studio-mobile-navigation nav { align-items: stretch; flex-direction: column; gap: 2px; } + .studio-app .studio-mobile-navigation a { width: 100%; } + .studio-app .studio-main { padding-block: 48px 88px; } + .studio-app .studio-overview-list section { display: block; } + .studio-app .studio-overview-list h2 { margin-bottom: 8px; } + .studio-app .studio-overview-list a { display: inline-flex; min-height: 44px; margin-top: 12px; align-items: center; } + .studio-app .studio-dialog-body { padding: 24px 20px; } + .studio-app .studio-dialog-actions { align-items: stretch; flex-direction: column; } + .studio-app .studio-page-top { align-items: flex-start; flex-direction: column; gap: 0; } + .studio-app .studio-page-top .studio-page-heading { padding-bottom: 24px; } + .studio-app .studio-primary-action { width: 100%; margin: 0 0 32px; } + .studio-app .studio-work-section { padding-top: 42px; } + .studio-app .studio-work-row { grid-template-columns: 78px minmax(0, 1fr); gap: 12px; } + .studio-app .studio-work-row time { grid-column: 2; } + .studio-app .studio-document-tools { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .studio-app .studio-document-tools form { grid-column: 1 / -1; } + .studio-app .studio-document-row { grid-template-columns: 74px minmax(0, 1fr); gap: 12px; } + .studio-app .studio-document-row dl { grid-column: 2; grid-template-columns: repeat(2, minmax(0, 1fr)); } + .studio-app .studio-type-list label { grid-template-columns: 30px minmax(0, 1fr); gap: 7px 12px; } + .studio-app .studio-type-list span, + .studio-app .studio-type-list small { grid-column: 2; } + .studio-app .studio-create-footer { align-items: stretch; flex-direction: column; } +} + +@media (max-width: 420px) { + .studio-app .studio-summary-grid { grid-template-columns: minmax(0, 1fr); } + .studio-app .studio-summary-grid div { border-right: 0; border-bottom: 1px solid var(--line); } + .studio-app .studio-summary-grid div:last-child { border-bottom: 0; } + .studio-app .studio-document-tools { grid-template-columns: minmax(0, 1fr); } + .studio-app .studio-document-tools form { grid-column: 1; } + .studio-app .studio-document-row dl { grid-template-columns: minmax(0, 1fr); } +} diff --git a/src/features/tech-log/presentation/styles/workflow.module.css b/src/features/tech-log/presentation/styles/workflow.module.css new file mode 100644 index 0000000..7b0e654 --- /dev/null +++ b/src/features/tech-log/presentation/styles/workflow.module.css @@ -0,0 +1,88 @@ +:global(.studio-workflow-screen), +:global(.studio-preview-screen) { min-width: 0; } + +:global(.studio-workflow-heading), +:global(.studio-preview-toolbar) { display: flex; align-items: flex-end; justify-content: space-between; gap: 32px; padding-bottom: 42px; border-bottom: 1px solid var(--line-strong); } +:global(.studio-workflow-heading) h1, +:global(.studio-preview-toolbar) h1 { margin: 0; font-size: clamp(38px, 5vw, 62px); line-height: 1.06; letter-spacing: -0.045em; } +:global(.studio-workflow-heading > div > p:last-child), +:global(.studio-preview-toolbar > div > p:last-child) { max-width: 780px; margin: 16px 0 0; color: var(--muted); font-size: 17px; line-height: 1.6; overflow-wrap: anywhere; } +:global(.studio-workflow-quiet-link) { display: inline-flex; min-height: 44px; align-items: center; color: var(--signal); font-weight: 650; white-space: nowrap; } + +:global(.studio-workflow-gate) { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 36px; align-items: center; margin-top: 34px; padding: 28px; border: 1px solid var(--line-strong); border-left-width: 4px; } +:global(.studio-workflow-gate--ready) { border-left-color: #166748; background: #f3faf6; } +:global(.studio-workflow-gate--blocked) { border-left-color: #9a5a1f; background: #fff9f0; } +:global(.studio-workflow-gate) h2 { margin: 0; font-size: 25px; letter-spacing: -0.03em; } +:global(.studio-workflow-gate) p:last-child { margin: 10px 0 0; color: var(--muted); line-height: 1.65; } +:global(.studio-workflow-actions) { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } +:global(.studio-workflow-actions) button, +:global(.studio-workflow-actions) a { display: inline-flex; min-height: 44px; align-items: center; justify-content: center; padding-inline: 15px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--paper); color: var(--ink); font-weight: 650; } +:global(.studio-workflow-actions) button:first-child { border-color: var(--signal); background: var(--signal); color: #fff; } +:global(.studio-workflow-actions) button:disabled { opacity: 0.55; cursor: wait; } +:global(.studio-workflow-feedback) { margin: 18px 0 0; padding: 14px 16px; border-left: 3px solid var(--signal); background: #f5f7ff; } +:global(.studio-workflow-loading) { min-height: 240px; padding-block: 56px; color: var(--muted); } + +:global(.studio-workflow-report) { margin-top: 54px; border-top: 1px solid var(--line-strong); } +:global(.studio-workflow-section-heading) { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding-block: 26px; border-bottom: 1px solid var(--line); } +:global(.studio-workflow-section-heading) h2 { margin: 0; font-size: 28px; letter-spacing: -0.03em; } +:global(.studio-workflow-status) { display: inline-flex; min-height: 32px; align-items: center; padding-inline: 11px; border: 1px solid currentColor; border-radius: 999px; font-family: "IBM Plex Mono", monospace; font-size: 11px; font-weight: 700; } +:global(.studio-workflow-status--invalid) { color: #8f2f27; } +:global(.studio-workflow-status--warnings) { color: #8b5a14; } +:global(.studio-workflow-status--valid) { color: #166748; } +:global(.studio-workflow-metadata), +:global(.studio-preview-metadata) { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0; border-bottom: 1px solid var(--line); } +:global(.studio-workflow-metadata) div, +:global(.studio-preview-metadata) div { min-width: 0; padding: 20px; border-right: 1px solid var(--line); } +:global(.studio-workflow-metadata) div:last-child, +:global(.studio-preview-metadata) div:last-child { border-right: 0; } +:global(.studio-workflow-metadata) dt, +:global(.studio-preview-metadata) dt { color: var(--muted); font-size: 11px; } +:global(.studio-workflow-metadata) dd, +:global(.studio-preview-metadata) dd { margin: 7px 0 0; font-size: 13px; overflow-wrap: anywhere; } +:global(.studio-workflow-issue-list) { margin: 0; padding: 0; list-style: none; } +:global(.studio-workflow-issue-list) li { min-width: 0; border-bottom: 1px solid var(--line); } +:global(.studio-workflow-issue-list) a { display: grid; grid-template-columns: 72px minmax(0, 1fr) auto; min-width: 0; gap: 18px; align-items: start; padding-block: 22px; color: inherit; } +:global(.studio-workflow-issue-severity) { color: var(--muted); font-family: "IBM Plex Mono", monospace; font-size: 11px; } +:global(.studio-workflow-issue-body) { display: grid; min-width: 0; gap: 7px; } +:global(.studio-workflow-issue-body) strong, +:global(.studio-workflow-issue-body) small { overflow-wrap: anywhere; } +:global(.studio-workflow-issue-body) small { color: var(--muted); font-family: "IBM Plex Mono", monospace; } +:global(.studio-workflow-empty-issues), +:global(.studio-workflow-empty-panel) { margin: 0; padding-block: 42px; border-bottom: 1px solid var(--line); color: var(--muted); } +:global(.studio-workflow-empty-panel) h2 { margin: 0; color: var(--ink); font-size: 25px; } +:global(.studio-workflow-empty-panel) p { margin: 10px 0 0; } + +:global(.studio-preview-toolbar--compact) { padding-bottom: 28px; } +:global(.studio-preview-title) { margin: 0; color: var(--ink); font-size: 28px; font-weight: 700; letter-spacing: -0.03em; } +:global(.studio-preview-state) { display: flex; justify-content: space-between; gap: 28px; align-items: center; margin-top: 24px; padding: 20px; border: 1px solid var(--line-strong); border-left-width: 4px; } +:global(.studio-preview-state) strong { font-family: "IBM Plex Mono", monospace; font-size: 12px; } +:global(.studio-preview-state) p { margin: 5px 0 0; color: var(--muted); } +:global(.studio-preview-state--current) { border-left-color: #166748; background: #f3faf6; } +:global(.studio-preview-state--none), +:global(.studio-preview-state--stale) { border-left-color: #9a5a1f; background: #fff9f0; } +:global(.studio-preview-state--expired) { border-left-color: #8f2f27; background: #fff7f4; } +:global(.studio-preview-metadata) { grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 20px; border-top: 1px solid var(--line); } +:global(.studio-preview-public-frame) { min-width: 0; margin-top: 38px; overflow: clip; border: 1px solid var(--line); background: var(--paper); } +:global(.studio-preview-public-frame .public-record-embedded) { width: 100%; max-width: none; margin: 0; padding: clamp(24px, 5vw, 56px); } + +@media (max-width: 1024px) { + :global(.studio-workflow-gate) { grid-template-columns: minmax(0, 1fr); } + :global(.studio-preview-state) { align-items: flex-start; flex-direction: column; } +} + +@media (max-width: 767px) { + :global(.studio-workflow-heading), + :global(.studio-preview-toolbar) { align-items: flex-start; flex-direction: column; gap: 12px; padding-bottom: 28px; } + :global(.studio-workflow-quiet-link) { white-space: normal; } + :global(.studio-workflow-gate) { margin-top: 24px; padding: 22px 18px; } + :global(.studio-workflow-actions) { display: grid; width: 100%; grid-template-columns: minmax(0, 1fr); } + :global(.studio-workflow-metadata), + :global(.studio-preview-metadata) { grid-template-columns: minmax(0, 1fr); } + :global(.studio-workflow-metadata) div, + :global(.studio-preview-metadata) div { border-right: 0; border-bottom: 1px solid var(--line); } + :global(.studio-workflow-metadata) div:last-child, + :global(.studio-preview-metadata) div:last-child { border-bottom: 0; } + :global(.studio-workflow-issue-list) a { grid-template-columns: minmax(0, 1fr); gap: 8px; } + :global(.studio-workflow-issue-list) a > span:last-child { display: none; } + :global(.studio-preview-public-frame .public-record-embedded) { padding: 22px 16px; } +} diff --git a/tests/features/tech-log/public-shell.test.tsx b/tests/features/tech-log/public-shell.test.tsx new file mode 100644 index 0000000..fd4eb55 --- /dev/null +++ b/tests/features/tech-log/public-shell.test.tsx @@ -0,0 +1,227 @@ +// @vitest-environment jsdom + +import { fireEvent, render, screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createMemoryRouter, RouterProvider } from "react-router-dom"; + +import { createTechLogFeatureInstalledInput } from "../../../src/features/tech-log/adapters/create-tech-log-feature-input.ts"; +import { FatalErrorState } from "../../../src/features/tech-log/presentation/public/components/fatal-error-state.tsx"; +import { PublicShell } from "../../../src/features/tech-log/presentation/public/public-shell.tsx"; +import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx"; +import { createTestApplication } from "../../helpers/create-test-application.ts"; + +const originalShowModal = HTMLDialogElement.prototype.showModal; +const originalClose = HTMLDialogElement.prototype.close; + +beforeEach(() => { + Object.defineProperty(HTMLDialogElement.prototype, "showModal", { + configurable: true, + value(this: HTMLDialogElement) { + this.setAttribute("open", ""); + }, + }); + Object.defineProperty(HTMLDialogElement.prototype, "close", { + configurable: true, + value(this: HTMLDialogElement) { + this.removeAttribute("open"); + this.dispatchEvent(new Event("close")); + }, + }); + vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => { + callback(0); + return 1; + }); +}); + +afterEach(() => { + vi.unstubAllGlobals(); + Object.defineProperty(HTMLDialogElement.prototype, "showModal", { + configurable: true, + value: originalShowModal, + }); + Object.defineProperty(HTMLDialogElement.prototype, "close", { + configurable: true, + value: originalClose, + }); +}); + +function renderShell(initialEntry = "/projects") { + const techLog = createTechLogFeatureInstalledInput().input; + const router = createMemoryRouter( + [ + { + path: "*", + element: ( + +
+ 공개 본문 +
+
+ ), + }, + ], + { initialEntries: [initialEntry] }, + ); + const view = render( + + + , + ); + return { ...view, router }; +} + +describe("TechLog Public shell", () => { + it("preserves source landmark order, navigation copy, current state, and footer", () => { + const view = renderShell(); + const frame = view.container.querySelector(".site-frame"); + + expect( + Array.from(frame?.children ?? [], (child) => child.tagName), + ).toEqual(["A", "HEADER", "MAIN", "FOOTER"]); + expect(screen.getByRole("link", { name: "본문으로 건너뛰기" })).toHaveAttribute( + "href", + "#main-content", + ); + expect(screen.getByRole("banner")).toHaveClass("site-header"); + expect(screen.getByRole("main")).toHaveAttribute("id", "main-content"); + expect(screen.getByRole("contentinfo")).toHaveClass("site-footer"); + expect(screen.getByRole("navigation", { name: "주요 탐색" })).toBeVisible(); + expect( + screen.getByRole("navigation", { name: "모바일 주요 탐색" }), + ).toBeInTheDocument(); + expect( + Array.from( + view.container.querySelectorAll(".desktop-nav a"), + (link) => link.textContent, + ), + ).toEqual(["탐색", "프로젝트", "변경 기록", "프로필"]); + expect( + Array.from( + view.container.querySelectorAll( + ".mobile-nav nav a", + ), + (link) => link.textContent, + ), + ).toEqual(["탐색", "프로젝트", "변경 기록", "프로필"]); + for (const link of screen.getAllByRole("link", { name: "프로젝트" })) { + expect(link).toHaveAttribute("aria-current", "page"); + } + expect(screen.getByText("동현")).toHaveClass("footer-name"); + expect( + screen.getByText("문제를 재현하고 검증해 운영 가능한 설계로 연결합니다."), + ).toBeVisible(); + expect(view.container.querySelector(".app-shell")).toBeNull(); + }); + + it("uses the canonical root route for brand navigation", async () => { + const user = userEvent.setup(); + const { router } = renderShell("/projects/backend-skeleton"); + + await user.click(screen.getByRole("link", { name: "TechLog 홈" })); + + expect(router.state.location.pathname).toBe("/"); + }); + + it("opens search by click, closes on Escape, and restores trigger focus", async () => { + const user = userEvent.setup(); + renderShell(); + const trigger = screen.getByRole("button", { name: "TechLog 검색 열기" }); + + await user.click(trigger); + + const dialog = screen.getByRole("dialog", { name: "TechLog 검색" }); + const input = within(dialog).getByRole("searchbox", { name: "검색어" }); + expect(dialog).toHaveAttribute("open"); + expect(dialog).toHaveAttribute("aria-labelledby", `${dialog.id}-title`); + expect(input).toHaveFocus(); + + await user.keyboard("{Escape}"); + + expect(dialog).not.toHaveAttribute("open"); + expect(trigger).toHaveFocus(); + }); + + it("opens the native search trigger from the keyboard", async () => { + const user = userEvent.setup(); + renderShell(); + const trigger = screen.getByRole("button", { name: "TechLog 검색 열기" }); + trigger.focus(); + + await user.keyboard("{Enter}"); + + expect(screen.getByRole("dialog", { name: "TechLog 검색" })).toHaveAttribute( + "open", + ); + expect(screen.getByRole("searchbox", { name: "검색어" })).toHaveFocus(); + }); + + it("returns source-equivalent results and navigates to their canonical route", async () => { + const user = userEvent.setup(); + const { router } = renderShell(); + await user.click( + screen.getByRole("button", { name: "TechLog 검색 열기" }), + ); + const input = screen.getByRole("searchbox", { name: "검색어" }); + + await user.type(input, "Keycloak"); + + expect(screen.getByText("1개의 공개 기록")).toBeVisible(); + const result = screen.getByRole("link", { name: /Auth Lab/ }); + expect(result).toHaveAttribute("href", "/projects/auth-lab"); + expect( + screen.getByRole("link", { name: /전체 검색 결과 보기/ }), + ).toHaveAttribute("href", "/search?q=Keycloak"); + + await user.click(result); + + expect(router.state.location.pathname).toBe("/projects/auth-lab"); + }); + + it("closes mobile navigation on Escape and before opening the one search dialog", async () => { + const user = userEvent.setup(); + const view = renderShell(); + const details = view.container.querySelector( + "details.mobile-nav", + ); + const summary = details?.querySelector("summary"); + expect(details).not.toBeNull(); + expect(summary).not.toBeNull(); + + details!.open = true; + fireEvent.keyDown(details!, { key: "Escape" }); + expect(details).not.toHaveAttribute("open"); + expect(summary).toHaveFocus(); + + details!.open = true; + await user.click( + screen.getByRole("button", { name: "TechLog 검색 열기" }), + ); + expect(details).not.toHaveAttribute("open"); + expect(screen.getAllByRole("dialog", { hidden: true })).toHaveLength(1); + }); +}); + +describe("TechLog fatal Public state", () => { + it("preserves error copy, trace relationship, and retry variants", async () => { + const user = userEvent.setup(); + const retry = vi.fn(); + const view = render( + , + ); + + expect(screen.getByRole("main")).toHaveClass("shell", "fatal-state"); + expect( + screen.getByRole("heading", { name: "페이지를 불러오지 못했습니다." }), + ).toBeVisible(); + expect(view.container.querySelector(".trace-id code")).toHaveTextContent( + "PUBLIC-500", + ); + await user.click(screen.getByRole("button", { name: "다시 시도" })); + expect(retry).toHaveBeenCalledOnce(); + }); +}); diff --git a/tests/features/tech-log/style-contract.test.ts b/tests/features/tech-log/style-contract.test.ts new file mode 100644 index 0000000..14e8764 --- /dev/null +++ b/tests/features/tech-log/style-contract.test.ts @@ -0,0 +1,142 @@ +// @vitest-environment jsdom + +import { render } from "@testing-library/react"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { createElement } from "react"; +import { MemoryRouter } from "react-router-dom"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +import { createTechLogFeatureInstalledInput } from "../../../src/features/tech-log/adapters/create-tech-log-feature-input.ts"; +import { FatalErrorState } from "../../../src/features/tech-log/presentation/public/components/fatal-error-state.tsx"; +import { PublicShell } from "../../../src/features/tech-log/presentation/public/public-shell.tsx"; +import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx"; +import { createTestApplication } from "../../helpers/create-test-application.ts"; + +let styleElement: HTMLStyleElement; + +beforeAll(() => { + styleElement = document.createElement("style"); + styleElement.textContent = readFileSync( + resolve( + process.cwd(), + "src/features/tech-log/presentation/styles/globals.css", + ), + "utf8", + ); + document.head.append(styleElement); +}); + +afterAll(() => { + styleElement.remove(); +}); + +function renderPublicSurface(node: React.ReactNode) { + const techLog = createTechLogFeatureInstalledInput().input; + const shell = createElement(PublicShell, { children: node }); + const router = createElement( + MemoryRouter, + { initialEntries: ["/projects"] }, + shell, + ); + return render( + createElement(ApplicationProvider, { + application: createTestApplication({ + featureInputs: { "tech-log": techLog }, + }), + children: router, + }), + ); +} + +describe("TechLog consumer-visible style contract", () => { + it("applies the source typography, palette, shell width, and header spacing", () => { + const view = renderPublicSurface( + createElement( + "main", + { id: "main-content", className: "shell" }, + "공개 본문", + ), + ); + + const rootStyle = getComputedStyle(document.documentElement); + const bodyStyle = getComputedStyle(document.body); + const headerInner = view.container.querySelector( + ".header-inner", + ); + const wordmark = view.getByRole("link", { name: "TechLog 홈" }); + + expect(rootStyle.getPropertyValue("--canvas")).toBe("#f7f7f5"); + expect(rootStyle.getPropertyValue("--ink")).toBe("#17181b"); + expect(rootStyle.getPropertyValue("--signal")).toBe("#3e5cc7"); + expect(rootStyle.getPropertyValue("--shell")).toBe("1180px"); + expect(rootStyle.getPropertyValue("--body-copy")).toBe("42rem"); + expect(bodyStyle.fontFamily).toContain("Pretendard Variable"); + expect(bodyStyle.fontSize).toBe("16px"); + expect(bodyStyle.fontWeight).toBe("400"); + expect(bodyStyle.lineHeight).toBe("1.6"); + expect(bodyStyle.minWidth).toBe("320px"); + expect(headerInner).not.toBeNull(); + expect(getComputedStyle(headerInner!).display).toBe("grid"); + expect(getComputedStyle(headerInner!).minHeight).toBe("76px"); + expect(getComputedStyle(headerInner!).gap).toBe("48px"); + expect(getComputedStyle(wordmark).fontSize).toBe("17px"); + expect(getComputedStyle(wordmark).fontWeight).toBe("680"); + }); + + it("keeps every header and dialog control at the source minimum target size", () => { + const view = renderPublicSurface( + createElement( + "main", + { id: "main-content", className: "shell" }, + "공개 본문", + ), + ); + + const wordmark = view.getByRole("link", { name: "TechLog 홈" }); + const navLink = view.container.querySelector(".nav-link"); + const searchButton = view.getByRole("button", { + name: "TechLog 검색 열기", + }); + const closeButton = view.container.querySelector( + ".dialog-close", + ); + const searchField = view.container.querySelector( + ".search-field", + ); + const dialogInner = view.container.querySelector( + ".search-dialog-inner", + ); + + expect(getComputedStyle(wordmark).minHeight).toBe("44px"); + expect(getComputedStyle(navLink!).minHeight).toBe("44px"); + expect(getComputedStyle(searchButton).minHeight).toBe("44px"); + expect(getComputedStyle(closeButton!).minWidth).toBe("52px"); + expect(getComputedStyle(closeButton!).minHeight).toBe("44px"); + expect(getComputedStyle(searchField!).minHeight).toBe("56px"); + expect(getComputedStyle(searchField!).marginTop).toBe("24px"); + expect(getComputedStyle(searchField!).backgroundColor).toBe( + "rgb(255, 255, 255)", + ); + expect(getComputedStyle(dialogInner!).padding).toBe("27px 28px 22px"); + }); + + it("preserves footer spacing and fatal-action sizing", () => { + const view = renderPublicSurface( + createElement(FatalErrorState, { + traceId: "PUBLIC-500", + retryHref: "/", + }), + ); + + const footerInner = view.container.querySelector( + ".footer-inner", + ); + const retry = view.getByRole("link", { name: "다시 시도" }); + + expect(getComputedStyle(footerInner!).minHeight).toBe("172px"); + expect(getComputedStyle(footerInner!).gap).toBe("40px"); + expect(getComputedStyle(retry).minHeight).toBe("44px"); + expect(getComputedStyle(retry).marginTop).toBe("34px"); + }); +});