feat: strengthen design skill and refresh all showcases
Some checks failed
ci / build (push) Has been cancelled

This commit is contained in:
Yun Chan 2026-09-12 18:16:33 +09:00
parent 69e5cc1efd
commit e09d4efb1c
257 changed files with 8196 additions and 2374 deletions

View file

@ -0,0 +1,24 @@
# 가온 학적부 디자인 기록
## 브리프
- 무엇을: 학원 강사가 수업·출석·과제·상담을 기록으로 확인하는 학적부형 관리 화면.
- 누구에게: 행과 상태를 빠르게 대조하고 다음 업무를 정해야 하는 강사.
- 제약: 예시 데이터와 기존 계산·상태·모바일 내비·대화상자·인쇄 흐름을 보존한다.
## 판단 기준
| 축 | 질문 | 적용 |
| --- | --- | --- |
| Warde · 정보 전달 | 다음 확인 대상이 수치보다 먼저 읽히는가 | 공통 정렬선과 가는 구분선의 요약 기록 밴드, 15px 표 본문 |
| Ruder · 비교 변수 | 같은 종류의 값이 같은 열과 숫자 폭에서 비교되는가 | 표 숫자에 tabular figures, 수치 열 오른쪽 정렬 |
| Albers · 상태 맥락 | 상태색이 주변의 중립 기록면에서 구분되는가 | 상태는 텍스트와 함께 유지하고 배경색은 보조로 사용 |
Warde, Ruder, Albers는 정보 전달·비교 변수·상태 맥락을 검토하는 참고 질문이며 인과나 성능 법칙이 아니다.
## 역할 토큰과 검증
- `--record-radius`, `--record-rule`, `--record-body-size`, `--record-meta-size`가 기록면·구분선·본문·보조정보 역할을 고정한다.
- `verify-final` 18개 묶음은 16개가 즉시 통과했다. 전체 묶음의 invariant·visual 검사는 Gaon·Dure를 대상으로 하며, 나머지 2개는 수정 뒤 release 독립 재실행에서 ALL PASS·ALL MATCH였다.
- root는 28개 모바일, 21개 데스크톱, 7개 하위 경로와 30개 앱 상태를 직접 검토해 수용했다. stress 112건은 hard failure 0이며, 가로 넘침 4건은 targeted fix 뒤 390px 재측정에서 0이다.
- 이 기록은 렌더·계약 범위의 현재 결과다. 모든 폭·키보드 경로·인쇄 등 개별 기능을 완전 재실행했다는 뜻이 아니며, 참여자 과업 효과도 미측정이다.

View file

@ -9,15 +9,15 @@
<link rel="manifest" href="./manifest.webmanifest" />
<link rel="apple-touch-icon" href="./icon-192.png" />
<meta property="og:type" content="website" />
<meta property="og:title" content="가온 학적부 — 학습관리 콘솔" />
<meta property="og:title" content="가온 학적부 — 학습 관리" />
<meta property="og:url" content="https://designpaca.chanpaca.net/work/gaon-lms/" />
<meta property="og:image" content="https://designpaca.chanpaca.net/work/gaon-lms/og.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="가온 학적부 — 학습관리 콘솔" />
<meta name="twitter:title" content="가온 학적부 — 학습 관리" />
<meta name="twitter:image" content="https://designpaca.chanpaca.net/work/gaon-lms/og.png" />
<meta
name="description"
content="학원 강사를 위한 학습관리 대시보드. 출석부, 수강생 기록, 과제 채점, 학사 일정까지 여덟 화면. 데모 데이터로 실제로 작동합니다."
content="학원 강사를 위한 학습 관리 화면입니다. 출석, 수강생 기록, 과제 채점, 학사 일정을 한곳에서 살펴볼 수 있습니다."
/>
<meta property="og:title" content="가온 학적부 · 학습관리 대시보드" />
<meta property="og:description" content="출석부에서 출발한 학습관리 대시보드." />
@ -35,7 +35,7 @@
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Noto+Serif+KR:wght@500;600&display=swap"
/>
<link rel="stylesheet" href="./styles/main.css?v=24" />
<link rel="stylesheet" href="./styles/main.css?v=20260912all" />
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%23fafafa'/%3E%3Crect x='6.5' y='6.5' width='19' height='19' rx='4' fill='%23059669'/%3E%3Crect x='10' y='12' width='12' height='1.8' rx='0.9' fill='%23ffffff'/%3E%3Crect x='10' y='15.5' width='8' height='1.8' rx='0.9' fill='%23ffffff' opacity='0.85'/%3E%3Crect x='10' y='19' width='10' height='1.8' rx='0.9' fill='%23ffffff' opacity='0.7'/%3E%3C/svg%3E"
@ -141,6 +141,7 @@
<div>
<h2 class="v-h" id="dash-h">이번 주, 열두 명 중 열 명</h2>
<p class="view-sub">3월 9일 월요일 오전 기준. 명단 12명 중 재학 10명, 주간 출석은 지난 주(3/2–3/6) 결산입니다.</p>
<p class="panel-note muted" role="note">표시된 수강생·수업·상담 기록은 예시입니다. 이 화면에서 바꾼 내용은 새로고침하면 초기화됩니다.</p>
</div>
</header>
@ -217,6 +218,7 @@
<span class="key"><i class="k-abs"></i>결석</span>
</figcaption>
</figure>
<div class="table-wrap" tabindex="0" aria-label="요일별 출석 결산 표 가로 스크롤">
<table class="wk-sum">
<caption class="sr-only">요일별 출석 결산</caption>
<thead><tr><th scope="col">요일</th><th scope="col" class="num">출석</th><th scope="col" class="num">지각</th><th scope="col" class="num">결석</th><th scope="col" class="num">출석률</th></tr></thead>
@ -229,6 +231,7 @@
</tbody>
<tfoot><tr><td>합계</td><td class="num">46</td><td class="num">2</td><td class="num">2</td><td class="num"><span class="hl">92%</span></td></tr></tfoot>
</table>
</div>
</section>
<!-- 마감 임박 -->
@ -550,7 +553,7 @@
</thead>
<tbody id="file-body"><!-- JS --></tbody>
</table>
<p class="panel-note muted">다운로드 링크를 만들지 않는다 — 이 데모에는 파일이 없다. 배부 상태만 기록한다.</p>
<p class="panel-note muted">배부 여부와 열람 현황을 확인하는 목록입니다. 원본 파일은 이 화면에서 제공하지 않습니다.</p>
</div>
</section>
@ -798,7 +801,7 @@
<script src="calc.js?v=2"></script>
<script>
/*
앱 상태는 전부 이 안에 있다. 서버도 localStorage 도 없다 — 데모 원칙.
화면 상태는 새로고침하면 초기화한다. 서버와 localStorage에는 저장하지 않는다.
뷰 전환 · 검색/필터 · 출석 순환 · 배부 상태 · 리포트 계산까지
네이티브 기능 위에 얹은 얇은 JS.
*/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Before After
Before After

View file

@ -76,6 +76,7 @@ h3 { font-size: var(--step-0); font-weight: 500; }
display: grid;
grid-template-columns: 244px minmax(0, 1fr);
min-height: 100dvh;
min-width: 0;
background: var(--background);
}
@ -149,7 +150,7 @@ h3 { font-size: var(--step-0); font-weight: 500; }
.topbar .brand { display: none; } /* 브랜드는 사이드바에 있다 — 모바일에서만 탑바가 이어받는다 */
.crumb { display: flex; align-items: center; gap: var(--space-2); font-size: var(--step-0); color: var(--muted-foreground); }
.crumb strong { color: var(--foreground); font-weight: 600; }
.topbar-right { display: flex; align-items: center; gap: var(--space-3); }
.topbar-right { display: flex; min-width: 0; align-items: center; gap: var(--space-3); }
.term-badge {
display: inline-flex; align-items: center;
font-size: var(--step-1); font-weight: 500;
@ -170,11 +171,11 @@ h3 { font-size: var(--step-0); font-weight: 500; }
/* ── 본문 ── */
.main { min-width: 0; display: flex; flex-direction: column; }
.view { display: none; }
.view { display: none; min-width: 0; }
.view.is-on { display: block; }
.view:focus { outline: none; }
.view-sub { color: var(--muted-foreground); font-size: var(--step-0); margin-top: 4px; max-width: 46em; }
.view-sub { color: var(--muted-foreground); font-size: var(--record-meta-size); margin-top: 4px; max-width: 46em; }
.view-head {
display: flex; justify-content: space-between; align-items: flex-start;
gap: var(--space-4);
@ -193,23 +194,23 @@ h3 { font-size: var(--step-0); font-weight: 500; }
.panel {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
border-radius: var(--record-radius);
box-shadow: none;
padding: var(--space-5);
min-width: 0;
}
.panel h2 { margin-bottom: var(--space-4); font-size: var(--step-2); }
.panel-note { font-size: var(--step-1); color: var(--faint); margin-top: var(--space-3); }
.panel-note { font-size: var(--record-meta-size); color: var(--faint); margin-top: var(--space-3); }
/* 통계 카드 */
.headline { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4); }
.headline { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; background: var(--card); border: 1px solid var(--record-rule); border-radius: var(--record-radius); box-shadow: none; font-variant-numeric: tabular-nums; }
.headline > div {
background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm); padding: var(--space-4) var(--space-5);
min-width: 0; padding: var(--space-4) var(--space-5) var(--space-5); background: transparent; border: 0; border-right: 1px solid var(--record-rule); border-radius: 0; box-shadow: none;
}
.headline dt { font-size: var(--step-0); color: var(--muted-foreground); font-weight: 500; }
.headline > div:last-child { border-right: 0; }
.headline dt { font-size: var(--record-meta-size); color: var(--muted-foreground); font-weight: 500; }
.headline dd { font-size: var(--step-3); font-weight: 600; letter-spacing: -0.03em; line-height: 1.2; font-variant-numeric: tabular-nums; margin-top: 4px; }
.headline .stat-sub { font-size: var(--step--1); color: var(--faint); margin: 6px 0 0; margin-inline-start: 0; font-variant-numeric: tabular-nums; }
.headline .stat-sub { font-size: var(--record-meta-size); color: var(--faint); margin: 6px 0 0; margin-inline-start: 0; font-variant-numeric: tabular-nums; }
.dash-grid {
display: grid;
@ -221,15 +222,19 @@ h3 { font-size: var(--step-0); font-weight: 500; }
.week-panel { grid-row: span 2; }
/* ── 표 ── */
.table-wrap { overflow-x: auto; overflow-y: hidden; }
table { width: 100%; border-collapse: collapse; }
.table-wrap { max-width: 100%; overflow-x: auto; overflow-y: hidden; overscroll-behavior-inline: contain; scrollbar-gutter: stable both-edges; }
table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.table-wrap > table { min-width: 600px; }
#stu-table, #att-table, #as-table { min-width: 760px; }
#report-table { min-width: 680px; }
table th, table td { overflow-wrap: normal; word-break: keep-all; }
thead th {
text-align: left; font-weight: 500; font-size: var(--step-1);
text-align: left; font-weight: 600; font-size: var(--record-meta-size);
color: var(--muted-foreground); padding: 0 var(--space-3) var(--space-3);
border-bottom: 1px solid var(--border);
height: 36px; white-space: nowrap;
}
tbody td, tbody th { padding: var(--space-3); border-bottom: 1px solid var(--border); vertical-align: middle; text-align: left; }
tbody td, tbody th { padding: var(--space-3); border-bottom: 1px solid var(--border); vertical-align: middle; text-align: left; font-size: var(--record-body-size); line-height: 1.55; }
tbody th { font-weight: 500; }
tbody tr:last-child td, tbody tr:last-child th { border-bottom: 0; }
tbody tr { transition: background-color 80ms ease; }
@ -239,7 +244,7 @@ tbody tr:hover { background: var(--muted); }
.row-open {
appearance: none; border: 0; background: none; padding: 0;
font: inherit; font-weight: 500; color: var(--foreground);
cursor: pointer; text-align: left;
min-height: 24px; cursor: pointer; text-align: left;
transition: color 80ms ease;
}
.row-open:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
@ -285,8 +290,8 @@ tbody tr:hover { background: var(--muted); }
.wk-sum thead th { padding: 8px var(--space-2); }
.wk-sum tbody td { padding: 7px var(--space-2); }
.wk-sum tfoot td { padding: 8px var(--space-2); border-top: 1px solid var(--border-strong); font-weight: 600; }
.wk-sum td:first-child, .wk-sum th:first-child { padding-left: 0; white-space: nowrap; }
.wk-sum td:last-child, .wk-sum th:last-child { padding-right: 0; }
.wk-sum td:first-child, .wk-sum th:first-child { padding-left: var(--space-2); white-space: nowrap; }
.wk-sum td:last-child, .wk-sum th:last-child { padding-right: var(--space-2); }
.key { display: inline-flex; align-items: center; gap: 6px; }
.key i { width: 8px; height: 8px; display: inline-block; border-radius: var(--radius-xs); }
.k-att { background: var(--border-strong); }
@ -373,7 +378,7 @@ tbody tr:hover { background: var(--muted); }
background: none; color: var(--muted-foreground);
border: 1px solid var(--border-strong); border-radius: var(--radius);
height: 28px; padding: 0 10px;
transition: all 100ms ease;
transition: background-color 100ms ease, border-color 100ms ease, color 100ms ease;
white-space: nowrap;
}
.give-btn:hover { border-color: var(--foreground); color: var(--foreground); }
@ -387,7 +392,7 @@ tbody tr:hover { background: var(--muted); }
align-items: start;
}
.cal { table-layout: fixed; }
.cal th { text-align: center; font-size: var(--step-1); height: 32px; padding: 0; }
.cal th { text-align: center; font-size: var(--step-1); height: 32px; padding-inline: var(--space-2); }
.cal-day {
height: 76px; padding: var(--space-1);
border: 1px solid var(--border);
@ -454,6 +459,8 @@ tbody tr:hover { background: var(--muted); }
/* ── 모달 ── */
.modal {
position: fixed;
inset: 0;
margin: auto;
width: min(94vw, 860px);
max-height: min(86dvh, 760px);
@ -463,8 +470,11 @@ tbody tr:hover { background: var(--muted); }
background: var(--card);
color: var(--foreground);
overflow: hidden;
overscroll-behavior: contain;
box-shadow: var(--shadow-xl);
}
html:has(dialog.modal[open]) { overflow: hidden; }
.modal::backdrop { background: var(--backdrop-dim); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }
@media (prefers-reduced-motion: no-preference) {
.modal[open] { animation: modal-in 200ms cubic-bezier(0.16, 1, 0.3, 1) both; }
@ -613,6 +623,8 @@ tbody tr:hover { background: var(--muted); }
padding-top: max(0px, var(--safe-top));
}
.topbar .brand { display: flex; flex: none; padding: 0; }
.topbar-right { flex-wrap: wrap; justify-content: flex-end; gap: var(--space-1); }
.topbar-right > * { min-width: 0; }
.crumb { display: none; }
.main { padding-bottom: calc(72px + var(--safe-bottom)); } /* 바텀 내비 + 홈 인디케이터 확보 */
.dash-grid, .cal-layout, .counsel-grid { grid-template-columns: minmax(0, 1fr); }
@ -620,7 +632,7 @@ tbody tr:hover { background: var(--muted); }
.dist-body { grid-template-columns: minmax(0, 1fr); }
.view-head { flex-direction: column; align-items: flex-start; gap: var(--space-2); padding: var(--space-5) var(--space-4) var(--space-4); }
.view > *:not(.view-head) { margin-inline: var(--space-4); }
.headline { gap: var(--space-2); }
.headline { gap: 0; }
.term-badge { display: none; }
.cal-day { height: 56px; }
}
@ -634,24 +646,26 @@ tbody tr:hover { background: var(--muted); }
.mcover-mark { font-size: 5rem; }
.msheet { padding: var(--space-4); min-height: 0; }
.headline { grid-template-columns: minmax(0, 1fr); }
.headline > div { border-right: 0; border-bottom: 1px solid var(--record-rule); }
.headline > div:last-child { border-bottom: 0; }
}
/* ── 알림 센터 ── */
.bell-wrap { position: relative; }
.bell {
position: relative; width: 32px; height: 32px;
position: relative; width: 44px; height: 44px;
display: inline-flex; align-items: center; justify-content: center;
border-radius: var(--radius); border: none; background: none; cursor: pointer;
color: var(--muted-foreground); transition: background-color 80ms ease, color 80ms ease;
}
.bell:hover, .bell.is-open { background: var(--muted-2); color: var(--foreground); }
.bell-dot {
position: absolute; top: 4px; right: 4px;
position: absolute; top: 8px; right: 8px;
width: 8px; height: 8px; border-radius: var(--radius-pill);
background: var(--danger); border: 2px solid var(--background);
}
.bell-menu {
display: none; position: absolute; top: 40px; right: 0; z-index: 40;
display: none; position: absolute; top: 52px; right: 0; z-index: 40;
width: min(340px, calc(100vw - 32px));
background: var(--card); border: 1px solid var(--border);
border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); overflow: hidden;
@ -679,6 +693,21 @@ tbody tr:hover { background: var(--muted); }
.ntc.is-read p { color: var(--muted-foreground); }
.ntc time { display: block; font-size: var(--step--1); color: var(--faint); margin-top: 2px; }
/* 좁거나 낮은 화면에서는 종 모서리가 아니라 뷰포트가 알림판의 기준이다.
상대 위치를 유지하면 340px 패널이 좌측으로 밀리고 긴 목록이 하단 내비 아래로 사라진다. */
@media (max-width: 680px) {
.bell-menu {
position: fixed;
top: calc(56px + var(--safe-top) + var(--space-2));
right: max(var(--space-3), var(--safe-right));
bottom: calc(72px + var(--safe-bottom) + var(--space-2));
left: max(var(--space-3), var(--safe-left));
width: auto;
max-height: none;
overflow-y: auto;
}
}
/* ── 태그 칩 (유형·사유) ── */
.tag {
display: inline-flex; align-items: center; white-space: nowrap;
@ -920,15 +949,15 @@ kbd {
border-top: 1px solid var(--border);
padding: 6px max(var(--space-2), var(--safe-left)) calc(6px + var(--safe-bottom)) max(var(--space-2), var(--safe-right));
}
@media (max-width: 900px) { .bottom-nav { display: grid; grid-auto-flow: column; } }
@media (max-width: 900px) { .bottom-nav { display: grid; min-width: 0; grid-template-columns: repeat(5, minmax(0, 1fr)); grid-auto-flow: row; } }
.bnav-item {
appearance: none; background: none; border: 0; cursor: pointer; font: inherit;
display: grid; justify-items: center; align-content: center; gap: 3px;
min-height: 52px; min-width: 48px; padding: 4px 2px;
min-height: 52px; min-width: 0; padding: 4px 2px;
border-radius: var(--radius);
color: var(--muted-foreground);
}
.bnav-item span { font-size: var(--step--2); font-weight: 500; }
.bnav-item span { max-width: 100%; font-size: var(--step--2); font-weight: 500; overflow-wrap: anywhere; }
.bnav-item.is-current { color: var(--accent-ink); font-weight: 600; }
.bnav-item.is-current svg { color: var(--accent); }

View file

@ -90,6 +90,11 @@
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--record-radius: 8px;
--record-rule: var(--border);
--record-body-size: 0.9375rem;
--record-meta-size: 0.8125rem;
/* 안전 영역 — viewport-fit=cover 선언 후에만 0이 아닌 값이 온다(WebKit 공식·MDN env()) */
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);