주요 주제 — 프로젝트 화면은 처음부터 `project.topics` 를 읽고 있었는데 공개 계약에 그 자리가 없었다. `project_topic` 은 테이블도 있고 조인할 수도 있었지만 응답에 실을 곳이 없었고, 고를 화면도 없었다. 계약에 자리를 만들고, 편집 화면에 주제 선택을 붙이고, 공개 조회가 ACTIVE 인 주제만 고른 순서대로 내보내게 한다. 활동 위치 열기 — 그 링크는 활동의 관련 기록을 따라간다. 백엔드는 처음부터 그 경로를 유도하고 있었지만, 활동을 만들 때 기록을 고를 방법이 없어 모든 줄이 관련 기록 없이 저장됐다. 활동 추가 폼에 유형·일시·제목·요약·연결할 기록·노출을 모두 둔다. Case 목차 — 오른쪽 목차가 어떤 문서에서도 나오지 않았다. Case 렌더러가 둘이었고 어느 쪽을 쓸지 `publicPath === "/cases/collection-fetch-join-pagination"` 이라는 슬러그 비교가 정했다. 설계 픽스처 문서 하나만 breadcrumb·목차가 있는 완성된 배치를 받고, 실제로 작성한 Case 는 전부 축약본으로 떨어졌다. 배치를 하나로 합치고, 목차는 본문에 제목이 있을 때만 그린다. Studio UI — 새로 만든 화면들이 기존 디테일을 따라가지 않고 있었다. `<label htmlFor>` + 별도 컨트롤 대신 편집기가 쓰는 `<label><span>` 묶음을, 임시로 만든 폼 클래스 대신 `studio-editor-section`·`studio-field-grid` 를 쓴다. 릴리즈 목록 행에는 유형 라벨이 빠져 격자 첫 열이 비어 있었고(제목이 당겨져 다른 목록과 줄이 맞지 않았다) 버튼 둘이 묶이지 않은 채였다. 프로젝트 행에는 단계·공개 범위를 한국어로 적고 "내용 편집" 을 다음 행동으로 둔다. 로그인 화면 — "인증 연동 지점을 확인하기 위한 보호 라우트" 는 이 뼈대를 만들던 사람에게 하는 말이었다. 이 화면을 보는 사람은 글을 쓰러 온 작성자다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XEHXspz4rv5pB5wiiSsVDu
187 lines
16 KiB
CSS
187 lines
16 KiB
CSS
.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-field > small { color: var(--faint); font-size: 12px; font-weight: 450; line-height: 1.6; }
|
|
|
|
/* 여러 개를 고르는 목록(주제 등). 작업본 목록의 유형 선택과 같은 모양을 쓴다. */
|
|
.studio-app .studio-choice-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 0 24px; margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--line); }
|
|
.studio-app .studio-choice { display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 4px 12px; align-items: center; min-height: 60px; padding: 12px 0; border-bottom: 1px solid var(--line); cursor: pointer; }
|
|
.studio-app .studio-choice input { width: 20px; min-width: 20px; min-height: 20px; padding: 0; }
|
|
.studio-app .studio-choice span { font-size: 15px; font-weight: 550; }
|
|
.studio-app .studio-choice small { grid-column: 2; color: var(--faint); font-family: "IBM Plex Mono", monospace; font-size: 11px; }
|
|
|
|
/* 편집 화면 하단의 저장 줄. 버튼과 그 옆의 상태 한 줄. */
|
|
.studio-app .studio-editor-footer { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; padding: 32px 0 0; }
|
|
.studio-app .studio-editor-footer .studio-primary-button,
|
|
.studio-app .studio-editor-footer .studio-secondary-button { margin: 0; }
|
|
.studio-app .studio-editor-footer .studio-field-note { flex: 1 1 260px; margin: 0; color: var(--muted); font-size: 12px; line-height: 1.6; }
|
|
|
|
/* 체크박스 묶음(변경 유형 등). `.studio-field-grid` 를 쓰면 legend 가 한 칸을 차지해 줄이 어긋난다. */
|
|
.studio-app .studio-checkbox-set { margin: 34px 0 0; padding: 0; border: 0; }
|
|
.studio-app .studio-checkbox-set legend { padding: 0 0 14px; color: var(--muted); font-size: 12px; font-weight: 650; }
|
|
.studio-app .studio-checkbox-set { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 0 24px; }
|
|
.studio-app .studio-checkbox-set legend { grid-column: 1 / -1; }
|
|
.studio-app .studio-checkbox-set .studio-field--checkbox { display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 12px; align-items: center; min-height: 52px; padding: 8px 0; border-bottom: 1px solid var(--line); cursor: pointer; }
|
|
.studio-app .studio-checkbox-set .studio-field--checkbox input { width: 20px; min-width: 20px; min-height: 20px; padding: 0; }
|
|
.studio-app .studio-checkbox-set .studio-field--checkbox span { font-size: 15px; font-weight: 500; }
|
|
|
|
.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-asset-panel { margin-top: 28px; padding-top: 28px; border-top: 1px solid var(--line); }
|
|
.studio-app .studio-asset-panel .studio-eyebrow { margin-bottom: 8px; }
|
|
.studio-app .studio-asset-panel h3 { margin: 0 0 10px; font-size: 19px; letter-spacing: -0.02em; }
|
|
.studio-app .studio-asset-panel > p { max-width: 640px; margin: 0 0 18px; color: var(--muted); font-size: 13px; line-height: 1.6; }
|
|
.studio-app .studio-asset-panel-actions { display: flex; flex-wrap: wrap; align-items: end; gap: 12px; margin-bottom: 18px; }
|
|
.studio-app .studio-asset-kind-field { display: grid; min-width: 0; gap: 8px; color: var(--muted); font-size: 12px; font-weight: 650; }
|
|
.studio-app .studio-asset-kind-field select { 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; }
|
|
.studio-app .studio-asset-panel-actions 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 .asset-picker ul { display: flex; flex-wrap: wrap; gap: 8px; padding: 0; margin: 0; list-style: none; }
|
|
.studio-app .asset-picker button { min-height: 36px; padding-inline: 12px; border: 1px solid var(--line-strong); border-radius: 999px; background: var(--paper); color: var(--ink); font-size: 13px; }
|
|
/* Declared after `.asset-picker button` on purpose: the pill rule above also
|
|
matches this form's submit button, and both selectors carry the same
|
|
specificity, so only source order tells them apart. */
|
|
/* `minmax(0, 1fr)` and the row's `min-width: 0` are both load bearing, for the
|
|
reason `.studio-editor-layout` already needs the same pair: a grid track and
|
|
a flex container both default to an automatic minimum of their min-content
|
|
size, so at 360px the search row sized itself to 366px inside a 328px column
|
|
and pushed the submit button off-screen (document scrollWidth 382). */
|
|
/* Picker 자체의 옵션이지 문서의 입력 칸이 아니다 — `studio-field` 를 쓰면 편집기의 칸 목록에
|
|
섞여 들어간다. 모양은 그 칸들과 같게 두고 이름만 분리한다. */
|
|
.studio-app .asset-picker-option { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; color: var(--muted); font-size: 12px; font-weight: 650; }
|
|
.studio-app .asset-picker-option input { width: 18px; min-width: 18px; min-height: 18px; padding: 0; }
|
|
.studio-app .asset-picker-search { display: grid; grid-template-columns: minmax(0, 1fr); max-width: 420px; gap: 8px; margin-bottom: 14px; color: var(--muted); font-size: 12px; font-weight: 650; }
|
|
.studio-app .asset-picker-search div { display: flex; min-width: 0; gap: 8px; }
|
|
.studio-app .asset-picker-search input { min-width: 0; flex: 1; min-height: 44px; padding-inline: 12px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--paper); color: var(--ink); font-size: 14px; }
|
|
.studio-app .asset-picker-search button { min-height: 44px; padding-inline: 16px; border-radius: 5px; font-size: 13px; font-weight: 650; }
|
|
.studio-app .asset-picker ul:not(:empty) { margin-top: 12px; }
|
|
.studio-app .studio-error,
|
|
.studio-app .studio-asset-picker-empty { margin: 0; color: var(--muted); font-size: 13px; }
|
|
.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; }
|
|
}
|
|
|
|
/* 게시까지의 단계. 순서가 있는 길이라 번호를 붙인다 — 장식이 아니라 밟아야 하는 차례다. */
|
|
.studio-app .studio-editor-flow ol { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 14px 0 0; padding: 0; list-style: none; }
|
|
.studio-app .studio-editor-flow li { display: flex; align-items: center; gap: 6px; font-size: 13px; }
|
|
.studio-app .studio-editor-flow li + li::before { content: "→"; margin-right: 4px; color: var(--faint); }
|
|
.studio-app .studio-editor-flow span { display: inline-flex; min-width: 18px; height: 18px; align-items: center; justify-content: center; border-radius: 999px; background: var(--line); color: var(--muted); font-size: 11px; font-weight: 650; }
|
|
|
|
/* 게시를 막는 항목. 고치는 자리에서 보이지 않으면 작성자는 별도 화면과 편집기를 오가며
|
|
어느 칸이었는지 기억해야 한다. */
|
|
.studio-app .studio-editor-validation { margin: 0 0 20px; padding: 14px 16px; border: 1px solid var(--danger, #b4232a); border-radius: 6px; background: var(--paper); }
|
|
.studio-app .studio-editor-validation--stale { border-color: var(--line-strong); opacity: 0.72; }
|
|
.studio-app .studio-editor-validation-title { margin: 0 0 10px; color: var(--danger, #b4232a); font-size: 13px; font-weight: 700; }
|
|
.studio-app .studio-editor-validation--stale .studio-editor-validation-title { color: var(--muted); }
|
|
.studio-app .studio-editor-validation ul { display: grid; gap: 7px; margin: 0; padding: 0; list-style: none; }
|
|
.studio-app .studio-editor-validation li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; font-size: 13px; color: var(--ink); }
|
|
.studio-app .studio-editor-validation li span { min-width: 30px; color: var(--danger, #b4232a); font-size: 11px; font-weight: 700; }
|
|
.studio-app .studio-editor-validation li[data-severity="WARNING"] span { color: var(--muted); }
|
|
.studio-app .studio-editor-validation code { color: var(--faint); font-size: 11px; }
|
|
|
|
/*
|
|
칸 아래에 붙는 지적. 검증 결과를 화면 맨 위 한 덩어리로 모아 두는 대신 그 칸 옆에 두면,
|
|
작성자는 `/topicId` 같은 경로를 읽고 어느 칸인지 스스로 찾을 필요가 없다.
|
|
|
|
`.studio-field` 는 세로 흐름이므로 별도의 배치가 필요 없다 — 입력 바로 다음 줄에 놓인다.
|
|
*/
|
|
.studio-app .studio-field-notice { display: block; margin-top: 6px; font-size: 12px; line-height: 1.5; }
|
|
.studio-app .studio-field-notice--error { color: var(--danger, #b4232a); }
|
|
.studio-app .studio-field-notice--warning { color: var(--muted); }
|
|
.studio-app .studio-editor-unplaced-issues { display: grid; gap: 6px; margin: 10px 0 0; padding: 0; list-style: none; }
|
|
.studio-app .studio-editor-unplaced-issues li { font-size: 12px; color: var(--danger, #b4232a); }
|
|
.studio-app .studio-editor-unplaced-issues li[data-severity="WARNING"] { color: var(--muted); }
|
|
.studio-app .studio-editor-unplaced-issues code { color: var(--faint); font-size: 11px; }
|
|
|
|
/*
|
|
저장과 게시는 되돌릴 수 있는 정도가 다르다 — 하나는 초안을 남기고, 하나는 공개한다. 둘이
|
|
맞붙어 있으면 누르려던 것을 지나쳐 누르기 쉬우므로 사이를 벌린다.
|
|
*/
|
|
.studio-app .studio-document-status-rail button + button { margin-top: 10px; }
|
|
/* 게시만 강조한다. 저장은 되돌릴 수 있으므로 같은 무게로 부를 이유가 없다. */
|
|
.studio-app .studio-document-status-rail button:not(.studio-primary-button) { border-color: var(--line-strong); background: var(--paper); color: var(--ink); }
|