21st.dev 사례 연구

Fieldwork · A workspace that remembers

탐색 · MIT

탐색 더 보기
CASE STUDY
Fieldwork · A workspace that remembers 정적 미리보기

갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.

큰 화면으로 보기 새 탭실행 HTML 저장

저장한 HTML에서는 갤러리의 재생 설정이 적용되지 않아요.

SOURCE FILES

독립 예제 코드 읽기

7개 파일

사례의 구도나 문서의 원리를 바탕으로 새로 만든 예제입니다. 아래 코드를 복사해 직접 응용할 수 있습니다. 실행 HTML에는 미리보기를 위한 실행 환경이 들어 있습니다.

index.html독립 구현
파일 저장

<!doctype html>
<!-- Independent observation study. Copyright (c) 2026 StyleGallery contributors. MIT. -->
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; font-src 'none'; connect-src 'none'; media-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'"><title>Fieldwork · A workspace that remembers</title><style>/* Original learning-example styling. Copyright (c) 2026 StyleGallery contributors. MIT. */
* { box-sizing: border-box; }
body { margin: 0; }
button, input, select { font: inherit; }
button { cursor: pointer; }
button:disabled { cursor: default; }
button, input, select { touch-action: manipulation; }
button:focus-visible, input:focus-visible, select:focus-visible, [tabindex="-1"]:focus-visible { outline: 3px solid #307865; outline-offset: 3px; }
[hidden] { display: none !important; }
h1, h2, p { margin: 0; }
svg { display: block; }
.sr-only { block-size: 1px; clip-path: inset(50%); inline-size: 1px; overflow: hidden; position: absolute; white-space: nowrap; }
.no-script { font: 12px/1.5 system-ui, sans-serif; padding: 12px; }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; scroll-behavior: auto !important; transition: none !important; } }

body { background: #e7eadf; color: #273329; font: 12px/1.4 Arial, Helvetica, sans-serif; padding: 16px; }
.workspace_layout { background: #fafbf6; block-size: calc(100svh - 32px); border: 1px solid #cfd6c7; border-radius: 13px; display: grid; grid-template-columns: 165px minmax(0, 1fr); grid-template-rows: 45px minmax(0, 1fr); margin-inline: auto; max-inline-size: 1040px; min-block-size: 388px; overflow: hidden; }
.workspace_header { align-items: center; background: #fffef9; border-block-end: 1px solid #e0e5d9; display: flex; gap: 12px; grid-column: 1 / -1; min-inline-size: 0; padding: 8px 15px; }
.sidebar_toggle { align-items: center; background: #f1f3e9; border: 1px solid #dce1d2; border-radius: 6px; color: #52614a; display: flex; flex-shrink: 0; justify-content: center; min-block-size: 29px; min-inline-size: 29px; padding: 5px; }
.sidebar_toggle:hover { background: #e3e9d8; }
.workspace_breadcrumb { align-items: center; color: #737d6b; display: flex; flex: 1; flex-wrap: wrap; font-size: 10px; gap: 9px; min-inline-size: 0; }
.workspace_breadcrumb strong { color: #35412c; font-weight: 600; }
.workspace_status { align-items: center; color: #7b836f; display: flex; font-size: 9px; gap: 5px; }
.workspace_status i { background: #7d965d; block-size: 5px; border-radius: 50%; inline-size: 5px; }
.sidebar_scroll_area { background: #f0f3e7; border-inline-end: 1px solid #e0e5d6; display: flex; flex-direction: column; gap: 22px; min-block-size: 0; overflow: auto; padding: 19px 11px 12px; }
.workspace_brand { align-items: center; display: flex; gap: 7px; padding-inline: 3px; }
.workspace_brand strong { font-size: 12px; letter-spacing: -.2px; }
.workspace_brand p { color: #76816a; font-size: 8px; margin-block-start: 2px; }
.workspace_navigation { display: grid; gap: 4px; }
.navigation_item { align-items: center; background: transparent; border: 0; border-radius: 6px; color: #626d56; display: flex; font-size: 11px; gap: 8px; inline-size: 100%; min-block-size: 32px; padding: 7px 9px; text-align: start; }
.navigation_item:hover { background: #e3e9d6; }
.navigation_item[aria-current="page"] { background: #dde7c8; color: #334523; font-weight: 700; }
.navigation_icon { font-size: 15px; inline-size: 15px; text-align: center; }
.navigation_count { background: #e6ebdc; border-radius: 5px; color: #687953; font-size: 8px; margin-inline-start: auto; min-inline-size: 17px; padding: 2px 5px; text-align: center; }
.navigation_label { color: #8a947e; font-size: 8px; letter-spacing: 1.4px; margin: 15px 8px 7px; }
.navigation_chevron { margin-inline-start: auto; }
.navigation_child { font-size: 10px; padding-inline-start: 32px; }
.workspace_account { align-items: center; border-block-start: 1px solid #dce2d0; display: flex; gap: 7px; margin-block-start: auto; padding-block-start: 11px; }
.account_avatar { align-items: center; background: #e2d3ba; block-size: 26px; border: 2px solid #fcfbf3; border-radius: 50%; color: #6d583c; display: flex; font-size: 8px; inline-size: 26px; justify-content: center; }
.workspace_account strong { font-size: 9px; font-weight: 600; }
.workspace_account p { color: #829072; font-size: 8px; }
.workspace_scroll_area { min-block-size: 0; min-inline-size: 0; overflow: auto; padding: 19px 22px 14px; }
.workspace_title_row { align-items: center; display: flex; gap: 12px; justify-content: space-between; }
.workspace_eyebrow { color: #809066; font-size: 8px; letter-spacing: 1.4px; }
.workspace_title_row h1 { font-family: Georgia, 'Times New Roman', serif; font-size: 29px; font-weight: 400; letter-spacing: -.8px; line-height: 1.2; margin-block-start: 4px; overflow-wrap: anywhere; }
.workspace_date { border: 1px solid #d9e0ce; border-radius: 6px; color: #8a977b; font: 9px/1 monospace; padding: 6px; }
.workspace_description { color: #839074; font-size: 10px; margin-block: 5px 14px; }
.workspace_metrics { display: grid; gap: 10px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.workspace_metric { background: #eef2e3; border: 1px solid #e0e6d1; border-radius: 8px; padding: 11px 13px; }
.workspace_metric p { color: #6b7958; font-size: 9px; }
.workspace_metric strong { display: block; font-family: Georgia, 'Times New Roman', serif; font-size: 30px; font-weight: 400; line-height: 1.2; margin-block: 3px; }
.workspace_metric span { color: #839171; font-size: 8px; }
.workspace_metric_alt { background: #f4efe3; border-color: #e8e0ce; }
.project_board { margin-block-start: 16px; }
.project_board_header { align-items: center; display: flex; gap: 8px; justify-content: space-between; margin-block-end: 5px; }
.project_board_header h2 { font-size: 10px; font-weight: 600; }
.project_board_header span { color: #8d977e; font-size: 8px; }
.project_list { list-style: none; margin: 0; padding: 0; }
.project_list li { align-items: center; border-block-end: 1px solid #e8ebdf; display: flex; gap: 9px; min-block-size: 40px; padding-block: 7px; }
.project_swatch { align-items: center; block-size: 25px; border-radius: 6px; display: flex; flex-shrink: 0; font-family: Georgia, serif; font-size: 12px; inline-size: 25px; justify-content: center; }
.project_swatch_olive { background: #dbe2c3; color: #586645; }
.project_swatch_clay { background: #ead9cb; color: #8b6754; }
.project_swatch_blue { background: #d4dfe0; color: #5a7376; }
.project_list strong { font-size: 9px; font-weight: 600; }
.project_list p { color: #8b967e; font-size: 8px; margin-block-start: 1px; }
.project_state { color: #889573; font-size: 8px; margin-inline-start: auto; }
.workspace_hint { color: #8c967e; font-size: 8px; margin-block-start: 11px; }
.workspace_layout[data-collapsed="true"] { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) { .workspace_layout { grid-template-columns: 220px minmax(0, 1fr); } .workspace_scroll_area { padding: 30px; } .workspace_metric { padding: 18px; } .workspace_title_row h1 { font-size: 38px; } .project_list li { min-block-size: 57px; } }
@media (max-width: 500px) { body { padding: 8px; } .workspace_layout { align-content: start; block-size: auto; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto; min-block-size: calc(100svh - 16px); } .workspace_header { padding-inline: 10px; } .workspace_status { display: none; } .sidebar_scroll_area { border-block-end: 1px solid #e0e5d6; border-inline-end: 0; gap: 12px; overflow: visible; padding: 12px; } .workspace_brand, .workspace_account { display: none; } .workspace_navigation { grid-template-columns: repeat(2, minmax(0, 1fr)); } .navigation_group { grid-column: 1 / -1; } .navigation_label { display: none; } .project_views { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); } .workspace_scroll_area { overflow: visible; padding: 18px 15px; } .project_state { display: none; } .workspace_metrics { gap: 8px; } .workspace_metric { padding: 10px; } }
</style></head><body><main class="workspace_layout" data-collapsed="false">
  <header class="workspace_header"><button type="button" class="sidebar_toggle" data-sidebar-toggle aria-expanded="true" aria-controls="workspace-navigation" aria-label="Hide navigation"><svg width="17" height="17" viewBox="0 0 20 20" aria-hidden="true"><rect x="2" y="3" width="16" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M7 3v14" fill="none" stroke="currentColor" stroke-width="1.4"/></svg></button><p class="workspace_breadcrumb">Fieldwork <span aria-hidden="true">/</span> <strong data-current-view>Overview</strong></p><span class="workspace_status"><i aria-hidden="true"></i> Personal space</span></header>
  <aside class="sidebar_scroll_area" id="workspace-navigation" aria-label="Workspace navigation">
    <div class="workspace_brand"><svg width="28" height="28" viewBox="0 0 28 28" aria-hidden="true"><rect width="28" height="28" rx="8" fill="#e3edc9"/><path d="M8 8h12v4H12v3h6v4h-6v3H8Z" fill="#344b32"/></svg><div><strong>Fieldwork</strong><p>A little more focus.</p></div></div>
    <nav class="workspace_navigation" aria-label="Views"><button type="button" class="navigation_item" data-view="overview" aria-current="page"><span class="navigation_icon" aria-hidden="true">▦</span>Overview</button><button type="button" class="navigation_item" data-view="notes"><span class="navigation_icon" aria-hidden="true">≡</span>Notes<span class="navigation_count">6</span></button><div class="navigation_group"><p class="navigation_label">YOUR WORK</p><button type="button" class="navigation_item" data-projects-toggle aria-expanded="false" aria-controls="project-views"><span class="navigation_icon" aria-hidden="true">▤</span>Projects<span class="navigation_chevron" data-group-chevron aria-hidden="true">+</span></button><div class="project_views" id="project-views" hidden><button type="button" class="navigation_item navigation_child" data-view="active">Active<span class="navigation_count">3</span></button><button type="button" class="navigation_item navigation_child" data-view="archive">Archive</button></div></div></nav>
    <div class="workspace_account"><span class="account_avatar" aria-hidden="true">ND</span><div><strong>Nora's studio</strong><p>Room to make things.</p></div></div>
  </aside>
  <section class="workspace_scroll_area" aria-labelledby="workspace-title"><div class="workspace_title_row"><div><p class="workspace_eyebrow" data-section-label>MONDAY, IN FOCUS</p><h1 id="workspace-title" tabindex="-1" data-view-heading>Overview</h1></div><span class="workspace_date">W. 42</span></div><p class="workspace_description" data-view-description>A clear view of the work ahead.</p>
    <div class="workspace_metrics"><article class="workspace_metric"><p data-metric-label>In progress</p><strong data-metric-value>03</strong><span data-metric-note>Ideas taking shape</span></article><article class="workspace_metric workspace_metric_alt"><p data-second-label>Collected notes</p><strong data-second-value>06</strong><span data-second-note>Small sparks, kept close</span></article></div>
    <section class="project_board" aria-labelledby="project-board-title"><div class="project_board_header"><h2 id="project-board-title" data-board-heading>On the workbench</h2><span data-board-count>3 projects</span></div><ol class="project_list" data-project-list><li><span class="project_swatch project_swatch_olive" aria-hidden="true">A</span><div><strong>Atlas journal</strong><p>Editorial · finding the rhythm</p></div><span class="project_state">Making</span></li><li><span class="project_swatch project_swatch_clay" aria-hidden="true">P</span><div><strong>Paper garden</strong><p>Objects · a study in texture</p></div><span class="project_state">Sketching</span></li><li><span class="project_swatch project_swatch_blue" aria-hidden="true">S</span><div><strong>Sunday sounds</strong><p>Identity · notes in the margins</p></div><span class="project_state">Exploring</span></li></ol></section>
    <p class="workspace_hint" role="status" data-nav-status>Open Projects to explore a view. Your place stays with you.</p>
  </section><noscript><p class="no-script">This is the Overview view. Changing views requires JavaScript.</p></noscript>
</main><script>/* Original learning example. Copyright (c) 2026 StyleGallery contributors. MIT. */
(() => {
'use strict';
const id = "21st-49e34572c05d";
function reportPreviewError(error) { parent.postMessage({type:'sg-preview-error',id,message:String(error?.message || error || 'Initialization failed')},'*'); }
window.addEventListener('error', event => reportPreviewError(event.error || event.message));
window.addEventListener('unhandledrejection', event => reportPreviewError(event.reason));
try {
function createNavigationState() {
  return { selected: 'overview', groupOpen: false, collapsed: false };
}
function updateNavigation(state, action) {
  if (action.type === 'collapse') return { ...state, collapsed: !state.collapsed };
  if (action.type === 'group') return { ...state, groupOpen: !state.groupOpen };
  if (action.type === 'select' && ['overview', 'notes', 'active', 'archive'].includes(action.id)) return { ...state, selected: action.id };
  return state;
}

let state = createNavigationState();
const shell = document.querySelector('.workspace_layout');
const sidebar = document.querySelector('#workspace-navigation');
const toggle = document.querySelector('[data-sidebar-toggle]');
const group = document.querySelector('[data-projects-toggle]');
const childViews = document.querySelector('#project-views');
const titles = { overview: 'Overview', notes: 'Notes', active: 'Active projects', archive: 'Archive' };
const views = {
  overview: { eyebrow: 'MONDAY, IN FOCUS', description: 'A clear view of the work ahead.', metrics: ['In progress', '03', 'Ideas taking shape', 'Collected notes', '06', 'Small sparks, kept close'], heading: 'On the workbench', items: [['Atlas journal', 'Editorial · finding the rhythm', 'Making'], ['Paper garden', 'Objects · a study in texture', 'Sketching'], ['Sunday sounds', 'Identity · notes in the margins', 'Exploring']] },
  notes: { eyebrow: 'THOUGHTS, KEPT CLOSE', description: 'A quiet place for unfinished ideas.', metrics: ['Collected notes', '06', 'Three shown below', 'New this week', '02', 'Worth coming back to'], heading: 'Pinned thoughts', items: [['Try a wider margin', 'Atlas journal · layout note', 'Idea'], ['A shade of late summer', 'Paper garden · color note', 'Color'], ['Listen for the spaces', 'Sunday sounds · first thought', 'Sound']] },
  active: { eyebrow: 'YOUR WORK / ACTIVE', description: 'Three projects with room to grow.', metrics: ['In progress', '03', 'Every project has a next step', 'Ready to review', '01', 'A fresh set of eyes'], heading: 'Active projects', items: [['Atlas journal', 'Next · review the opening spread', 'Review'], ['Paper garden', 'Next · test the folded sample', 'Making'], ['Sunday sounds', 'Next · choose a direction', 'Exploring']] },
  archive: { eyebrow: 'YOUR WORK / ARCHIVE', description: 'Finished chapters, ready to revisit.', metrics: ['Completed', '02', 'A small collection of good work', 'On the shelf', '02', 'Nothing is lost by closing it'], heading: 'Past projects', items: [['Common ground', 'Identity · a neighborhood table', 'Finished'], ['A slow morning', 'Print · a small seasonal edition', 'Finished']] }
};
function renderNavigation() {
  const view = views[state.selected];
  if (state.collapsed && sidebar.contains(document.activeElement)) toggle.focus();
  if (!state.groupOpen && childViews.contains(document.activeElement)) group.focus();
  shell.dataset.collapsed = String(state.collapsed); sidebar.hidden = state.collapsed;
  toggle.setAttribute('aria-expanded', String(!state.collapsed)); toggle.setAttribute('aria-label', state.collapsed ? 'Show navigation' : 'Hide navigation');
  group.setAttribute('aria-expanded', String(state.groupOpen)); childViews.hidden = !state.groupOpen;
  document.querySelector('[data-group-chevron]').textContent = state.groupOpen ? '−' : '+';
  document.querySelectorAll('[data-view]').forEach(button => { if (button.dataset.view === state.selected) button.setAttribute('aria-current', 'page'); else button.removeAttribute('aria-current'); });
  document.querySelector('[data-current-view]').textContent = titles[state.selected];
  document.querySelector('[data-view-heading]').textContent = titles[state.selected];
  document.querySelector('[data-section-label]').textContent = view.eyebrow;
  document.querySelector('[data-view-description]').textContent = view.description;
  ['metric-label', 'metric-value', 'metric-note', 'second-label', 'second-value', 'second-note'].forEach((key, index) => { document.querySelector('[data-' + key + ']').textContent = view.metrics[index]; });
  document.querySelector('[data-board-heading]').textContent = view.heading;
  document.querySelector('[data-board-count]').textContent = view.items.length + ' shown';
  const list = document.querySelector('[data-project-list]'); list.replaceChildren();
  view.items.forEach((item, index) => {
    const li = document.createElement('li'), swatch = document.createElement('span'), text = document.createElement('div'), title = document.createElement('strong'), note = document.createElement('p'), status = document.createElement('span');
    swatch.className = 'project_swatch project_swatch_' + ['olive', 'clay', 'blue'][index % 3]; swatch.setAttribute('aria-hidden', 'true'); swatch.textContent = item[0][0]; title.textContent = item[0]; note.textContent = item[1]; status.className = 'project_state'; status.textContent = item[2]; text.append(title, note); li.append(swatch, text, status); list.append(li);
  });
}
toggle.addEventListener('click', () => { state = updateNavigation(state, { type: 'collapse' }); renderNavigation(); document.querySelector('[data-nav-status]').textContent = (state.collapsed ? 'Navigation hidden. ' : 'Navigation restored. ') + titles[state.selected] + ' remains selected.'; });
group.addEventListener('click', () => { state = updateNavigation(state, { type: 'group' }); renderNavigation(); });
document.querySelectorAll('[data-view]').forEach(button => button.addEventListener('click', () => { state = updateNavigation(state, { type: 'select', id: button.dataset.view }); renderNavigation(); document.querySelector('[data-nav-status]').textContent = titles[state.selected] + ' selected. The same selection is shown in the header.'; }));
renderNavigation();

parent.postMessage({type:'sg-preview-ready',id},'*');
} catch (error) { reportPreviewError(error); }
})();
</script></body></html>
styles.css독립 구현
파일 저장

/* Original learning-example styling. Copyright (c) 2026 StyleGallery contributors. MIT. */
* { box-sizing: border-box; }
body { margin: 0; }
button, input, select { font: inherit; }
button { cursor: pointer; }
button:disabled { cursor: default; }
button, input, select { touch-action: manipulation; }
button:focus-visible, input:focus-visible, select:focus-visible, [tabindex="-1"]:focus-visible { outline: 3px solid #307865; outline-offset: 3px; }
[hidden] { display: none !important; }
h1, h2, p { margin: 0; }
svg { display: block; }
.sr-only { block-size: 1px; clip-path: inset(50%); inline-size: 1px; overflow: hidden; position: absolute; white-space: nowrap; }
.no-script { font: 12px/1.5 system-ui, sans-serif; padding: 12px; }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; scroll-behavior: auto !important; transition: none !important; } }

body { background: #e7eadf; color: #273329; font: 12px/1.4 Arial, Helvetica, sans-serif; padding: 16px; }
.workspace_layout { background: #fafbf6; block-size: calc(100svh - 32px); border: 1px solid #cfd6c7; border-radius: 13px; display: grid; grid-template-columns: 165px minmax(0, 1fr); grid-template-rows: 45px minmax(0, 1fr); margin-inline: auto; max-inline-size: 1040px; min-block-size: 388px; overflow: hidden; }
.workspace_header { align-items: center; background: #fffef9; border-block-end: 1px solid #e0e5d9; display: flex; gap: 12px; grid-column: 1 / -1; min-inline-size: 0; padding: 8px 15px; }
.sidebar_toggle { align-items: center; background: #f1f3e9; border: 1px solid #dce1d2; border-radius: 6px; color: #52614a; display: flex; flex-shrink: 0; justify-content: center; min-block-size: 29px; min-inline-size: 29px; padding: 5px; }
.sidebar_toggle:hover { background: #e3e9d8; }
.workspace_breadcrumb { align-items: center; color: #737d6b; display: flex; flex: 1; flex-wrap: wrap; font-size: 10px; gap: 9px; min-inline-size: 0; }
.workspace_breadcrumb strong { color: #35412c; font-weight: 600; }
.workspace_status { align-items: center; color: #7b836f; display: flex; font-size: 9px; gap: 5px; }
.workspace_status i { background: #7d965d; block-size: 5px; border-radius: 50%; inline-size: 5px; }
.sidebar_scroll_area { background: #f0f3e7; border-inline-end: 1px solid #e0e5d6; display: flex; flex-direction: column; gap: 22px; min-block-size: 0; overflow: auto; padding: 19px 11px 12px; }
.workspace_brand { align-items: center; display: flex; gap: 7px; padding-inline: 3px; }
.workspace_brand strong { font-size: 12px; letter-spacing: -.2px; }
.workspace_brand p { color: #76816a; font-size: 8px; margin-block-start: 2px; }
.workspace_navigation { display: grid; gap: 4px; }
.navigation_item { align-items: center; background: transparent; border: 0; border-radius: 6px; color: #626d56; display: flex; font-size: 11px; gap: 8px; inline-size: 100%; min-block-size: 32px; padding: 7px 9px; text-align: start; }
.navigation_item:hover { background: #e3e9d6; }
.navigation_item[aria-current="page"] { background: #dde7c8; color: #334523; font-weight: 700; }
.navigation_icon { font-size: 15px; inline-size: 15px; text-align: center; }
.navigation_count { background: #e6ebdc; border-radius: 5px; color: #687953; font-size: 8px; margin-inline-start: auto; min-inline-size: 17px; padding: 2px 5px; text-align: center; }
.navigation_label { color: #8a947e; font-size: 8px; letter-spacing: 1.4px; margin: 15px 8px 7px; }
.navigation_chevron { margin-inline-start: auto; }
.navigation_child { font-size: 10px; padding-inline-start: 32px; }
.workspace_account { align-items: center; border-block-start: 1px solid #dce2d0; display: flex; gap: 7px; margin-block-start: auto; padding-block-start: 11px; }
.account_avatar { align-items: center; background: #e2d3ba; block-size: 26px; border: 2px solid #fcfbf3; border-radius: 50%; color: #6d583c; display: flex; font-size: 8px; inline-size: 26px; justify-content: center; }
.workspace_account strong { font-size: 9px; font-weight: 600; }
.workspace_account p { color: #829072; font-size: 8px; }
.workspace_scroll_area { min-block-size: 0; min-inline-size: 0; overflow: auto; padding: 19px 22px 14px; }
.workspace_title_row { align-items: center; display: flex; gap: 12px; justify-content: space-between; }
.workspace_eyebrow { color: #809066; font-size: 8px; letter-spacing: 1.4px; }
.workspace_title_row h1 { font-family: Georgia, 'Times New Roman', serif; font-size: 29px; font-weight: 400; letter-spacing: -.8px; line-height: 1.2; margin-block-start: 4px; overflow-wrap: anywhere; }
.workspace_date { border: 1px solid #d9e0ce; border-radius: 6px; color: #8a977b; font: 9px/1 monospace; padding: 6px; }
.workspace_description { color: #839074; font-size: 10px; margin-block: 5px 14px; }
.workspace_metrics { display: grid; gap: 10px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.workspace_metric { background: #eef2e3; border: 1px solid #e0e6d1; border-radius: 8px; padding: 11px 13px; }
.workspace_metric p { color: #6b7958; font-size: 9px; }
.workspace_metric strong { display: block; font-family: Georgia, 'Times New Roman', serif; font-size: 30px; font-weight: 400; line-height: 1.2; margin-block: 3px; }
.workspace_metric span { color: #839171; font-size: 8px; }
.workspace_metric_alt { background: #f4efe3; border-color: #e8e0ce; }
.project_board { margin-block-start: 16px; }
.project_board_header { align-items: center; display: flex; gap: 8px; justify-content: space-between; margin-block-end: 5px; }
.project_board_header h2 { font-size: 10px; font-weight: 600; }
.project_board_header span { color: #8d977e; font-size: 8px; }
.project_list { list-style: none; margin: 0; padding: 0; }
.project_list li { align-items: center; border-block-end: 1px solid #e8ebdf; display: flex; gap: 9px; min-block-size: 40px; padding-block: 7px; }
.project_swatch { align-items: center; block-size: 25px; border-radius: 6px; display: flex; flex-shrink: 0; font-family: Georgia, serif; font-size: 12px; inline-size: 25px; justify-content: center; }
.project_swatch_olive { background: #dbe2c3; color: #586645; }
.project_swatch_clay { background: #ead9cb; color: #8b6754; }
.project_swatch_blue { background: #d4dfe0; color: #5a7376; }
.project_list strong { font-size: 9px; font-weight: 600; }
.project_list p { color: #8b967e; font-size: 8px; margin-block-start: 1px; }
.project_state { color: #889573; font-size: 8px; margin-inline-start: auto; }
.workspace_hint { color: #8c967e; font-size: 8px; margin-block-start: 11px; }
.workspace_layout[data-collapsed="true"] { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) { .workspace_layout { grid-template-columns: 220px minmax(0, 1fr); } .workspace_scroll_area { padding: 30px; } .workspace_metric { padding: 18px; } .workspace_title_row h1 { font-size: 38px; } .project_list li { min-block-size: 57px; } }
@media (max-width: 500px) { body { padding: 8px; } .workspace_layout { align-content: start; block-size: auto; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto; min-block-size: calc(100svh - 16px); } .workspace_header { padding-inline: 10px; } .workspace_status { display: none; } .sidebar_scroll_area { border-block-end: 1px solid #e0e5d6; border-inline-end: 0; gap: 12px; overflow: visible; padding: 12px; } .workspace_brand, .workspace_account { display: none; } .workspace_navigation { grid-template-columns: repeat(2, minmax(0, 1fr)); } .navigation_group { grid-column: 1 / -1; } .navigation_label { display: none; } .project_views { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); } .workspace_scroll_area { overflow: visible; padding: 18px 15px; } .project_state { display: none; } .workspace_metrics { gap: 8px; } .workspace_metric { padding: 10px; } }
함께 쓰는 파일 5개 보기
script.js독립 구현
파일 저장

/* Original learning example. Copyright (c) 2026 StyleGallery contributors. MIT. */
(() => {
'use strict';
const id = "21st-49e34572c05d";
function reportPreviewError(error) { parent.postMessage({type:'sg-preview-error',id,message:String(error?.message || error || 'Initialization failed')},'*'); }
window.addEventListener('error', event => reportPreviewError(event.error || event.message));
window.addEventListener('unhandledrejection', event => reportPreviewError(event.reason));
try {
function createNavigationState() {
  return { selected: 'overview', groupOpen: false, collapsed: false };
}
function updateNavigation(state, action) {
  if (action.type === 'collapse') return { ...state, collapsed: !state.collapsed };
  if (action.type === 'group') return { ...state, groupOpen: !state.groupOpen };
  if (action.type === 'select' && ['overview', 'notes', 'active', 'archive'].includes(action.id)) return { ...state, selected: action.id };
  return state;
}

let state = createNavigationState();
const shell = document.querySelector('.workspace_layout');
const sidebar = document.querySelector('#workspace-navigation');
const toggle = document.querySelector('[data-sidebar-toggle]');
const group = document.querySelector('[data-projects-toggle]');
const childViews = document.querySelector('#project-views');
const titles = { overview: 'Overview', notes: 'Notes', active: 'Active projects', archive: 'Archive' };
const views = {
  overview: { eyebrow: 'MONDAY, IN FOCUS', description: 'A clear view of the work ahead.', metrics: ['In progress', '03', 'Ideas taking shape', 'Collected notes', '06', 'Small sparks, kept close'], heading: 'On the workbench', items: [['Atlas journal', 'Editorial · finding the rhythm', 'Making'], ['Paper garden', 'Objects · a study in texture', 'Sketching'], ['Sunday sounds', 'Identity · notes in the margins', 'Exploring']] },
  notes: { eyebrow: 'THOUGHTS, KEPT CLOSE', description: 'A quiet place for unfinished ideas.', metrics: ['Collected notes', '06', 'Three shown below', 'New this week', '02', 'Worth coming back to'], heading: 'Pinned thoughts', items: [['Try a wider margin', 'Atlas journal · layout note', 'Idea'], ['A shade of late summer', 'Paper garden · color note', 'Color'], ['Listen for the spaces', 'Sunday sounds · first thought', 'Sound']] },
  active: { eyebrow: 'YOUR WORK / ACTIVE', description: 'Three projects with room to grow.', metrics: ['In progress', '03', 'Every project has a next step', 'Ready to review', '01', 'A fresh set of eyes'], heading: 'Active projects', items: [['Atlas journal', 'Next · review the opening spread', 'Review'], ['Paper garden', 'Next · test the folded sample', 'Making'], ['Sunday sounds', 'Next · choose a direction', 'Exploring']] },
  archive: { eyebrow: 'YOUR WORK / ARCHIVE', description: 'Finished chapters, ready to revisit.', metrics: ['Completed', '02', 'A small collection of good work', 'On the shelf', '02', 'Nothing is lost by closing it'], heading: 'Past projects', items: [['Common ground', 'Identity · a neighborhood table', 'Finished'], ['A slow morning', 'Print · a small seasonal edition', 'Finished']] }
};
function renderNavigation() {
  const view = views[state.selected];
  if (state.collapsed && sidebar.contains(document.activeElement)) toggle.focus();
  if (!state.groupOpen && childViews.contains(document.activeElement)) group.focus();
  shell.dataset.collapsed = String(state.collapsed); sidebar.hidden = state.collapsed;
  toggle.setAttribute('aria-expanded', String(!state.collapsed)); toggle.setAttribute('aria-label', state.collapsed ? 'Show navigation' : 'Hide navigation');
  group.setAttribute('aria-expanded', String(state.groupOpen)); childViews.hidden = !state.groupOpen;
  document.querySelector('[data-group-chevron]').textContent = state.groupOpen ? '−' : '+';
  document.querySelectorAll('[data-view]').forEach(button => { if (button.dataset.view === state.selected) button.setAttribute('aria-current', 'page'); else button.removeAttribute('aria-current'); });
  document.querySelector('[data-current-view]').textContent = titles[state.selected];
  document.querySelector('[data-view-heading]').textContent = titles[state.selected];
  document.querySelector('[data-section-label]').textContent = view.eyebrow;
  document.querySelector('[data-view-description]').textContent = view.description;
  ['metric-label', 'metric-value', 'metric-note', 'second-label', 'second-value', 'second-note'].forEach((key, index) => { document.querySelector('[data-' + key + ']').textContent = view.metrics[index]; });
  document.querySelector('[data-board-heading]').textContent = view.heading;
  document.querySelector('[data-board-count]').textContent = view.items.length + ' shown';
  const list = document.querySelector('[data-project-list]'); list.replaceChildren();
  view.items.forEach((item, index) => {
    const li = document.createElement('li'), swatch = document.createElement('span'), text = document.createElement('div'), title = document.createElement('strong'), note = document.createElement('p'), status = document.createElement('span');
    swatch.className = 'project_swatch project_swatch_' + ['olive', 'clay', 'blue'][index % 3]; swatch.setAttribute('aria-hidden', 'true'); swatch.textContent = item[0][0]; title.textContent = item[0]; note.textContent = item[1]; status.className = 'project_state'; status.textContent = item[2]; text.append(title, note); li.append(swatch, text, status); list.append(li);
  });
}
toggle.addEventListener('click', () => { state = updateNavigation(state, { type: 'collapse' }); renderNavigation(); document.querySelector('[data-nav-status]').textContent = (state.collapsed ? 'Navigation hidden. ' : 'Navigation restored. ') + titles[state.selected] + ' remains selected.'; });
group.addEventListener('click', () => { state = updateNavigation(state, { type: 'group' }); renderNavigation(); });
document.querySelectorAll('[data-view]').forEach(button => button.addEventListener('click', () => { state = updateNavigation(state, { type: 'select', id: button.dataset.view }); renderNavigation(); document.querySelector('[data-nav-status]').textContent = titles[state.selected] + ' selected. The same selection is shown in the header.'; }));
renderNavigation();

parent.postMessage({type:'sg-preview-ready',id},'*');
} catch (error) { reportPreviewError(error); }
})();
LICENSE독립 구현
파일 저장

MIT License

Copyright (c) 2026 StyleGallery contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

This notice covers only the independently authored StyleGallery learning
example. The observed 21st source's reuse license remains unresolved.
NOTES.md독립 구현
파일 저장

# Fieldwork · A workspace that remembers

Independent observation study. Domain: website consumer / Design Engineering. This is an unaffiliated learning example.

The shell stores selected view, group disclosure and sidebar visibility independently. Hiding navigation releases its space while the breadcrumb and content continue to read the same selection.

## Observation and original-source boundary

Observed public listing: [Dashboard Sidebar](https://21st.dev/@arunjdass/components/dashboard-sidebar). Observed demo URL: https://cdn.21st.dev/arunjdass/dashboard-sidebar/default/bundle.1781812420628.html.

The observed sidebar reveals a Projects group, updates Active selection and the breadcrumb together, then hides and restores the navigation while retaining that selection. The observation does not establish search, routing, sign-out or real loading behavior.

Original source acquired: false. Original source acquisition status: unresolved. The supplied Stage 3 record says: 정확한 공개 demo 전체가 있으나 게시자 license 빈값; 같은 username GitHub 공개 경로 404. That status is preserved, not re-investigated or replaced by this implementation's license.

The Fieldwork identity, project content, SVG mark, native buttons, disclosure relationships, focus restoration and narrow-width document flow are new implementation choices.

The new copy, artwork, code and documentation are covered by the bundled MIT notice. No original source file, asset, distinctive copy, screenshot or brand is included. The evidence revision does not grant a license for the observed original.

## Evidence identity

sourceRevision: 63aea6728c5d45bb91cb42aabe617fa4410de4ee31a7815cf48cfabbc91f3584

The revision is SHA-256 of the JSON sourceEvidence object in the manifest. It binds the complete Stage 3 index and SceneBook hashes, this record's observation, and its named PNG and metadata hashes. It identifies actual research evidence, not an original Git checkout or a runtime result.

- Stage 3 index: artifacts/full-import/21st-author-sources/stage3/index.json, SHA-256 a9bd0da9712ea0a24e26b3612bfc87a09e13ee5cbb318eb97fe375d8c18d8c08.
- SceneBook: artifacts/full-import/21st-author-sources/stage3/SCENEBOOK.md, SHA-256 140b65a2248332fd415c9a4f42879cc060004f290ea83232af68558f2011e564.
- Relevant frame names: sidebar-initial, sidebar-projects, sidebar-selected, sidebar-collapsed-settled, sidebar-restored.

The initial PNG for this record was directly viewed during implementation as explicitly requested. Other states follow the Stage 3 transcription; their bytes and metadata hashes were checked, not claimed as newly observed interactions.

## Running and editing

Open index.html directly. The page contains its CSS, JavaScript and original SVG artwork and makes no outside requests. A content-security policy restricts runtime content to inline script/styles and data/blob images. There is no browser storage or remote write.

[styles.css](styles.css) and [script.js](script.js) are exact reference copies of the inline code. To edit a downloaded page, change its inline code or create your own build step. Editing only those companion copies does not change index.html. For repository generation, use StyleGallery/site as the working directory. The authoring modules are vendor/21st-studies/scenes.mjs and models.mjs; run node scripts/build-21st-studies.mjs there. Canonical Markdown lives in the parent StyleGallery checkout, so a binding such as motion/accessible-motion.md resolves as ../motion/accessible-motion.md from the site directory. Generation needs that parent corpus and the private research packet; running the download does not. A rebuild overwrites generated files.

Initial state: Overview selected; Projects closed; sidebar expanded; no transition animation.

## Interaction and sizing contract

The initial composition targets 640 × 420 CSS pixels. Navigation changes are immediate in every motion preference. The shell owns separate navigation and content scrolling at larger widths; at 320 pixels its stacked presentation switches to document scrolling. The header toggle remains available when navigation is hidden. No horizontal pan or wheel interception is used. These are implementation contracts to check in the rendered page, not conformance results.

## Verification boundary

The generator verifies evidence bytes against recorded hashes, three-ID membership, unresolved source status, canonical Markdown anchors and hashes, named authored functions, JavaScript parsing, semantic landmarks, ID/label relationships, inline-copy equality, and forbidden runtime destinations/storage. This record's model checks cover selection survives collapse and invalid routes are rejected. These are source/model checks, not rendered interaction checks.

[KNOWLEDGE.json](KNOWLEDGE.json) holds actionable checks with status not_executed. runtime_verified: false. Browser interaction, full keyboard paths, screen-reader output, comfort and performance remain unverified. In the site checkout, research/full-import/preview-posters.md is the central SG initial-render report, artifacts/full-import/preview-posters/index.json owns capture receipts, and artifacts/full-import/preview-posters/visual-review.json owns frame observations. These private reports are not bundled in the download. Initial-render evidence does not change the interaction boundary.

consumer_reference: consumer-reference/agent-native/registry.json
KNOWLEDGE.json독립 구현
파일 저장

[
  {
    "id": "21st-49e34572c05d-study-0",
    "source": "design-engineering/state-management/patterns/single-owner.md",
    "anchor": "state-model-and-ownership",
    "sourceSha256": "0657203d6f5cfcd2545c0f834b03e316efdc3f7d5fe8cbec868388bac9ce65ee",
    "title": "Fieldwork · A workspace that remembers · renderNavigation",
    "principle": "Selected view belongs to the workspace; navigation visibility and disclosure do not create another selected value.",
    "application": "The shell stores selected view, group disclosure and sidebar visibility independently. Hiding navigation releases its space while the breadcrumb and content continue to read the same selection. This binding describes the authored study; it is not verification of the original.",
    "code": [
      {
        "file": "vendor/21st-studies/records/21st-49e34572c05d/script.js",
        "symbol": "renderNavigation",
        "explanation": "This named function is part of the exact inline runtime in the downloadable HTML. codeFiles records the matching authored source hash."
      }
    ],
    "checks": [
      {
        "id": "21st-49e34572c05d-0-renderNavigation",
        "action": "Open Projects, select Active, hide navigation, then show it again.",
        "expected": "Active projects remains in the breadcrumb and heading; its navigation button still has aria-current=\"page\" when restored.",
        "status": "not_executed"
      }
    ],
    "runtime_verified": false
  },
  {
    "id": "21st-49e34572c05d-study-1",
    "source": "design-engineering/native-interaction-contracts.md",
    "anchor": "mechanisms-and-obligations",
    "sourceSha256": "07c5ea356070efcea644a7ef5cef988f624a55dd01dc3f9b8e9bd08130286164",
    "title": "Fieldwork · A workspace that remembers · renderNavigation",
    "principle": "A disclosure button exposes its current state and controlled content; hiding a surface must not strand focus within it.",
    "application": "The shell stores selected view, group disclosure and sidebar visibility independently. Hiding navigation releases its space while the breadcrumb and content continue to read the same selection. This binding describes the authored study; it is not verification of the original.",
    "code": [
      {
        "file": "vendor/21st-studies/records/21st-49e34572c05d/script.js",
        "symbol": "renderNavigation",
        "explanation": "This named function is part of the exact inline runtime in the downloadable HTML. codeFiles records the matching authored source hash."
      }
    ],
    "checks": [
      {
        "id": "21st-49e34572c05d-1-renderNavigation",
        "action": "Use Tab and Enter or Space to open Projects and toggle navigation. Inspect focus after collapsing and restoring.",
        "expected": "The relevant aria-expanded values match visibility. Focus stays on the invoked toggle, and hidden child buttons leave the Tab order.",
        "status": "not_executed"
      }
    ],
    "runtime_verified": false
  }
]
RETRIEVAL-LOG.md독립 구현
파일 저장

# Retrieval log

Task: create exactly three independent runnable learning examples from the Stage 3 public observation packet; preserve IDs and unresolved original-source acquisition.

1. Root AGENTS.md and README.md: domain ownership and guided local retrieval.
2. DOMAINS.md and GOVERNANCE.md: product-layer adaptation, generated artifacts, evidence separation.
3. SG discover: compiler configured at revision 6deb1970c80ed113b90be8d6ca617616bbe336c4. SG workflow read; SHA-256 0318a99707293af423e52b99b40cf1d87d9c7725e3ab06c5a231e3946f2bebb9. The provided workflow snapshot carries the same contract.
4. artifacts/full-import/21st-author-sources/stage3/SCENEBOOK.md and artifacts/full-import/21st-author-sources/stage3/index.json: primary implementation handoff. No original source code was inspected or copied.
5. design-engineering/index.md → decision-tree.md → component-contract.md → state-management/patterns/single-owner.md, state-management/recipes/multi-step-form.md and native-interaction-contracts.md: state, draft and disclosure ownership.
6. motion/index.md → accessible-motion.md and observed-choreography.md: stationary path and observation limits. Scroll-specific pin and choreography contracts were rejected because these three observations have no scroll choreography.
7. Opened atc-initial.png, sidebar-initial.png and form-initial.png through local image inspection because the delegated task explicitly requested actual PNG review. The authored output does not contain those images.

KNOWLEDGE.json contains exact current-authoring Markdown paths, real anchors and SHA-256 values for the applied sections. No design-terminology records are used. Named external sources inside the canonical guides were not re-checked in this lane; only local guidance and the supplied observation evidence were checked.

No canonical corpus edits, original 21st source-pack edits, shared UI edits, source-acquisition changes or new worker sessions belong to this lane.

consumer_reference: consumer-reference/agent-native/registry.json