@qualisoft/ai-skills 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/bin/cli.mjs +331 -0
- package/package.json +53 -0
- package/skills/erd-visual/SKILL.md +294 -0
- package/skills/erd-visual/build.mjs +404 -0
- package/skills/erd-visual/engine/dbml.mjs +133 -0
- package/skills/erd-visual/engine/ingest.mjs +273 -0
- package/skills/erd-visual/engine/layout.mjs +165 -0
- package/skills/erd-visual/engine/overview.mjs +314 -0
- package/skills/erd-visual/engine/render.mjs +144 -0
- package/skills/erd-visual/engine/router.mjs +401 -0
- package/skills/erd-visual/engine/verify.mjs +138 -0
- package/skills/erd-visual/engine/wire.mjs +115 -0
- package/skills/erd-visual/fixtures/crm-large.dbml +1337 -0
- package/skills/erd-visual/fixtures/edge-cases.dbml +43 -0
- package/skills/erd-visual/fixtures/map-dynamics.json +32 -0
- package/skills/erd-visual/fixtures/shop-basic.dbml +78 -0
- package/skills/erd-visual/fixtures/src-json/api.json +38 -0
- package/skills/erd-visual/fixtures/src-prisma/schema.prisma +44 -0
- package/skills/erd-visual/fixtures/src-sql/shop.sql +62 -0
- package/skills/erd-visual/readers/csv.mjs +81 -0
- package/skills/erd-visual/readers/index.mjs +55 -0
- package/skills/erd-visual/readers/jsonschema.mjs +86 -0
- package/skills/erd-visual/readers/prisma.mjs +102 -0
- package/skills/erd-visual/readers/sql.mjs +205 -0
- package/skills/erd-visual/readers/xlsx.mjs +220 -0
- package/skills/erd-visual/readers/xml.mjs +130 -0
- package/skills/erd-visual/readers/zip.mjs +74 -0
- package/skills/erd-visual/viewer/index.html +539 -0
- package/skills/project-build/SKILL.md +171 -0
- package/skills/project-build/templates/build-rules.md +88 -0
- package/skills/project-build/templates/change-log.md +29 -0
- package/skills/project-build/templates/coverage.md +49 -0
- package/skills/project-build/templates/parity.html +375 -0
- package/skills/project-build/templates/theme.css +45 -0
- package/skills/project-design/MEDIUMS.md +104 -0
- package/skills/project-design/QUESTIONS.md +126 -0
- package/skills/project-design/SKILL.md +365 -0
- package/skills/project-design/templates/audit.html +689 -0
- package/skills/project-design/templates/change-log.md +53 -0
- package/skills/project-design/templates/design-rules.md +118 -0
- package/skills/project-design/templates/mockup-app.html +96 -0
- package/skills/project-design/templates/mockup-slides.html +82 -0
- package/skills/project-design/templates/mockup-web.html +45 -0
- package/skills/project-design/templates/styleguide.html +436 -0
- package/skills/project-design/templates/tokens.css +69 -0
- package/skills/project-design/templates/tone-options.html +138 -0
- package/skills/project-init/GUIDE.md +370 -0
- package/skills/project-init/RUNBOOK.md +192 -0
- package/skills/project-init/SKILL.md +382 -0
- package/skills/project-init/build.mjs +2898 -0
- package/skills/project-init/evals/RUBRIC.md +81 -0
- package/skills/project-init/evals/cases/conflicting.expect.json +17 -0
- package/skills/project-init/evals/cases/conflicting.md +9 -0
- package/skills/project-init/evals/cases/vague-idea.expect.json +11 -0
- package/skills/project-init/evals/cases/vague-idea.md +7 -0
- package/skills/project-init/evals/cases/well-formed.expect.json +18 -0
- package/skills/project-init/evals/cases/well-formed.md +28 -0
- package/skills/project-init/markdown.mjs +0 -0
- package/skills/project-init/modules/a11y.json +46 -0
- package/skills/project-init/modules/ai.json +77 -0
- package/skills/project-init/modules/audience.json +48 -0
- package/skills/project-init/modules/backend.json +129 -0
- package/skills/project-init/modules/brand.json +78 -0
- package/skills/project-init/modules/core.json +132 -0
- package/skills/project-init/modules/design.json +72 -0
- package/skills/project-init/modules/engineering.json +86 -0
- package/skills/project-init/modules/mobile.json +22 -0
- package/skills/project-init/modules/ops.json +122 -0
- package/skills/project-init/modules/process.json +104 -0
- package/skills/project-init/modules/product.json +37 -0
- package/skills/project-init/modules/ux.json +52 -0
- package/skills/project-init/modules/web.json +129 -0
- package/skills/project-init/presets/ai-product.json +12 -0
- package/skills/project-init/presets/internal-system.json +15 -0
- package/skills/project-init/presets/mobile-app.json +20 -0
- package/skills/project-init/presets/web-corporate.json +116 -0
- package/skills/project-init/schema.json +80 -0
- package/skills/project-init/templates/log.md +54 -0
- package/skills/project-init/templates/readme.md +62 -0
- package/skills/project-init/templates/reference.md +34 -0
- package/skills/project-init/templates/register.md +63 -0
- package/skills/project-init/templates/spec.md +46 -0
- package/skills/project-interview/INTERVIEW.md +221 -0
- package/skills/project-interview/SKILL.md +156 -0
- package/skills/project-interview/fixtures/brief.md +49 -0
- package/skills/project-interview/fixtures/decisions.md +13 -0
- package/skills/project-interview/fixtures/open-questions.md +9 -0
- package/skills/project-interview/templates/brief.md +179 -0
- package/skills/project-interview/templates/decisions.md +55 -0
- package/skills/project-interview/templates/open-questions.md +40 -0
- package/skills/project-interview/validate.mjs +49 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="ko">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>시안 ↔ 구현 대조 — parity</title>
|
|
7
|
+
<!--
|
|
8
|
+
시안 대조기. Docs/_build/parity.html
|
|
9
|
+
|
|
10
|
+
── 왜 필요한가 ──
|
|
11
|
+
시안을 Tailwind 로 재작성하면 클래스 이름이 전부 달라진다.
|
|
12
|
+
그러면 "구현이 시안과 같은가"를 볼 방법이 사라진다.
|
|
13
|
+
시안은 여러 회차를 다듬은 결과물이라, 조용히 흐트러지면 그 작업이 통째로 날아간다.
|
|
14
|
+
|
|
15
|
+
── 어떻게 짝을 맞추나 ──
|
|
16
|
+
선택자로는 못 맞춘다(클래스가 다르니까). 그래서 **표식**으로 맞춘다.
|
|
17
|
+
시안과 구현 양쪽에 같은 data-parity 이름을 달면 그 둘을 비교한다.
|
|
18
|
+
|
|
19
|
+
<header class="site-header" data-parity="header"> ← 시안
|
|
20
|
+
<header data-parity="header" className="fixed z-50 …"> ← 구현
|
|
21
|
+
|
|
22
|
+
클래스가 달라도 된다. 그게 재작성을 허용하면서 값을 지키는 방법이다.
|
|
23
|
+
|
|
24
|
+
── 무엇을 비교하나 ──
|
|
25
|
+
색(합성) · 타이포 · 모서리 · 여백 · 너비. 높이는 내용이 만드는 값이라 참고로만 낸다.
|
|
26
|
+
**위치는 비교하지 않는다** — 레이아웃이 조금 다른 건 정상일 수 있고,
|
|
27
|
+
전부 틀렸다고 하면 사람이 이 도구를 무시하게 된다.
|
|
28
|
+
-->
|
|
29
|
+
<style>
|
|
30
|
+
:root{
|
|
31
|
+
--bg:#ffffff;--panel:#f7f7f5;--ink:#111111;--muted:#6b6b68;--line:#dededb;
|
|
32
|
+
--ok:#0f7b6c;--okbg:#e6f4ea;--bad:#b02f1c;--badbg:#fdecea;--warn:#8a6100;--warnbg:#fdf4e3;
|
|
33
|
+
}
|
|
34
|
+
*,*::before,*::after{box-sizing:border-box}
|
|
35
|
+
body{margin:0;background:var(--panel);color:var(--ink);
|
|
36
|
+
font:15px/1.6 -apple-system,BlinkMacSystemFont,"Pretendard Variable",Pretendard,"Segoe UI","Apple SD Gothic Neo",sans-serif;
|
|
37
|
+
word-break:keep-all}
|
|
38
|
+
.wrap{max-width:1180px;margin:0 auto;padding:36px 24px 120px}
|
|
39
|
+
h1{margin:0 0 6px;font-size:26px;letter-spacing:-.02em}
|
|
40
|
+
.sub{margin:0 0 26px;color:var(--muted);font-size:14px}
|
|
41
|
+
fieldset{margin:0 0 22px;padding:18px 20px;border:1px solid var(--line);border-radius:12px;background:var(--bg)}
|
|
42
|
+
legend{padding:0 8px;font-size:12.5px;font-weight:600;color:var(--muted);letter-spacing:.04em;text-transform:uppercase}
|
|
43
|
+
label{display:block;margin:0 0 4px;font-size:12.5px;color:var(--muted)}
|
|
44
|
+
input[type=text]{width:100%;margin-bottom:14px;padding:9px 11px;border:1px solid var(--line);border-radius:8px;
|
|
45
|
+
font:13px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;background:var(--bg);color:var(--ink)}
|
|
46
|
+
input[type=text]:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
|
|
47
|
+
button{min-height:44px;padding:0 22px;border:0;border-radius:999px;background:var(--ink);color:#fff;
|
|
48
|
+
font-size:14px;font-weight:600;cursor:pointer}
|
|
49
|
+
button:disabled{opacity:.5;cursor:default}
|
|
50
|
+
button:focus-visible{outline:2px solid var(--ink);outline-offset:3px}
|
|
51
|
+
|
|
52
|
+
.score{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 22px}
|
|
53
|
+
.tile{flex:1 1 160px;padding:14px 16px;border:1px solid var(--line);border-radius:10px;background:var(--bg)}
|
|
54
|
+
.tile b{display:block;font-size:23px;letter-spacing:-.02em}
|
|
55
|
+
.tile span{font-size:12px;color:var(--muted)}
|
|
56
|
+
.tile.pass b{color:var(--ok)} .tile.fail b{color:var(--bad)} .tile.warn b{color:var(--warn)}
|
|
57
|
+
|
|
58
|
+
.grp{margin:0 0 14px;border:1px solid var(--line);border-radius:12px;background:var(--bg);overflow:hidden}
|
|
59
|
+
.grp > summary{display:flex;align-items:center;gap:10px;padding:14px 18px;cursor:pointer;
|
|
60
|
+
font-size:14.5px;font-weight:600;letter-spacing:-.01em;list-style:none}
|
|
61
|
+
.grp > summary::-webkit-details-marker{display:none}
|
|
62
|
+
.grp > summary::before{content:"▸";color:var(--muted);font-size:11px;transition:transform .15s}
|
|
63
|
+
.grp[open] > summary::before{transform:rotate(90deg)}
|
|
64
|
+
.badge{margin-left:auto;padding:3px 10px;border-radius:999px;font-size:11.5px;font-weight:600}
|
|
65
|
+
.badge.ok{background:var(--okbg);color:var(--ok)}
|
|
66
|
+
.badge.bad{background:var(--badbg);color:var(--bad)}
|
|
67
|
+
.badge.warn{background:var(--warnbg);color:var(--warn)}
|
|
68
|
+
.hint{margin:0;padding:0 18px 12px;font-size:12.5px;color:var(--muted);line-height:1.55}
|
|
69
|
+
table{width:100%;border-collapse:collapse;font-size:12.5px}
|
|
70
|
+
th,td{padding:8px 10px;text-align:left;border-top:1px solid var(--line);vertical-align:top}
|
|
71
|
+
th{background:var(--panel);font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);font-weight:600}
|
|
72
|
+
td.mono,th.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
73
|
+
tr.bad td{background:var(--badbg)}
|
|
74
|
+
.swatch{display:inline-block;width:11px;height:11px;border-radius:3px;border:1px solid rgba(0,0,0,.15);
|
|
75
|
+
vertical-align:-1px;margin-right:5px}
|
|
76
|
+
.empty{padding:0 18px 16px;color:var(--ok);font-size:13px}
|
|
77
|
+
.frame{position:fixed;left:-10000px;top:0;border:0}
|
|
78
|
+
.note{margin:26px 0 0;padding:16px 18px;border-left:3px solid var(--line);background:var(--bg);
|
|
79
|
+
border-radius:0 10px 10px 0;font-size:13px;color:var(--muted);line-height:1.7}
|
|
80
|
+
.note b{color:var(--ink)}
|
|
81
|
+
code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}
|
|
82
|
+
</style>
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
<div class="wrap">
|
|
86
|
+
<h1>시안 ↔ 구현 대조</h1>
|
|
87
|
+
<p class="sub">같은 <code>data-parity</code> 표식끼리 색·타이포·형태·너비를 비교합니다. 클래스 이름이 달라도 됩니다.</p>
|
|
88
|
+
|
|
89
|
+
<fieldset>
|
|
90
|
+
<legend>대상</legend>
|
|
91
|
+
<label for="a">시안 (기준)</label>
|
|
92
|
+
<input type="text" id="a" value="../_design/mockups/home.html">
|
|
93
|
+
<label for="b">구현 (개발 서버)</label>
|
|
94
|
+
<input type="text" id="b" value="http://localhost:5173/">
|
|
95
|
+
<label for="widths">확인 폭 (px)</label>
|
|
96
|
+
<input type="text" id="widths" value="390, 768, 1280, 1536">
|
|
97
|
+
<label for="tol">허용 오차 — <code>크기px, 글자px</code>. 색은 항상 정확히 비교합니다</label>
|
|
98
|
+
<input type="text" id="tol" value="2, 0.5">
|
|
99
|
+
<button id="run">대조 실행</button>
|
|
100
|
+
</fieldset>
|
|
101
|
+
|
|
102
|
+
<div id="out"></div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<script>
|
|
106
|
+
(function(){
|
|
107
|
+
"use strict";
|
|
108
|
+
|
|
109
|
+
/* ── 비교 대상 속성 ─────────────────────────────────────────────
|
|
110
|
+
위치(top/left)는 넣지 않는다. 레이아웃이 조금 다른 것은 정상일 수 있고,
|
|
111
|
+
그것까지 실패로 만들면 진짜 어긋남이 묻힌다. */
|
|
112
|
+
/* 너비는 레이아웃이 만든다 — 어긋나면 문제다.
|
|
113
|
+
높이는 대부분 **내용이** 만든다. 카피 한 줄만 달라도 수백 px 차이가 나고,
|
|
114
|
+
그게 색·타이포 같은 진짜 어긋남을 묻어버린다. 그래서 따로 뺀다. */
|
|
115
|
+
var BOX = ["width"];
|
|
116
|
+
var SIZE_SOFT = ["height"];
|
|
117
|
+
var TYPE = ["fontSize", "fontWeight", "lineHeight", "letterSpacing", "textAlign", "textTransform"];
|
|
118
|
+
var COLOR = ["color", "backgroundColor", "borderTopColor"];
|
|
119
|
+
var SHAPE = ["borderTopLeftRadius", "borderBottomLeftRadius", "borderTopWidth",
|
|
120
|
+
"paddingTop", "paddingLeft", "marginTop", "opacity", "display", "flexDirection"];
|
|
121
|
+
|
|
122
|
+
var LABEL = {
|
|
123
|
+
width:"너비", height:"높이",
|
|
124
|
+
fontSize:"글자 크기", fontWeight:"굵기", lineHeight:"행간", letterSpacing:"자간",
|
|
125
|
+
textAlign:"정렬", textTransform:"대소문자",
|
|
126
|
+
color:"글자색", backgroundColor:"배경색", borderTopColor:"테두리색",
|
|
127
|
+
borderTopLeftRadius:"모서리(상좌)", borderBottomLeftRadius:"모서리(하좌)", borderTopWidth:"테두리 두께",
|
|
128
|
+
paddingTop:"안쪽 여백(위)", paddingLeft:"안쪽 여백(좌)", marginTop:"바깥 여백(위)",
|
|
129
|
+
opacity:"불투명도", display:"display", flexDirection:"flex 방향"
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
function parseColor(s){
|
|
133
|
+
if(!s) return null;
|
|
134
|
+
var m = s.match(/rgba?\(([^)]+)\)/);
|
|
135
|
+
if(!m) return null;
|
|
136
|
+
var p = m[1].split(/[,\s\/]+/).filter(Boolean).map(Number);
|
|
137
|
+
if(p.length < 3 || p.some(isNaN)) return null;
|
|
138
|
+
return {r:p[0], g:p[1], b:p[2], a:p.length > 3 ? p[3] : 1};
|
|
139
|
+
}
|
|
140
|
+
function over(fg, bg){
|
|
141
|
+
var a = fg.a;
|
|
142
|
+
return {r:fg.r*a + bg.r*(1-a), g:fg.g*a + bg.g*(1-a), b:fg.b*a + bg.b*(1-a), a:1};
|
|
143
|
+
}
|
|
144
|
+
function hex(c){
|
|
145
|
+
return "#" + [c.r,c.g,c.b].map(function(v){
|
|
146
|
+
return Math.round(Math.max(0,Math.min(255,v))).toString(16).padStart(2,"0");
|
|
147
|
+
}).join("");
|
|
148
|
+
}
|
|
149
|
+
/* 색은 합성해서 비교한다. rgba(0,0,0,0) 끼리 같다고 넘기면
|
|
150
|
+
"배경이 없다"와 "배경이 흰색이다"를 구분하지 못한다. */
|
|
151
|
+
function flatColor(el, win, prop){
|
|
152
|
+
var raw = win.getComputedStyle(el)[prop];
|
|
153
|
+
var c = parseColor(raw);
|
|
154
|
+
if(!c) return raw;
|
|
155
|
+
if(c.a === 0) return "투명";
|
|
156
|
+
var node = el.parentElement, base = {r:255,g:255,b:255,a:1}, layers = [];
|
|
157
|
+
while(node && node.nodeType === 1){
|
|
158
|
+
var b = parseColor(win.getComputedStyle(node).backgroundColor);
|
|
159
|
+
if(b && b.a > 0){ layers.push(b); if(b.a >= 0.999) break; }
|
|
160
|
+
node = node.parentElement;
|
|
161
|
+
}
|
|
162
|
+
for(var i = layers.length - 1; i >= 0; i--) base = over(layers[i], base);
|
|
163
|
+
return hex(over(c, base));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function num(v){
|
|
167
|
+
var n = parseFloat(v);
|
|
168
|
+
return isNaN(n) ? null : n;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function readOne(el, win){
|
|
172
|
+
var cs = win.getComputedStyle(el), r = el.getBoundingClientRect(), out = {};
|
|
173
|
+
BOX.concat(SIZE_SOFT).forEach(function(k){ out[k] = k === "width" ? r.width : r.height; });
|
|
174
|
+
TYPE.concat(SHAPE).forEach(function(k){ out[k] = cs[k]; });
|
|
175
|
+
COLOR.forEach(function(k){ out[k] = flatColor(el, win, k); });
|
|
176
|
+
out.__text = (el.textContent || "").trim().slice(0, 40);
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function collect(D, win){
|
|
181
|
+
var map = {};
|
|
182
|
+
Array.prototype.forEach.call(D.querySelectorAll("[data-parity]"), function(el){
|
|
183
|
+
var name = el.getAttribute("data-parity");
|
|
184
|
+
var cs = win.getComputedStyle(el);
|
|
185
|
+
if(cs.display === "none" || cs.visibility === "hidden") return;
|
|
186
|
+
if(!el.getClientRects().length) return;
|
|
187
|
+
if(map[name]) { map[name].__dup = (map[name].__dup || 1) + 1; return; }
|
|
188
|
+
map[name] = readOne(el, win);
|
|
189
|
+
});
|
|
190
|
+
return map;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function loadAt(src, w){
|
|
194
|
+
return new Promise(function(res, rej){
|
|
195
|
+
var f = document.createElement("iframe");
|
|
196
|
+
f.className = "frame"; f.width = w; f.height = 900;
|
|
197
|
+
var sep = src.indexOf("?") < 0 ? "?" : "&";
|
|
198
|
+
f.src = src + sep + "parity=" + Date.now() + Math.random();
|
|
199
|
+
document.body.appendChild(f);
|
|
200
|
+
var done = false;
|
|
201
|
+
f.onload = function(){
|
|
202
|
+
setTimeout(function(){
|
|
203
|
+
// 전환이 진행되지 않는 환경에서도 목표 값을 읽게 전환을 끈다
|
|
204
|
+
try{
|
|
205
|
+
var D = f.contentDocument;
|
|
206
|
+
var kill = D.createElement("style");
|
|
207
|
+
kill.textContent = "*,*::before,*::after{transition:none!important;animation:none!important}";
|
|
208
|
+
D.head.appendChild(kill);
|
|
209
|
+
// 진입 모션의 시작 상태를 걷어낸다. kill 스타일을 붙인 **뒤에** 지워야 한다 —
|
|
210
|
+
// 먼저 지우면 전환이 시작되고, 전환이 진행되지 않는 환경에서
|
|
211
|
+
// 계산값이 0 에 멈춘 채로 읽혀 검사를 통째로 건너뛴다.
|
|
212
|
+
D.documentElement.classList.remove("js-reveal");
|
|
213
|
+
}catch(e){}
|
|
214
|
+
setTimeout(function(){ done = true; res(f); }, 150);
|
|
215
|
+
}, 550);
|
|
216
|
+
};
|
|
217
|
+
setTimeout(function(){
|
|
218
|
+
if(!done){ try{ f.remove(); }catch(e){} rej(new Error("불러오지 못했습니다: " + src)); }
|
|
219
|
+
}, 12000);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
document.getElementById("run").addEventListener("click", async function(){
|
|
224
|
+
var btn = this;
|
|
225
|
+
btn.disabled = true; btn.textContent = "대조 중…";
|
|
226
|
+
var A = document.getElementById("a").value.trim();
|
|
227
|
+
var B = document.getElementById("b").value.trim();
|
|
228
|
+
var widths = document.getElementById("widths").value.split(",")
|
|
229
|
+
.map(function(s){ return parseInt(s.trim(),10); }).filter(function(n){ return n > 0; });
|
|
230
|
+
var tolRaw = document.getElementById("tol").value.split(",").map(function(s){ return parseFloat(s.trim()); });
|
|
231
|
+
var tol = { size: isNaN(tolRaw[0]) ? 2 : tolRaw[0], text: isNaN(tolRaw[1]) ? 0.5 : tolRaw[1] };
|
|
232
|
+
|
|
233
|
+
var result = {}, fatal = null;
|
|
234
|
+
for(var i = 0; i < widths.length; i++){
|
|
235
|
+
var w = widths[i];
|
|
236
|
+
try{
|
|
237
|
+
var fa = await loadAt(A, w);
|
|
238
|
+
var fb = await loadAt(B, w);
|
|
239
|
+
result[w] = {
|
|
240
|
+
a: collect(fa.contentDocument, fa.contentWindow),
|
|
241
|
+
b: collect(fb.contentDocument, fb.contentWindow)
|
|
242
|
+
};
|
|
243
|
+
fa.remove(); fb.remove();
|
|
244
|
+
}catch(e){
|
|
245
|
+
fatal = e.message;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
render(result, widths, tol, fatal);
|
|
250
|
+
btn.disabled = false; btn.textContent = "대조 실행";
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
function diffValue(key, va, vb, tol){
|
|
254
|
+
if(va === undefined || vb === undefined) return null;
|
|
255
|
+
var na = num(va), nb = num(vb);
|
|
256
|
+
if(na !== null && nb !== null && /px|^\d/.test(String(va))){
|
|
257
|
+
var limit = (key === "fontSize" || key === "letterSpacing" || key === "lineHeight") ? tol.text : tol.size;
|
|
258
|
+
if(key === "opacity") limit = 0.02;
|
|
259
|
+
if(Math.abs(na - nb) <= limit) return null;
|
|
260
|
+
return { a: (Math.round(na*100)/100) + "", b: (Math.round(nb*100)/100) + "", delta: (nb - na > 0 ? "+" : "") + (Math.round((nb-na)*100)/100) };
|
|
261
|
+
}
|
|
262
|
+
if(String(va) === String(vb)) return null;
|
|
263
|
+
return { a: String(va), b: String(vb), delta: "" };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function render(result, widths, tol, fatal){
|
|
267
|
+
var out = document.getElementById("out");
|
|
268
|
+
if(fatal){
|
|
269
|
+
out.innerHTML = '<div class="grp" open><summary>불러오기 실패<span class="badge bad">중단</span></summary>' +
|
|
270
|
+
'<p class="hint">' + esc(fatal) + '<br>구현 쪽은 개발 서버가 떠 있어야 하고, 두 주소가 <b>같은 출처</b>여야 계산 스타일을 읽을 수 있습니다. ' +
|
|
271
|
+
'dev 서버를 이 파일과 같은 호스트로 프록시하거나, 빌드 결과를 같은 서버에서 서빙하세요.</p></div>';
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
var names = {}, missA = {}, missB = {};
|
|
276
|
+
widths.forEach(function(w){
|
|
277
|
+
var r = result[w]; if(!r) return;
|
|
278
|
+
Object.keys(r.a).forEach(function(n){ names[n] = 1; if(!r.b[n]) missB[n] = 1; });
|
|
279
|
+
Object.keys(r.b).forEach(function(n){ names[n] = 1; if(!r.a[n]) missA[n] = 1; });
|
|
280
|
+
});
|
|
281
|
+
var all = Object.keys(names).sort();
|
|
282
|
+
|
|
283
|
+
var groups = [
|
|
284
|
+
{ key:"color", title:"색", keys:COLOR },
|
|
285
|
+
{ key:"type", title:"타이포", keys:TYPE },
|
|
286
|
+
{ key:"shape", title:"형태·여백", keys:SHAPE },
|
|
287
|
+
{ key:"box", title:"너비", keys:BOX },
|
|
288
|
+
{ key:"soft", title:"높이 (참고)", keys:SIZE_SOFT, soft:true,
|
|
289
|
+
hint:"높이는 대부분 내용이 만듭니다. 카피 한 줄이 달라도 수백 px 차이가 나므로 <b>어긋남으로 세지 않습니다.</b> 고정 높이를 쓴 블록(헤더 등)에서 차이가 나면 그건 봐야 합니다." }
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
var rows = {}, total = 0, soft = 0;
|
|
293
|
+
groups.forEach(function(g){ rows[g.key] = ""; });
|
|
294
|
+
|
|
295
|
+
all.forEach(function(name){
|
|
296
|
+
widths.forEach(function(w){
|
|
297
|
+
var r = result[w]; if(!r) return;
|
|
298
|
+
var va = r.a[name], vb = r.b[name];
|
|
299
|
+
if(!va || !vb) return;
|
|
300
|
+
groups.forEach(function(g){
|
|
301
|
+
g.keys.forEach(function(k){
|
|
302
|
+
var d = diffValue(k, va[k], vb[k], tol);
|
|
303
|
+
if(!d) return;
|
|
304
|
+
if(!g.soft) total++;
|
|
305
|
+
else soft++;
|
|
306
|
+
var sw = /color|Color/.test(k) && /^#/.test(d.a) && /^#/.test(d.b)
|
|
307
|
+
? '<span class="swatch" style="background:' + d.a + '"></span>' + d.a +
|
|
308
|
+
' → <span class="swatch" style="background:' + d.b + '"></span>' + d.b
|
|
309
|
+
: esc(d.a) + " → " + esc(d.b);
|
|
310
|
+
rows[g.key] += '<tr class="bad"><td class="mono">' + w + "</td>" +
|
|
311
|
+
'<td class="mono">' + esc(name) + "</td>" +
|
|
312
|
+
"<td>" + (LABEL[k] || k) + "</td>" +
|
|
313
|
+
"<td>" + sw + "</td>" +
|
|
314
|
+
'<td class="mono">' + esc(d.delta) + "</td></tr>";
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
var missing = Object.keys(missB).length + Object.keys(missA).length;
|
|
321
|
+
// 짝이 하나도 맞지 않았는데 "일치" 라고 하면 안 된다.
|
|
322
|
+
// 비교를 못 한 것과 비교해서 같은 것은 전혀 다르다 — 오판이 가장 위험하다.
|
|
323
|
+
var matched = all.filter(function(n){ return !missA[n] && !missB[n]; }).length;
|
|
324
|
+
if (!matched) total = Math.max(total, 1);
|
|
325
|
+
|
|
326
|
+
var html = '<div class="score">' +
|
|
327
|
+
'<div class="tile ' + (total ? "fail" : "pass") + '"><b>' +
|
|
328
|
+
(!matched ? "비교 못 함" : total ? total + "건" : "일치") + "</b><span>어긋난 값</span></div>" +
|
|
329
|
+
'<div class="tile ' + (soft ? "warn" : "pass") + '"><b>' + (soft ? soft + "건" : "없음") + "</b><span>높이 차이 (참고)</span></div>" +
|
|
330
|
+
'<div class="tile ' + (missing ? "warn" : "pass") + '"><b>' + (missing ? missing + "개" : "없음") + "</b><span>짝 없는 표식</span></div>" +
|
|
331
|
+
'<div class="tile ' + (matched ? "" : "fail") + '"><b>' + matched + " / " + all.length + "</b><span>짝이 맞은 표식</span></div>" +
|
|
332
|
+
"</div>";
|
|
333
|
+
|
|
334
|
+
if(!all.length){
|
|
335
|
+
html += '<div class="grp" open><summary>표식이 없습니다<span class="badge warn">확인</span></summary>' +
|
|
336
|
+
'<p class="hint">시안과 구현 양쪽에 <code>data-parity="이름"</code> 을 달아야 비교합니다. ' +
|
|
337
|
+
'먼저 시안의 주요 블록(header · hero · 각 섹션 · footer)에 표식을 달고, 구현에도 같은 이름을 다세요.</p></div>';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
groups.forEach(function(g){
|
|
341
|
+
var n = (rows[g.key].match(/<tr/g) || []).length;
|
|
342
|
+
html += '<details class="grp"' + (n && !g.soft ? " open" : "") + "><summary>" + g.title +
|
|
343
|
+
(n ? '<span class="badge ' + (g.soft ? "warn" : "bad") + '">' + n + "건</span>" : '<span class="badge ok">일치</span>') + "</summary>" +
|
|
344
|
+
(g.hint ? '<p class="hint">' + g.hint + "</p>" : "") +
|
|
345
|
+
(n ? '<table><thead><tr><th class="mono">폭</th><th class="mono">표식</th><th>속성</th><th>시안 → 구현</th><th class="mono">차이</th></tr></thead><tbody>' +
|
|
346
|
+
rows[g.key] + "</tbody></table>"
|
|
347
|
+
: '<p class="empty">어긋남 없음</p>') + "</details>";
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
if(missing){
|
|
351
|
+
var ml = "";
|
|
352
|
+
Object.keys(missB).forEach(function(n){ ml += '<tr class="bad"><td class="mono">' + esc(n) + "</td><td>시안에만 있음 — 구현에 표식이 없거나 그 블록을 아직 안 만들었습니다</td></tr>"; });
|
|
353
|
+
Object.keys(missA).forEach(function(n){ ml += '<tr class="bad"><td class="mono">' + esc(n) + "</td><td>구현에만 있음 — 시안에 없는 블록입니다. 의도한 확장이면 build-rules 에 적으세요</td></tr>"; });
|
|
354
|
+
html += '<details class="grp" open><summary>짝 없는 표식<span class="badge warn">' + missing + "개</span></summary>" +
|
|
355
|
+
'<p class="hint">한쪽에만 있는 표식입니다. 시안에 없는 화면을 4단계가 채우는 것은 정상이지만, <b>어느 쪽인지 적어 두어야</b> 빠뜨린 것과 구분됩니다.</p>' +
|
|
356
|
+
'<table><thead><tr><th class="mono">표식</th><th>상태</th></tr></thead><tbody>' + ml + "</tbody></table></details>";
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
html += '<p class="note"><b>이 도구가 보지 않는 것.</b> 위치(top/left)는 비교하지 않습니다 — ' +
|
|
360
|
+
'레이아웃이 조금 다른 것은 정상일 수 있고, 그것까지 실패로 만들면 진짜 어긋남이 묻힙니다. ' +
|
|
361
|
+
'대신 색·타이포·형태·너비를 봅니다. 높이는 내용이 만드는 값이라 참고로만 냅니다. <b>표식을 달지 않은 블록은 비교되지 않습니다</b> — ' +
|
|
362
|
+
'"일치"는 <i>표식을 단 것 중에</i> 어긋남이 없다는 뜻입니다.</p>';
|
|
363
|
+
|
|
364
|
+
out.innerHTML = html;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function esc(s){
|
|
368
|
+
return String(s).replace(/[&<>"]/g, function(c){
|
|
369
|
+
return {"&":"&","<":"<",">":">",'"':"""}[c];
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
})();
|
|
373
|
+
</script>
|
|
374
|
+
</body>
|
|
375
|
+
</html>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* src/styles/theme.css
|
|
2
|
+
*
|
|
3
|
+
* 토큰 정본은 Docs/_design/tokens.css 하나다. **값을 여기에 적지 않는다.**
|
|
4
|
+
* 여기서는 그 이름을 Tailwind 이름에 잇기만 한다.
|
|
5
|
+
*
|
|
6
|
+
* 값을 옮겨 적는 순간 두 곳이 되고, 두 곳은 반드시 갈라진다 —
|
|
7
|
+
* 3단계에서 실제로 그렇게 갈라진 적이 있다(DS-007).
|
|
8
|
+
* build.mjs check 가 복사를 감지해 경고한다.
|
|
9
|
+
*/
|
|
10
|
+
@import "tailwindcss";
|
|
11
|
+
@import "../../Docs/_design/tokens.css";
|
|
12
|
+
|
|
13
|
+
@theme inline {
|
|
14
|
+
/* 색 */
|
|
15
|
+
--color-bg: var(--bg);
|
|
16
|
+
--color-bg-subtle: var(--bg-subtle);
|
|
17
|
+
--color-surface: var(--surface);
|
|
18
|
+
--color-ink: var(--ink);
|
|
19
|
+
--color-ink-secondary: var(--ink-secondary);
|
|
20
|
+
--color-ink-muted: var(--ink-muted);
|
|
21
|
+
--color-brand: var(--brand);
|
|
22
|
+
--color-line: var(--line);
|
|
23
|
+
--color-line-strong: var(--line-strong);
|
|
24
|
+
--color-line-soft: var(--line-soft);
|
|
25
|
+
|
|
26
|
+
/* 서체 */
|
|
27
|
+
--font-display: var(--font-display);
|
|
28
|
+
--font-body: var(--font-body);
|
|
29
|
+
|
|
30
|
+
/* 크기 — clamp() 를 그대로 넘긴다.
|
|
31
|
+
고정 px 로 바꾸면 반응형이 죽고, 그게 대조기가 가장 자주 잡는 실수다. */
|
|
32
|
+
--text-display: var(--text-display);
|
|
33
|
+
--text-h2: var(--text-h2);
|
|
34
|
+
--text-h3: var(--text-h3);
|
|
35
|
+
--text-lead: var(--text-lead);
|
|
36
|
+
|
|
37
|
+
/* 형태 */
|
|
38
|
+
--radius-panel: var(--radius-panel);
|
|
39
|
+
--radius-card: var(--radius-card);
|
|
40
|
+
--radius-sm: var(--radius-sm);
|
|
41
|
+
|
|
42
|
+
/* 간격 */
|
|
43
|
+
--spacing-section: var(--section-y);
|
|
44
|
+
--container-site: var(--container);
|
|
45
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# 산출 매체별 규칙
|
|
2
|
+
|
|
3
|
+
시안은 **무엇으로 나가느냐**에 따라 만드는 법이 다르다.
|
|
4
|
+
같은 디자인 규칙이라도 웹과 슬라이드는 글자 크기 기준부터 다르다.
|
|
5
|
+
|
|
6
|
+
기본값은 2단계 `Docs/docs.config.json` 의 `preset` 에서 추정한다.
|
|
7
|
+
`web-corporate` → 웹, `mobile-app` → 모바일 앱, `internal-system` → 어드민.
|
|
8
|
+
추정이 맞는지 **사용자에게 확인은 받는다.**
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 웹 (반응형)
|
|
13
|
+
|
|
14
|
+
| 항목 | 기준 |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| 시안 형태 | `mockup-web.html` — 브라우저에서 그대로 확인 |
|
|
17
|
+
| 확인 폭 | 390 / 768 / 1280 / 1536 |
|
|
18
|
+
| 본문 최소 | 16px. 캡션도 13px 아래로 내리지 않는다 |
|
|
19
|
+
| 터치 타깃 | 모바일 폭에서 44×44px 이상 |
|
|
20
|
+
| 검수 | **`audit.html` 로 자동 판정.** 손으로 감사 스크립트를 짜지 않는다 |
|
|
21
|
+
|
|
22
|
+
`audit.html` 이 판정하는 것 — 가로 스크롤 · 뷰포트 이탈 · **합성 명도비** ·
|
|
23
|
+
터치 타깃(24px 필수 / 44px 권고) · 글자 하한 · 포커스 링 · **스크롤 상태 전환** · 죽은 CSS.
|
|
24
|
+
|
|
25
|
+
**명도비는 토큰 쌍이 아니라 합성 결과로 잰다.** 실제로 터지는 결함은
|
|
26
|
+
`검정 배경 위 검정 글자`(CSS 우선순위 사고), `opacity 를 먹은 회색 글자`,
|
|
27
|
+
`고정 글라스가 어두운 섹션 위를 지날 때`처럼 전부 합성에서 나온다.
|
|
28
|
+
스타일 가이드의 토큰 표로는 원리적으로 안 잡힌다.
|
|
29
|
+
|
|
30
|
+
## 모바일 앱
|
|
31
|
+
|
|
32
|
+
| 항목 | 기준 |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| 시안 형태 | `mockup-app.html` — 디바이스 프레임 안에 렌더 |
|
|
35
|
+
| 기준 크기 | 390×844 (iOS 기준). Android는 폭 360 확인 |
|
|
36
|
+
| 세이프 에어리어 | 상단 노치·하단 홈 인디케이터 영역을 침범하지 않는다 |
|
|
37
|
+
| 터치 타깃 | **최소 44×44pt.** 웹보다 엄격하다 |
|
|
38
|
+
| 플랫폼 차이 | 상단 바·뒤로가기·기본 제스처가 iOS/Android에서 다르다. 어느 쪽 기준인지 규칙에 적는다 |
|
|
39
|
+
| 검수 | 한 손 조작 영역, 스크롤 시 고정 요소, 키보드가 올라왔을 때 |
|
|
40
|
+
|
|
41
|
+
> 앱은 **화면 전환**이 디자인의 절반이다. 정적 화면만 그리지 말고
|
|
42
|
+
> 어디서 어디로 가는지를 `design-rules.md` 에 적는다.
|
|
43
|
+
|
|
44
|
+
## 데스크톱 앱 · 어드민
|
|
45
|
+
|
|
46
|
+
| 항목 | 기준 |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| 시안 형태 | `mockup-web.html` (밀도만 높임) |
|
|
49
|
+
| 정보 밀도 | 여백을 줄이고 한 화면에 더 담는다. 마케팅 사이트 기준을 그대로 쓰지 않는다 |
|
|
50
|
+
| 핵심 패턴 | 표, 필터, 폼, 일괄 작업, 빈 상태, 오류 상태 |
|
|
51
|
+
| 검수 | 행이 수백 개일 때, 열이 넘칠 때, 값이 아주 길 때 |
|
|
52
|
+
|
|
53
|
+
## 발표자료 (PPT · 키노트)
|
|
54
|
+
|
|
55
|
+
| 항목 | 기준 |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| 시안 형태 | `mockup-slides.html` — 16:9 슬라이드 |
|
|
58
|
+
| 크기 | 1920×1080 기준으로 그리고 축소 표시 |
|
|
59
|
+
| 글자 최소 | **제목 40px, 본문 24px 이상.** 뒷자리에서 읽혀야 한다 |
|
|
60
|
+
| 한 장 = 한 메시지 | 슬라이드에 두 주장이 들어가면 나눈다 |
|
|
61
|
+
| 검수 | 축소했을 때 읽히는가, 흑백 인쇄에서 구분되는가 |
|
|
62
|
+
|
|
63
|
+
> 발표자료는 **말이 주고 슬라이드가 보조**다. 슬라이드에 문장을 다 넣지 않는다.
|
|
64
|
+
|
|
65
|
+
## 대시보드 · 데이터 리포트
|
|
66
|
+
|
|
67
|
+
차트가 들어가면 **`dataviz` 스킬을 먼저 로드한다.**
|
|
68
|
+
차트 코드를 한 줄이라도 쓰기 전에 로드하는 것이 그 스킬의 사용 조건이다.
|
|
69
|
+
|
|
70
|
+
| 항목 | 기준 |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| 시안 형태 | `mockup-web.html` + 차트 |
|
|
73
|
+
| 색 | 계열색·발산색 규칙은 `dataviz` 를 따른다. 임의로 정하지 않는다 |
|
|
74
|
+
| 검수 | 색각 이상에서 구분되는가, 축·범례가 잘리지 않는가 |
|
|
75
|
+
|
|
76
|
+
## 다이어그램 · 구조도
|
|
77
|
+
|
|
78
|
+
구조도·플로우가 주 산출물이면 **`artifact-diagramming` 스킬을 로드한다.**
|
|
79
|
+
인라인 SVG 작성법과 명암 테마 대응이 거기에 있다.
|
|
80
|
+
|
|
81
|
+
## 외부 공유용 문서 · 리포트
|
|
82
|
+
|
|
83
|
+
시안이 아니라 **읽히는 문서**가 목적이면 **`artifact-design` 스킬을 로드**하고
|
|
84
|
+
Artifact로 퍼블리시하는 편이 낫다. `_design/mockups/` 에 두는 것은
|
|
85
|
+
프로젝트 내부에서 확인할 시안일 때다.
|
|
86
|
+
|
|
87
|
+
## 인쇄물 (명함 · 브로슈어)
|
|
88
|
+
|
|
89
|
+
| 항목 | 기준 |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| 시안 형태 | `mockup-web.html` 을 mm 기준으로 (`@page` + `mm` 단위) |
|
|
92
|
+
| 재단 여백 | 사방 3mm 여유. 잘려도 되는 영역과 안 되는 영역을 표시 |
|
|
93
|
+
| 색 | 화면 RGB와 인쇄 CMYK는 다르게 나온다. **화면 색을 그대로 약속하지 않는다** |
|
|
94
|
+
| 글자 최소 | 6pt 아래로 내리지 않는다 |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 매체를 모를 때
|
|
99
|
+
|
|
100
|
+
물어본다. 추정하지 않는다. 다만 선택지는 좁혀서 제시한다 —
|
|
101
|
+
2단계 프리셋과 화면 정의서를 보면 대개 하나로 좁혀진다.
|
|
102
|
+
|
|
103
|
+
여러 매체가 동시에 필요하면 (예: 웹 + 발표자료) **매체마다 시안을 따로 만들고
|
|
104
|
+
규칙은 하나로 공유한다.** 토큰이 갈라지면 브랜드가 갈라진다.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# 디자인 질문 은행 (텍스트 설명 방식)
|
|
2
|
+
|
|
3
|
+
말로만 있는 상태에서 규칙을 뽑아내기 위한 질문. **전부 묻지 않는다.**
|
|
4
|
+
한 번에 최대 4문항, 선택형, 모든 문항에 "아직 모름".
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## A. 인상 — 무엇처럼 보여야 하는가
|
|
9
|
+
|
|
10
|
+
| 질문 | 선택지 |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| 첫인상을 한 단어로 | 신뢰감 / 세련됨 / 친근함 / 대담함 / 차분함 / 전문적 |
|
|
13
|
+
| 무게감 | 가볍고 경쾌 / 중간 / 무겁고 진중 |
|
|
14
|
+
| 밀도 | 여백이 넓고 성김 / 보통 / 정보가 빽빽 |
|
|
15
|
+
| **피하고 싶은 인상** | (자유 입력 — 반드시 받는다) |
|
|
16
|
+
|
|
17
|
+
> **형용사만 나온 답은 되묻는다.**
|
|
18
|
+
> "깔끔하게" → 여백을 넓힌다는 뜻인지, 색을 줄인다는 뜻인지, 요소를 줄인다는 뜻인지.
|
|
19
|
+
> 셋은 서로 다른 결과를 만든다.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## B. 색
|
|
24
|
+
|
|
25
|
+
| 질문 | 선택지 |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| 밝기 기조 (사이트 전체) | 밝은 배경 / 어두운 배경 / 둘 다 지원 |
|
|
28
|
+
| **지배적인 면의 톤** | 첫 화면(Hero)·주요 배너를 **전체와 다르게** 갈 수 있다. 같게 / 반대로 / 아직 모름 |
|
|
29
|
+
| 강조색 성격 | 차분한 / 선명한 / 강조색 거의 안 씀 |
|
|
30
|
+
| 이미 정해진 색이 있나 | 브랜드 색 있음 / 로고에서 따올 것 / 없음 |
|
|
31
|
+
| 색 사용량 | 한 가지 강조색만 / 두세 가지 / 다채롭게 |
|
|
32
|
+
|
|
33
|
+
> **말로 받지 말고 보여주고 고르게 한다.** `tone-options.html` 로 색만 다른 후보를
|
|
34
|
+
> 2~4개 만들어 띄운다. "밝게요"는 흰색인지 옅은 회색인지 알 수 없고,
|
|
35
|
+
> 그 차이 때문에 시안을 다시 만든 적이 있다.
|
|
36
|
+
> 화면마다 답이 다를 수 있으므로 **화면 단위로** 받는다.
|
|
37
|
+
|
|
38
|
+
> 2단계 디자인 시스템에 이미 토큰 값이 있으면 **그것을 먼저 보여주고** 유지할지 묻는다.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## C. 타이포그래피
|
|
43
|
+
|
|
44
|
+
| 질문 | 선택지 |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| 제목과 본문의 대비 | 아주 크게 / 보통 / 작게 |
|
|
47
|
+
| 글자 인상 | 각지고 또렷 / 부드럽고 둥금 / 중립적 |
|
|
48
|
+
| 국문 서체 선호 | 있음 (지정) / 없음 (추천 받음) |
|
|
49
|
+
| 본문 크기 | 작게 (14~15) / 보통 (16) / 크게 (17+) |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## D. 레이아웃
|
|
54
|
+
|
|
55
|
+
| 질문 | 선택지 |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| 폭 | 좁고 읽기 중심 / 넓게 꽉 차게 / 콘텐츠마다 다르게 |
|
|
58
|
+
| 정렬 | 좌측 정렬 위주 / 중앙 정렬 위주 / 섞어서 |
|
|
59
|
+
| 리듬 | 같은 형태 반복 / 섹션마다 변주 |
|
|
60
|
+
| 카드 사용 | 적극적으로 / 꼭 필요한 곳만 / 쓰지 않음 |
|
|
61
|
+
|
|
62
|
+
> 2단계 안티패턴에 "모든 섹션을 카드로" 같은 항목이 있으면 그대로 알린다.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## E. 형태와 질감
|
|
67
|
+
|
|
68
|
+
| 질문 | 선택지 |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| 모서리 | 각지게 / 살짝 둥글게 / 많이 둥글게 |
|
|
71
|
+
| 구분선·그림자 | 선으로 구분 / 그림자로 띄움 / 배경색 차이로 |
|
|
72
|
+
| 이미지·일러스트 | 사진 중심 / 일러스트 / 도형·패턴 / 최소한만 |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## F. 움직임
|
|
77
|
+
|
|
78
|
+
| 질문 | 선택지 |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| 모션 정도 | 거의 없음 / 은은하게 / 눈에 띄게 |
|
|
81
|
+
| 스크롤 등장 효과 | 쓴다 / 안 쓴다 |
|
|
82
|
+
|
|
83
|
+
> 2단계 인터랙션 문서에 금지 항목(스크롤 하이재킹 등)이 있으면 선택지에서 제외한다.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## G. 참고 대상
|
|
88
|
+
|
|
89
|
+
| 질문 | 선택지 |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| 좋게 본 서비스 | (자유 입력) |
|
|
92
|
+
| 그 서비스의 **어느 부분**이 | 색 / 타이포 / 레이아웃 / 여백 / 움직임 / 카피 |
|
|
93
|
+
| **싫은** 사례 | (자유 입력) |
|
|
94
|
+
|
|
95
|
+
> **싫은 사례가 정보량이 더 크다.** 반드시 묻는다.
|
|
96
|
+
> "이렇게는 하지 마세요"가 방향을 가장 빨리 좁힌다.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## H. 제약
|
|
101
|
+
|
|
102
|
+
| 질문 | 선택지 |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| 지켜야 할 브랜드 가이드 | 있음 / 없음 |
|
|
105
|
+
| 접근성 요구 | 일반 / 공공·금융 수준 / 아직 모름 |
|
|
106
|
+
| 다크 모드 | 필요 / 불필요 / 나중에 |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## I. 필수 표기 (웹 · 국내 사업자)
|
|
111
|
+
|
|
112
|
+
푸터를 그리기 **전에** 받는다. 그리면서 없다는 걸 알면 레이아웃을 다시 짠다.
|
|
113
|
+
|
|
114
|
+
| 질문 | 선택지 |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| 상호 · 대표자명 | (입력) |
|
|
117
|
+
| **사업자등록번호** | (입력) — 표기 의무. 가장 자주 빠진다 |
|
|
118
|
+
| 주소 | (입력) |
|
|
119
|
+
| 연락처 | 메일 / 전화 / 둘 다 |
|
|
120
|
+
| 통신판매업 신고 | 대상 / 비대상 / 아직 모름 |
|
|
121
|
+
| 개인정보처리방침 · 이용약관 | 있음 / 만들어야 함 / 아직 모름 |
|
|
122
|
+
| 채널 링크 | 카카오채널 / 인스타 / 링크드인 / 없음 |
|
|
123
|
+
|
|
124
|
+
> 없는 값을 지어내지 않는다. `TODO(주체)` 로 자리만 잡고 **리스크 대장에 올린다.**
|
|
125
|
+
|
|
126
|
+
> 2단계 범위 옵션(`a11y`, `darkmode`)에 이미 답이 있으면 다시 묻지 않는다.
|