feat(skill): 자율 검증 폐쇄 루프 내재화 — 0.6.0
Some checks failed
ci / build (push) Failing after 5s

- 핵심 규칙 6: 완료의 정의는 게이트 통과. 사용자를 QA로 쓰지 않는다
- 5단계 기계 검사 4번째: design-gate 실행 의무화
- references/audit-gate.md: 사고-검사 매핑, SEO/meta 체크리스트, OS/브라우저 특성, 하니스 규칙
- tools/design-gate.mjs: 범용 게이트(메타/SEO·대비·수축·리듬·트랙·스케일×폭 + 옵션 L0/L3/L4, checks 깊은 병합)
- 리듬·트랙 불변식: 숫자 라벨 등폭·빈 셀 트랙 균일·등간격 — 시간표 자동배치 결함 재현 픽스처 검출, 앱 15뷰 통과(오탐 0)

feat(site): 관리 앱 2종 프로덕션 콘솔(v6→v18)

- 가온 학적부 9뷰·두레 수강신청 6뷰: shadcn 문법, Pretendard/Noto Serif/IBM Plex 폰트 전략, 볼드 금지(400/500/600), WCAG AA 대비 전면 교정, 한글 keep-all 조판
- LMS 필수 요소(알림 센터·공지·진도·평가 유형·출결 사유·학점 경고), 12명 기준 데이터 정합, SEO 구조(h1 유일·OG/twitter·og.png)
- 시간표 자동배치 결함 수리(명시적 격자 좌표)
- QA 게이트: L0 stylelint/html-validate · L1 단위 30 · L2 감사 61+불변식 · L3 시각회귀 30화면 · L4 WebKit · L5 키보드 탐색 — npm run verify 실패 시 배포 금지 체인
This commit is contained in:
Yun Chan 2026-08-22 21:22:05 +09:00
parent 78ddc8b61a
commit 72337b7ee0
226 changed files with 18212 additions and 380 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -0,0 +1,333 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>무월 (霧月) · 싱글오리진 원두 정기구독</title>
<meta
name="description"
content="원두마다 산미·단맛·바디를 같은 척도로 측정해 적고, 생두 매입가를 공개합니다. 격주 화요일 로스팅, 서울 서촌."
/>
<meta property="og:title" content="무월 (霧月) · 싱글오리진 원두 정기구독" />
<meta
property="og:description"
content="측정된 좌표로 원두를 고르는 격주 구독. 생두 매입 원장 공개."
/>
<meta property="og:type" content="website" />
<meta name="theme-color" content="#F4F4F0" />
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/webfontworld/naver/MaruBuri.css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap"
/>
<link rel="stylesheet" href="./styles/main.css?v=2" />
<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' fill='%23F4F4F0'/%3E%3Cpath d='M16 7a7 7 0 0 1 7 7h-7z' fill='%237A3E1D'/%3E%3Cpath d='M16 9a7 7 0 0 0-7 7h7z' fill='%23181B18'/%3E%3Ccircle cx='16' cy='16' r='9' fill='none' stroke='%23181B18' stroke-width='1.5'/%3E%3C/svg%3E"
/>
</head>
<body>
<a class="skip" href="#main">본문으로 건너뛰기</a>
<header class="site-head">
<div class="wrap head-inner">
<a class="wordmark" href="#main">
무월 <span class="wordmark-han">霧月</span>
</a>
<nav class="nav" aria-label="섹션">
<a href="#scale">척도</a>
<a href="#ledger">원장</a>
<a href="#recipe">레시피</a>
<a href="#plan">구독</a>
</nav>
</div>
</header>
<main id="main">
<!--
히어로 — R1(coffeecollective.dk): 가격을 첫 화면에서 숨기지 않는다.
문구만으로 시작하는 대신 이번 주 로스 사진 한 장과 원두 둘(값 포함)을 같이 둔다.
-->
<section class="hero" aria-labelledby="hero-h">
<div class="wrap hero-grid">
<div class="hero-text">
<p class="hero-label reveal">싱글오리진 로스터리 · 서울 서촌</p>
<h1 id="hero-h" class="reveal" style="--delay: 60ms">
취향은 형용사가 아니라<br />측정된 좌표입니다
</h1>
<p class="lead reveal" style="--delay: 120ms">
과일 향이 난다는 말로는 추출 온도를 정할 수 없습니다. 무월은 원두마다
산미와 단맛, 바디를 같은 척도로 측정해 적고, 생두를 얼마에 들였는지까지
공개합니다. 마셔보기 전에 판단할 수 있어야 원두 고르는 즐거움이 시작됩니다.
</p>
<div class="hero-actions reveal" style="--delay: 180ms">
<a class="btn" href="#plan">격주 구독 시작하기</a>
<p class="hero-note">격주 화요일 로스팅 출고 · 언제든 해지</p>
</div>
</div>
<figure class="hero-photo reveal" style="--delay: 100ms">
<img
src="./assets/beans-hero.webp"
alt="따뜻한 회색 리넨 위 질도기 그릇에 담긴 갓 볶은 원두와 놋쇠 스쿱"
width="900"
height="1200"
loading="eager"
fetchpriority="high"
/>
<figcaption>이번 주 첫 로스 · 8월 19일</figcaption>
</figure>
<!-- 이번 주 원두 — 카드가 아니라 원장의 첫 두 줄 -->
<dl class="hero-beans reveal" style="--delay: 240ms">
<div class="bean-row">
<div>
<dt>에티오피아 구지 우라가</dt>
<dd class="bean-origin">워시드 · 해발 2,150m · 커핑 88.75</dd>
</div>
<dd class="bean-price"><span>100g</span> 8,500원</dd>
</div>
<div class="bean-row">
<div>
<dt>콜롬비아 엘 파라이소</dt>
<dd class="bean-origin">혐기발효 · 해발 1,930m · 커핑 91.20</dd>
</div>
<dd class="bean-price"><span>100g</span> 11,000원</dd>
</div>
</dl>
</div>
</section>
<!--
척도 — 향미 좌표계. 히어로의 계기판 노릇을 내려놓고
본문의 척도로 옮긴다. 눈금과 점만 남긴다.
-->
<section id="scale" class="section" aria-labelledby="scale-h">
<div class="wrap scale-grid">
<div class="scale-text">
<h2 id="scale-h">같은 척도로 잰<br />향미 좌표</h2>
<p>
같은 밝기로 볶아도 생두가 머금은 수분과 밀도에 따라 열은 다르게
들어갑니다. 그래서 무월은 색이 아니라 1차 팝핑 후 감률과 커핑 점수로
로트를 기록합니다.
</p>
<p class="muted">
좌표는 로트마다 다시 측정해 옮겨 적습니다. 지난 로트의 좌표와 지금이
같다고 말하지 않습니다.
</p>
<ul class="scale-legend" aria-label="좌표 범례">
<li><span class="key" aria-hidden="true"></span>우라가 G1 워시드</li>
<li><span class="key key-b" aria-hidden="true"></span>엘 파라이소 혐기발효</li>
<li><span class="key key-c" aria-hidden="true"></span>엘 소코로 마라카투라</li>
</ul>
</div>
<figure class="scale-plot reveal">
<svg
viewBox="0 0 520 340"
role="img"
aria-label="산미-바디 2차원 좌표. 우라가는 산미 높음, 엘 파라이소는 단맛 높음, 엘 소코로는 바디 높음."
>
<!-- 눈금 격자 -->
<g class="grid">
<line x1="56" y1="40" x2="56" y2="280" />
<line x1="56" y1="280" x2="500" y2="280" />
<line x1="56" y1="160" x2="500" y2="160" />
<line x1="278" y1="40" x2="278" y2="280" />
<line x1="167" y1="40" x2="167" y2="280" stroke-dasharray="2 6" />
<line x1="389" y1="40" x2="389" y2="280" stroke-dasharray="2 6" />
<line x1="56" y1="100" x2="500" y2="100" stroke-dasharray="2 6" />
<line x1="56" y1="220" x2="500" y2="220" stroke-dasharray="2 6" />
</g>
<!-- 축 이름 -->
<g class="axis-labels">
<text x="56" y="302">산미 →</text>
<text x="500" y="302" text-anchor="end">바디 →</text>
<text x="46" y="46" text-anchor="end">10.0</text>
<text x="46" y="286" text-anchor="end">0</text>
</g>
<!-- 로트 점 -->
<g class="points">
<circle cx="140" cy="62" r="5" />
<text x="140" y="46" text-anchor="middle">우라가 8.8</text>
<circle cx="252" cy="92" r="5" class="pt-b" />
<text x="252" y="76" text-anchor="middle">엘 파라이소 9.0</text>
<circle cx="430" cy="196" r="5" class="pt-c" />
<text x="430" y="180" text-anchor="middle">엘 소코로 8.6</text>
</g>
</svg>
<figcaption>8월 3주차 로트 실측 · 좌표는 주마다 다시 놓입니다</figcaption>
</figure>
</div>
</section>
<!--
원장 — 이 페이지의 신뢰 장치 (R1: 거래 내역 공개가 이 업종의 스펙).
밴드를 살짝 눌러 표가 페이지의 중심임을 알린다.
-->
<section id="ledger" class="section band" aria-labelledby="ledger-h">
<div class="wrap">
<div class="ledger-head">
<h2 id="ledger-h">생두 매입 원장</h2>
<p class="muted">
농장에 돌아가는 돈이 얼마인지 보여주는 일이 스페셜티의 조건이라
생각합니다. 1kg당 매입가와 가공 방식까지 로트 단위로 남깁니다.
</p>
</div>
<div class="table-wrap reveal">
<table>
<caption class="sr-only">
원두별 생두 매입 원장 — 원두명, 생산자, 가공 방식, 매입가, 커핑 점수
</caption>
<thead>
<tr>
<th scope="col">원두</th>
<th scope="col">생산자</th>
<th scope="col">가공</th>
<th scope="col" class="num">매입가 (FOB/kg)</th>
<th scope="col" class="num">커핑</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">에티오피아 구지 우라가</th>
<td>하르소 콜루 세척장</td>
<td>워시드 · 72시간 발효</td>
<td class="num">$14.20</td>
<td class="num">88.75</td>
</tr>
<tr>
<th scope="row">콜롬비아 엘 파라이소</th>
<td>디에고 사무엘 베르무데스</td>
<td>혐기발효 · 열충격 2회</td>
<td class="num">$26.50</td>
<td class="num">91.20</td>
</tr>
<tr>
<th scope="row">과테말라 엘 소코로</th>
<td>후안 디에고 데 라 세르다</td>
<td>전량 워시드</td>
<td class="num">$16.80</td>
<td class="num">89.00</td>
</tr>
</tbody>
</table>
</div>
<p class="ledger-note muted">
운임·통관·관세는 포함하지 않은 현지 출하가이며, 환율은 매입일 기준입니다.
</p>
</div>
</section>
<!--
레시피 — 사진과 사양. 사양은 목록의 주인공이 아니라 사진의 각주다.
-->
<section id="recipe" class="section" aria-labelledby="recipe-h">
<div class="wrap recipe-grid">
<figure class="recipe-photo reveal">
<img
src="./assets/pourover.webp"
alt="고핫물이 필터 백에 담긴 도자기 드리퍼로 얇은 물줄기로 들어가는 중"
width="900"
height="1200"
loading="lazy"
/>
<figcaption>서촌 매장 · 오후 첫 잔</figcaption>
</figure>
<div class="recipe-text">
<h2 id="recipe-h">원두 18g, 물 288g</h2>
<p>
구독 패키지에는 원두 밀도에 맞춘 분쇄도와 주수 간격을 적은 카드 한 장이
들어갑니다. 드립의 변주는 그 다음 문제입니다.
</p>
<dl class="spec">
<div>
<dt>물 온도</dt>
<dd>93°C</dd>
</div>
<div>
<dt>분쇄도</dt>
<dd>코만단테 24딸</dd>
</div>
<div>
<dt>뜸 들이기</dt>
<dd>40초 · 물 40g</dd>
</div>
<div>
<dt>주수</dt>
<dd>3회 · 116g씩</dd>
</div>
</dl>
<p class="muted">
추출 시간은 2분 30초에서 3분 사이가 기준입니다. 그보다 빨리 흘러내리면
곱게 갈고, 늦으면 굵게 갑니다.
</p>
</div>
</div>
</section>
<!--
구독 — 회색조에서 가장 어두운 블록. 이 페이지의 유일한 강한 면.
(1단계 회색조 위계 계획의 실행: CTA가 1위 덩어리)
-->
<section id="plan" class="section plan" aria-labelledby="plan-h">
<div class="wrap plan-grid">
<div class="plan-text">
<h2 id="plan-h">격주, 서로 다른 좌표의<br />원두 두 종</h2>
<p>
2주마다 화요일 아침, 산미형 한 종과 밸런스형 한 종(각 100g)을 볶어
보냅니다. 원두는 시음 후 남는 양이 없어 다음 주에 같은 로트를 못 드릴 수
있습니다.
</p>
<ul class="plan-list">
<li>원두별 추출 카드 동봉 — 분쇄도·온도·주수</li>
<li>아로마 밸브 틴케이스 첫 회 증정</li>
<li>출장·휴가 주는 앱에서 건너뛰기</li>
</ul>
</div>
<div class="plan-box">
<p class="plan-price">
<span>28,000원</span><span class="plan-unit">회당 · 배송비 포함</span>
</p>
<a class="btn btn-inverse" href="#plan">격주 구독 시작하기</a>
<p class="plan-note">200g(100g×2) · 첫 회는 다음 화요일에 출고됩니다</p>
</div>
</div>
</section>
</main>
<footer class="site-foot">
<div class="wrap foot-inner">
<p>무월 로스터리 · 서울 종로구 옥인길 18</p>
<p class="muted">화요일 로스팅 · 목요일부터 토요일까지 문을 엽니다</p>
<p class="muted">roastery@muwol.kr</p>
</div>
</footer>
<script>
/* 목요일의 화원과 같은 문법 — 한 번 보이면 유지, 스크롤 이벤트 없음 */
const io = new IntersectionObserver(
(entries) => {
for (const e of entries) {
if (!e.isIntersecting) continue;
e.target.classList.add("is-in");
io.unobserve(e.target);
}
},
{ rootMargin: "0px 0px -8% 0px", threshold: 0.05 },
);
for (const el of document.querySelectorAll(".reveal")) io.observe(el);
</script>
</body>
</html>

View file

@ -0,0 +1,560 @@
@import url("./tokens.css");
/* ==========================================================================
무월 quiet-commerce. 종이 원장의 문법.
레이아웃 재질 모션 순서로 읽힌다.
========================================================================== */
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html {
-webkit-text-size-adjust: 100%;
scroll-behavior: smooth;
background: var(--surface);
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
body {
background: var(--surface);
color: var(--ink);
font-family: var(--font-body);
font-size: var(--step-0);
line-height: var(--leading-normal);
-webkit-font-smoothing: antialiased;
}
:where(h1, h2) {
font-family: var(--font-display);
font-weight: 500;
line-height: var(--leading-tight);
letter-spacing: -0.01em;
word-break: keep-all;
overflow-wrap: break-word;
text-wrap: balance;
}
:where(p, li, dd, dt, th, td, figcaption) {
word-break: keep-all;
overflow-wrap: break-word;
line-height: 1.75;
text-wrap: pretty;
}
h1 {
font-size: var(--step-3);
}
h2 {
font-size: var(--step-2);
}
.muted {
color: var(--ink-muted);
}
a {
color: inherit;
}
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
}
.skip {
position: absolute;
left: -9999px;
top: 0;
background: var(--ink);
color: var(--surface);
padding: var(--space-3) var(--space-4);
z-index: 100;
}
.skip:focus {
left: 0;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
.wrap {
width: min(100% - 2 * var(--pad-inline), var(--container));
margin-inline: auto;
}
/* ── 헤더 ─────────────────────────────────────────────────────────────── */
.site-head {
position: sticky;
top: 0;
z-index: 50;
background: color-mix(in srgb, var(--surface) 88%, transparent);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--line);
}
.head-inner {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-4);
padding-block: var(--space-3);
}
.wordmark {
font-family: var(--font-display);
font-size: var(--step-1);
font-weight: 600;
letter-spacing: 0.02em;
text-decoration: none;
}
.wordmark-han {
color: var(--ink-muted);
font-size: var(--step-0);
margin-left: var(--space-1);
font-weight: 400;
}
.nav {
display: flex;
gap: clamp(var(--space-3), 2vw, var(--space-5));
font-size: var(--step--1);
}
.nav a {
text-decoration: none;
color: var(--ink-muted);
padding-block: 2px;
border-bottom: 1px solid transparent;
transition: color var(--dur-quick) var(--ease-soft),
border-color var(--dur-quick) var(--ease-soft);
}
.nav a:hover {
color: var(--ink);
border-color: var(--accent);
}
/* ── 히어로 ───────────────────────────────────────────────────────────── */
.hero {
padding-block: var(--space-7);
}
.hero-grid {
display: grid;
grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
gap: var(--space-7) var(--space-6);
align-items: start;
}
.hero-label {
font-size: var(--step--1);
color: var(--ink-muted);
letter-spacing: 0.04em;
margin-bottom: var(--space-4);
}
.lead {
max-width: 34em;
}
.hero-text .lead {
margin-top: var(--space-4);
}
.hero-actions {
margin-top: var(--space-5);
display: flex;
align-items: center;
gap: var(--space-4);
flex-wrap: wrap;
}
.hero-note {
font-size: var(--step--1);
color: var(--ink-faint);
}
.btn {
display: inline-block;
background: var(--accent);
color: var(--accent-ink);
font-weight: 500;
text-decoration: none;
padding: 0.75rem 1.5rem;
border-radius: var(--radius);
transition: background var(--dur-quick) var(--ease-soft);
}
.btn:hover {
background: var(--accent-hover);
}
.btn-inverse {
background: var(--surface);
color: var(--ink);
}
.btn-inverse:hover {
background: var(--surface-bright);
}
.hero-photo img {
display: block;
width: 100%;
height: auto;
border-radius: var(--radius);
}
.hero-photo figcaption,
.recipe-photo figcaption,
.scale-plot figcaption {
font-size: var(--step--1);
color: var(--ink-faint);
margin-top: var(--space-2);
}
/* 이번 주 원두 — 원장의 첫 두 줄 (카드 아님) */
.hero-beans {
margin-top: var(--space-6);
border-top: 1px solid var(--line);
}
.bean-row {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: var(--space-4);
padding-block: var(--space-3);
border-bottom: 1px solid var(--line);
}
.bean-row dt {
font-family: var(--font-display);
font-weight: 500;
font-size: var(--step-1);
}
.bean-origin {
font-size: var(--step--1);
color: var(--ink-muted);
margin-top: 2px;
}
.bean-price {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.bean-price span {
color: var(--ink-faint);
margin-right: var(--space-2);
font-size: var(--step--1);
}
/* ── 섹션 공통 ────────────────────────────────────────────────────────── */
.section {
padding-block: var(--space-8);
}
.band {
background: var(--surface-raised);
border-block: 1px solid var(--line);
}
/* ── 척도(좌표 플롯) ──────────────────────────────────────────────────── */
.scale-grid {
display: grid;
grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
gap: var(--space-6) var(--space-7);
align-items: center;
}
.scale-text p + p {
margin-top: var(--space-3);
}
.scale-legend {
list-style: none;
padding: 0;
margin-top: var(--space-4);
display: flex;
flex-wrap: wrap;
gap: var(--space-2) var(--space-4);
font-size: var(--step--1);
color: var(--ink-muted);
}
.key {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--ink);
margin-right: var(--space-1);
vertical-align: baseline;
}
.key-b {
background: var(--accent);
}
.key-c {
background: var(--ink-faint);
}
.scale-plot svg {
display: block;
width: 100%;
height: auto;
}
.scale-plot .grid line {
stroke: var(--line);
stroke-width: 1;
}
.scale-plot .grid line:first-child,
.scale-plot .grid line:nth-child(2) {
stroke: var(--ink-faint);
stroke-width: 1.25;
}
.axis-labels text {
font-family: var(--font-body);
font-size: 13px;
fill: var(--ink-faint);
}
.points circle {
fill: var(--ink);
}
.points .pt-b {
fill: var(--accent);
}
.points .pt-c {
fill: var(--ink-faint);
}
.points text {
font-family: var(--font-body);
font-size: 13px;
fill: var(--ink-muted);
}
/* ── 원장(밴드) ───────────────────────────────────────────────────────── */
.ledger-head {
max-width: 38em;
margin-bottom: var(--space-5);
}
.ledger-head p {
margin-top: var(--space-3);
}
.table-wrap {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: var(--step-0);
}
thead th {
text-align: left;
font-weight: 500;
font-size: var(--step--1);
color: var(--ink-muted);
padding: var(--space-2) var(--space-3);
border-bottom: 1px solid var(--ink-faint);
}
tbody th {
text-align: left;
font-weight: 500;
}
tbody td,
tbody th {
padding: var(--space-3);
border-bottom: 1px solid var(--line);
vertical-align: baseline;
}
.num {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
text-align: right;
white-space: nowrap;
}
.ledger-note {
margin-top: var(--space-3);
font-size: var(--step--1);
}
/* ── 레시피 ───────────────────────────────────────────────────────────── */
.recipe-grid {
display: grid;
grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
gap: var(--space-6) var(--space-7);
align-items: center;
}
.recipe-photo img {
display: block;
width: 100%;
height: auto;
border-radius: var(--radius);
}
.recipe-text p + p {
margin-top: var(--space-3);
}
.spec {
margin-top: var(--space-4);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
gap: var(--space-3) var(--space-5);
}
.spec dt {
font-size: var(--step--1);
color: var(--ink-faint);
}
.spec dd {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
font-size: var(--step-1);
margin-top: var(--space-1);
}
/* ── 구독(반전 블록) ──────────────────────────────────────────────────── */
.plan {
background: var(--surface-inverse);
color: var(--ink-inverse);
}
.plan-note {
color: var(--ink-inverse-muted);
}
.plan-grid {
display: grid;
grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
gap: var(--space-6) var(--space-7);
align-items: center;
}
.plan-text p {
margin-top: var(--space-4);
max-width: 34em;
}
.plan-list {
margin-top: var(--space-4);
padding: 0;
list-style: none;
}
.plan-list li {
padding-left: 1.25em;
position: relative;
color: var(--ink-inverse-muted);
}
.plan-list li::before {
content: "";
position: absolute;
left: 0;
top: 0.72em;
width: 6px;
height: 1.5px;
background: var(--ink-inverse-muted);
}
.plan-list li + li {
margin-top: var(--space-2);
}
.plan-box {
border: 1px solid var(--line-inverse);
border-radius: var(--radius);
padding: var(--space-5);
display: grid;
gap: var(--space-4);
justify-items: start;
}
.plan-price span:first-child {
font-family: var(--font-display);
font-size: var(--step-2);
font-weight: 600;
}
.plan-unit {
display: block;
font-size: var(--step--1);
margin-top: var(--space-1);
color: var(--ink-inverse-muted);
}
/* ── 푸터 ─────────────────────────────────────────────────────────────── */
.site-foot {
border-top: 1px solid var(--line);
padding-block: var(--space-6);
font-size: var(--step--1);
}
.foot-inner {
display: flex;
flex-wrap: wrap;
gap: var(--space-2) var(--space-5);
justify-content: space-between;
}
/* ── 등장 모션 — IO 1회, 유지 ─────────────────────────────────────────── */
.reveal {
opacity: 0;
translate: 0 12px;
transition: opacity var(--dur-normal) var(--ease-soft),
translate var(--dur-normal) var(--ease-soft);
transition-delay: var(--delay, 0ms);
}
.reveal.is-in {
opacity: 1;
translate: 0 0;
}
@media (prefers-reduced-motion: reduce) {
.reveal {
opacity: 1;
translate: 0 0;
transition: none;
}
}
/* ── 반응형 ───────────────────────────────────────────────────────────── */
@media (max-width: 900px) {
.hero-grid,
.scale-grid,
.recipe-grid,
.plan-grid {
grid-template-columns: 1fr;
gap: var(--space-5);
}
.hero-photo,
.recipe-photo {
max-width: 26rem;
}
.hero {
padding-block: var(--space-6);
}
}
@media (max-width: 560px) {
.head-inner {
flex-direction: column;
align-items: flex-start;
gap: var(--space-1);
}
.nav {
width: 100%;
justify-content: space-between;
}
.bean-row {
flex-direction: column;
align-items: flex-start;
gap: var(--space-1);
}
.plan-box {
padding: var(--space-4);
}
}

View file

@ -0,0 +1,67 @@
/* ==========================================================================
무월 (霧月 · MUWOL) 디자인 토큰
프리셋: quiet-commerce
컨셉 문장: 종이 원장처럼 정직한 구독 화면에는 사양과 실측만.
레퍼런스: R1 coffeecollective.dk(구조·가격 노출·거래 투명성)
R2 fitzcarraldoeditions.com(세리프 본문 정돈, 1강조색)
R3 scroll-driven view() 채택 (IO 등장만 . 정적 HTML + 폴백 우선)
하지 않은 : 세로 GNB 레일, 영어 대문자 eyebrow, 전면 그레인 오버레이,
FIG.00 캡션 문법, 카드 그림자. 이유: 계기판 흉내가 상품을 가림.
========================================================================== */
:root {
/* 타입 스케일 — 최대(64px) ÷ 본문(16px) = 4.0배 */
--step--1: 0.8125rem; /* 13px — 캡션·주석 */
--step-0: 1rem; /* 16px — 본문 */
--step-1: 1.25rem; /* 20px — 소제목 */
--step-2: clamp(1.375rem, 1.2rem + 0.9vw, 1.75rem); /* 28px — 섹션 표제 */
--step-3: clamp(2.25rem, 1.4rem + 3.6vw, 4rem); /* 64px — 디스플레이 */
--leading-tight: 1.28;
--leading-normal: 1.75;
--measure: 34ch;
--font-display: "MaruBuri", serif;
--font-serif: "MaruBuri", serif;
--font-body: "Pretendard Variable", Pretendard, -apple-system, sans-serif;
--font-mono: "JetBrains Mono", monospace;
/* 색 — hue 2개: 안개 종이 + 볶은 원두 앰버. 강조색은 앰버 하나. */
--surface: #f4f4f0;
--surface-raised: #eaeae4;
--surface-inverse: #181b18;
--ink: #181b18;
--ink-muted: #585c56;
--ink-faint: #8c908a;
--ink-inverse: #f4f4f0;
--ink-inverse-muted: #a8aca4;
--line: #d5d7d0;
--line-inverse: #3a3e39;
--accent: #7a3e1d;
--accent-hover: #5c2d13;
--surface-bright: #fffffa; /* 반전 블록 안 밝은 hover 단계 */
--accent-ink: #f4f4f0;
/* 대비(실측 예정 5단계에서 재검): ink/surface 15.6, muted/surface 6.2,
accent/surface 7.5, inverse 조합은 본문 AA 이상으로 설계 */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 1rem;
--space-4: 1.5rem;
--space-5: 2.5rem;
--space-6: 4rem;
--space-7: 6rem;
--space-8: clamp(4.5rem, 3rem + 6vw, 9rem);
--radius: 2px;
--container: 1120px;
--pad-inline: clamp(1.25rem, 1rem + 2.5vw, 4rem);
--dur-quick: 180ms;
--dur-normal: 420ms;
--ease-soft: cubic-bezier(0.25, 1, 0.35, 1);
}