kwon-ui 0.0.0-stage → 0.2.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/CATALOG.md +322 -0
- package/CHANGELOG.md +61 -0
- package/LICENSE +4 -0
- package/README.md +71 -2
- package/bin/kwon-ui.mjs +60 -0
- package/check/index.mjs +233 -0
- package/claude/ADOPTING.md +107 -0
- package/claude/APP-CLAUDE.md +15 -0
- package/claude/hook.mjs +46 -0
- package/dist/classes.json +76 -0
- package/dist/kwon-ui.css +523 -0
- package/dist/react/controls.d.ts +20 -0
- package/dist/react/controls.js +19 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +5 -0
- package/dist/react/layout.d.ts +68 -0
- package/dist/react/layout.js +59 -0
- package/dist/react/tone.d.ts +18 -0
- package/dist/react/tone.js +29 -0
- package/dist/react/type.d.ts +29 -0
- package/dist/react/type.js +28 -0
- package/dist/react/util.d.ts +10 -0
- package/dist/react/util.js +4 -0
- package/package.json +62 -4
package/dist/kwon-ui.css
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/* kwon-ui 0.2.0. 이 파일은 빌드 결과다. 앱에서 고치지 않는다. 원본: src/css/ */
|
|
2
|
+
|
|
3
|
+
/* ── tokens.css ── */
|
|
4
|
+
/*
|
|
5
|
+
* 토큰. 색, 글자 크기, 모서리, 폭 같은 값은 이 파일에만 있다.
|
|
6
|
+
* 앱 코드와 이 패키지의 다른 CSS 는 값을 직접 쓰지 않고 이 변수만 쓴다.
|
|
7
|
+
* 값은 kwonandcompany.com 의 밤 테마(운영 화면)에서 그대로 옮겼다.
|
|
8
|
+
*/
|
|
9
|
+
.night {
|
|
10
|
+
/* 바탕과 면 */
|
|
11
|
+
--n-bg: #000000;
|
|
12
|
+
--n-panel: #0d0d0d;
|
|
13
|
+
--n-card: #171717;
|
|
14
|
+
--n-card-2: #202020;
|
|
15
|
+
--n-line: rgba(255, 255, 255, 0.1);
|
|
16
|
+
|
|
17
|
+
/* 글자 네 톤: 진한 글자, 본문, 받쳐 주는 말, 가장 흐린 글 */
|
|
18
|
+
--n-hi: #ffffff;
|
|
19
|
+
--n-soft: rgba(255, 255, 255, 0.78);
|
|
20
|
+
--n-mid: rgba(255, 255, 255, 0.5);
|
|
21
|
+
--n-dim: rgba(255, 255, 255, 0.32);
|
|
22
|
+
|
|
23
|
+
/* 버튼 */
|
|
24
|
+
--n-btn: #ffffff;
|
|
25
|
+
--n-btn-ink: #000000;
|
|
26
|
+
--n-btn-hover: #e9e9e9;
|
|
27
|
+
--n-ghost: rgba(255, 255, 255, 0.08);
|
|
28
|
+
--n-ghost-hover: rgba(255, 255, 255, 0.16);
|
|
29
|
+
--n-focus: rgba(255, 255, 255, 0.55);
|
|
30
|
+
|
|
31
|
+
/* 사진 위: 글 뒤를 누르는 그늘 두 단계, 사진 위에서 받쳐 주는 말의 색 */
|
|
32
|
+
--n-shade: rgba(0, 0, 0, 0.82);
|
|
33
|
+
--n-shade-soft: rgba(0, 0, 0, 0.1);
|
|
34
|
+
--n-on-photo: rgba(255, 255, 255, 0.62);
|
|
35
|
+
|
|
36
|
+
/* 글자 크기. 읽는 글은 제목, 본문, 작은 글 셋이다. display 는 첫 화면 제목, control 은 버튼과 링크 */
|
|
37
|
+
--n-fs-display: clamp(34px, 3.8vw, 50px);
|
|
38
|
+
--n-fs-title: clamp(26px, 2.4vw, 32px);
|
|
39
|
+
--n-fs-body: 18px;
|
|
40
|
+
--n-fs-small: 14px;
|
|
41
|
+
--n-fs-control: 15px;
|
|
42
|
+
|
|
43
|
+
/* 모서리: 판, 그림, 알약 */
|
|
44
|
+
--n-radius: 32px;
|
|
45
|
+
--n-radius-md: 20px;
|
|
46
|
+
--n-radius-pill: 999px;
|
|
47
|
+
|
|
48
|
+
/* 폭과 바깥 여백 */
|
|
49
|
+
--n-wrap: 1064px;
|
|
50
|
+
--n-gutter: 24px;
|
|
51
|
+
--n-edge: clamp(20px, 3.2vw, 48px);
|
|
52
|
+
|
|
53
|
+
/* 글꼴 이름. 글꼴 파일은 앱이 불러온다(패키지는 글꼴을 싣지 않는다) */
|
|
54
|
+
--n-font: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, "Segoe UI", Roboto,
|
|
55
|
+
"Helvetica Neue", Arial, sans-serif;
|
|
56
|
+
}
|
|
57
|
+
@media (min-width: 768px) {
|
|
58
|
+
.night {
|
|
59
|
+
--n-gutter: 40px;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ── base.css ── */
|
|
64
|
+
/*
|
|
65
|
+
* 바탕. 페이지 맨 바깥 요소에 .night 를 붙이면 그 안이 검은 화면이 된다.
|
|
66
|
+
* 여백(margin)과 글꼴 지정은 넣지 않는다: 앱의 초기화 규칙과 Tailwind 유틸리티가 그대로 먹어야 한다.
|
|
67
|
+
*/
|
|
68
|
+
.night {
|
|
69
|
+
background: var(--n-bg);
|
|
70
|
+
color: var(--n-hi);
|
|
71
|
+
font-weight: 400;
|
|
72
|
+
overflow-x: clip;
|
|
73
|
+
/* 한글은 낱말 단위로만 줄을 바꾼다(글자 중간에서 끊지 않는다) */
|
|
74
|
+
word-break: keep-all;
|
|
75
|
+
}
|
|
76
|
+
/* 스크롤 끝에서 흰 바탕이 비치지 않게 문서 바탕도 검정으로 덮는다 */
|
|
77
|
+
html:has(.night),
|
|
78
|
+
html:has(.night) body {
|
|
79
|
+
background: #000000;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* 화면에는 안 보이고 화면 낭독기만 읽는 글(두 톤 제목 앞의 구간 이름) */
|
|
83
|
+
.night .n-sr {
|
|
84
|
+
position: absolute;
|
|
85
|
+
width: 1px;
|
|
86
|
+
height: 1px;
|
|
87
|
+
padding: 0;
|
|
88
|
+
margin: -1px;
|
|
89
|
+
overflow: hidden;
|
|
90
|
+
clip: rect(0, 0, 0, 0);
|
|
91
|
+
white-space: nowrap;
|
|
92
|
+
border-width: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ── type.css ── */
|
|
96
|
+
/*
|
|
97
|
+
* 글자. 읽는 글자 크기는 화면마다 셋이다: 제목(n-title), 본문(n-body), 작은 글(n-small).
|
|
98
|
+
* n-display 는 첫 화면 제목에만 쓴다. 위계는 굵기 대신 두 톤(--n-hi, --n-mid)으로 만든다.
|
|
99
|
+
* 굵기는 제목 500, 본문 400 두 가지다.
|
|
100
|
+
*/
|
|
101
|
+
.night .n-display {
|
|
102
|
+
font-size: var(--n-fs-display);
|
|
103
|
+
line-height: 1.16;
|
|
104
|
+
letter-spacing: -0.035em;
|
|
105
|
+
font-weight: 500;
|
|
106
|
+
color: var(--n-hi);
|
|
107
|
+
}
|
|
108
|
+
.night .n-title {
|
|
109
|
+
font-size: var(--n-fs-title);
|
|
110
|
+
line-height: 1.2;
|
|
111
|
+
letter-spacing: -0.03em;
|
|
112
|
+
font-weight: 500;
|
|
113
|
+
color: var(--n-hi);
|
|
114
|
+
}
|
|
115
|
+
/* 여러 줄 제목은 줄 길이를 고르게 맞춘다 */
|
|
116
|
+
.night :is(.n-display, .n-title) {
|
|
117
|
+
text-wrap: balance;
|
|
118
|
+
}
|
|
119
|
+
/* 두 톤: 제목 안에서 받쳐 주는 말. lo 는 줄을 바꿔서, lo-inline 은 같은 줄에서 회색 */
|
|
120
|
+
.night .n-title .lo,
|
|
121
|
+
.night .n-display .lo {
|
|
122
|
+
display: block;
|
|
123
|
+
color: var(--n-mid);
|
|
124
|
+
}
|
|
125
|
+
.night .n-title .lo-inline,
|
|
126
|
+
.night .n-display .lo-inline {
|
|
127
|
+
color: var(--n-mid);
|
|
128
|
+
}
|
|
129
|
+
.night .n-body {
|
|
130
|
+
font-size: var(--n-fs-body);
|
|
131
|
+
line-height: 1.5;
|
|
132
|
+
letter-spacing: -0.01em;
|
|
133
|
+
color: var(--n-soft);
|
|
134
|
+
/* 마지막 줄에 낱말 하나만 남지 않게 */
|
|
135
|
+
text-wrap: pretty;
|
|
136
|
+
}
|
|
137
|
+
/* 목록이나 카드 안의 작은 제목. 본문과 같은 크기이고 진한 글자 */
|
|
138
|
+
.night .n-card-title {
|
|
139
|
+
font-size: var(--n-fs-body);
|
|
140
|
+
line-height: 1.4;
|
|
141
|
+
font-weight: 500;
|
|
142
|
+
letter-spacing: -0.01em;
|
|
143
|
+
color: var(--n-hi);
|
|
144
|
+
}
|
|
145
|
+
.night .n-small {
|
|
146
|
+
font-size: var(--n-fs-small);
|
|
147
|
+
line-height: 1.45;
|
|
148
|
+
letter-spacing: 0;
|
|
149
|
+
color: var(--n-mid);
|
|
150
|
+
}
|
|
151
|
+
/* 본문과 작은 글 안의 진한 낱말(기관 이름, 핵심 구절). 굵기는 그대로 두고 색만 올린다 */
|
|
152
|
+
.night :is(.n-body, .n-small) .hi {
|
|
153
|
+
color: var(--n-hi);
|
|
154
|
+
}
|
|
155
|
+
/* 숫자가 세로로 줄이 맞아야 하는 곳(연도, 금액) */
|
|
156
|
+
.night .n-num {
|
|
157
|
+
font-variant-numeric: tabular-nums;
|
|
158
|
+
letter-spacing: 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* ── layout.css ── */
|
|
162
|
+
/*
|
|
163
|
+
* 뼈대. 읽는 글은 한 줄기로 흐른다: 왼쪽에 작은 라벨이나 제목, 오른쪽 한 칸에 본문.
|
|
164
|
+
* 규칙의 순서를 바꾸지 않는다(특이도가 같은 규칙은 뒤에 온 것이 이긴다).
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
/* 내용 폭: 가운데 1064px */
|
|
168
|
+
.night .n-wrap {
|
|
169
|
+
width: 100%;
|
|
170
|
+
max-width: var(--n-wrap);
|
|
171
|
+
margin-inline: auto;
|
|
172
|
+
padding-inline: var(--n-gutter);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* 하위 페이지의 바깥과 머리. 위 여백은 떠 있는 메뉴 자리다 */
|
|
176
|
+
.night.n-page {
|
|
177
|
+
padding-top: clamp(120px, 12vw, 164px);
|
|
178
|
+
min-height: 100vh;
|
|
179
|
+
}
|
|
180
|
+
.night .n-page-head {
|
|
181
|
+
padding-bottom: 72px;
|
|
182
|
+
}
|
|
183
|
+
@media (min-width: 1024px) {
|
|
184
|
+
.night .n-page-head {
|
|
185
|
+
padding-bottom: 96px;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* 구간 */
|
|
190
|
+
.night .n-sect {
|
|
191
|
+
position: relative;
|
|
192
|
+
padding: 96px 0 72px;
|
|
193
|
+
}
|
|
194
|
+
@media (min-width: 1024px) {
|
|
195
|
+
.night .n-sect {
|
|
196
|
+
padding: 128px 0 96px;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* 짧은 구간: 문장 하나와 버튼 하나뿐인 줄 */
|
|
201
|
+
.night .n-sect.slim {
|
|
202
|
+
padding-block: 64px;
|
|
203
|
+
}
|
|
204
|
+
@media (min-width: 1024px) {
|
|
205
|
+
.night .n-sect.slim {
|
|
206
|
+
padding-block: 80px;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/* 선으로 시작하는 구간: 앞 구간과의 사이에 가는 선을 내용 폭만큼 긋는다 */
|
|
210
|
+
.night .n-sect.ruled::before {
|
|
211
|
+
content: "";
|
|
212
|
+
position: absolute;
|
|
213
|
+
top: 0;
|
|
214
|
+
left: max(var(--n-gutter), calc(50% - var(--n-wrap) / 2 + var(--n-gutter)));
|
|
215
|
+
right: max(var(--n-gutter), calc(50% - var(--n-wrap) / 2 + var(--n-gutter)));
|
|
216
|
+
border-top: 1px solid var(--n-line);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* 가는 선 하나(위쪽) */
|
|
220
|
+
.night .n-rule {
|
|
221
|
+
border-top: 1px solid var(--n-line);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* 구간 머리: 넓은 화면에서 왼쪽 제목, 오른쪽 본문 */
|
|
225
|
+
.night .n-head {
|
|
226
|
+
display: grid;
|
|
227
|
+
gap: 16px;
|
|
228
|
+
margin-bottom: 48px;
|
|
229
|
+
}
|
|
230
|
+
@media (min-width: 1024px) {
|
|
231
|
+
.night .n-head {
|
|
232
|
+
grid-template-columns: 1fr 1fr;
|
|
233
|
+
align-items: center;
|
|
234
|
+
gap: 40px;
|
|
235
|
+
margin-bottom: 64px;
|
|
236
|
+
}
|
|
237
|
+
.night .n-head > :last-child {
|
|
238
|
+
max-width: 30em;
|
|
239
|
+
justify-self: end;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/* 제목만 있으면 한 줄 전체를 쓴다 */
|
|
243
|
+
.night .n-head > :only-child {
|
|
244
|
+
justify-self: start;
|
|
245
|
+
max-width: none;
|
|
246
|
+
grid-column: 1 / -1;
|
|
247
|
+
}
|
|
248
|
+
/* 선으로 시작하는 구간 머리(<SectionHead rule>). 위 여백까지 한 이름으로 묶었다 */
|
|
249
|
+
.night .n-head.ruled {
|
|
250
|
+
border-top: 1px solid var(--n-line);
|
|
251
|
+
padding-top: 48px;
|
|
252
|
+
}
|
|
253
|
+
@media (min-width: 768px) {
|
|
254
|
+
.night .n-head.ruled {
|
|
255
|
+
padding-top: 64px;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/* 페이지 머리 안에서는 아래 여백을 페이지 머리가 정한다 */
|
|
259
|
+
.night .n-page-head > .n-head {
|
|
260
|
+
margin-bottom: 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* 줄 목록: 왼쪽 200px 라벨, 오른쪽 본문. 라벨과 본문은 첫 줄의 글자 밑선을 맞추고, 긴 낱말이 칸을 밀지 않게 한다 */
|
|
264
|
+
.night .n-rows {
|
|
265
|
+
border-top: 1px solid var(--n-line);
|
|
266
|
+
}
|
|
267
|
+
.night .n-row {
|
|
268
|
+
display: grid;
|
|
269
|
+
gap: 8px 24px;
|
|
270
|
+
align-items: baseline;
|
|
271
|
+
padding: 24px 0;
|
|
272
|
+
border-bottom: 1px solid var(--n-line);
|
|
273
|
+
}
|
|
274
|
+
@media (min-width: 768px) {
|
|
275
|
+
.night .n-row {
|
|
276
|
+
grid-template-columns: 200px minmax(0, 1fr);
|
|
277
|
+
padding: 28px 0;
|
|
278
|
+
}
|
|
279
|
+
.night .n-row.wide-label {
|
|
280
|
+
grid-template-columns: 280px minmax(0, 1fr);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
.night .n-row > .n-row-label {
|
|
284
|
+
font-size: var(--n-fs-small);
|
|
285
|
+
line-height: 1.6;
|
|
286
|
+
font-weight: 400;
|
|
287
|
+
letter-spacing: 0;
|
|
288
|
+
color: var(--n-mid);
|
|
289
|
+
}
|
|
290
|
+
.night .n-rows > .n-row:last-child {
|
|
291
|
+
border-bottom: 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* 검은 판: 화면 양옆에서 한 칸 들어온 둥근 면 */
|
|
295
|
+
.night .n-panel {
|
|
296
|
+
position: relative;
|
|
297
|
+
z-index: 1;
|
|
298
|
+
margin: 0 var(--n-gutter);
|
|
299
|
+
border-radius: var(--n-radius);
|
|
300
|
+
background: var(--n-panel);
|
|
301
|
+
padding: 88px 0 72px;
|
|
302
|
+
}
|
|
303
|
+
@media (min-width: 1024px) {
|
|
304
|
+
.night .n-panel {
|
|
305
|
+
padding: 120px 0 104px;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
@media (max-width: 767px) {
|
|
309
|
+
.night .n-panel {
|
|
310
|
+
margin: 0 12px;
|
|
311
|
+
padding: 72px 0 56px;
|
|
312
|
+
}
|
|
313
|
+
.night .n-panel .n-wrap {
|
|
314
|
+
padding: 0 16px;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* 그림과 영상의 틀 */
|
|
319
|
+
.night .n-media {
|
|
320
|
+
position: relative;
|
|
321
|
+
aspect-ratio: 4 / 3;
|
|
322
|
+
border-radius: var(--n-radius-md);
|
|
323
|
+
overflow: hidden;
|
|
324
|
+
background: var(--n-card);
|
|
325
|
+
}
|
|
326
|
+
.night .n-media.wide {
|
|
327
|
+
aspect-ratio: 16 / 9;
|
|
328
|
+
}
|
|
329
|
+
.night .n-media video,
|
|
330
|
+
.night .n-media img {
|
|
331
|
+
display: block;
|
|
332
|
+
width: 100%;
|
|
333
|
+
height: 100%;
|
|
334
|
+
object-fit: cover;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* 사진 판: 어둡게 누른 사진 위 왼쪽 아래에 글. 사진(img, video)은 판을 채운다.
|
|
338
|
+
쌓이는 순서(사진, 그늘, 글)는 판 안에 가둔다(isolation). 글의 z-index 가 판 밖의 요소와 겨루지 않는다 */
|
|
339
|
+
.night .n-photo {
|
|
340
|
+
position: relative;
|
|
341
|
+
isolation: isolate;
|
|
342
|
+
overflow: hidden;
|
|
343
|
+
display: flex;
|
|
344
|
+
align-items: flex-end;
|
|
345
|
+
min-height: 480px;
|
|
346
|
+
border-radius: var(--n-radius);
|
|
347
|
+
background: var(--n-card);
|
|
348
|
+
}
|
|
349
|
+
@media (min-width: 1024px) {
|
|
350
|
+
.night .n-photo {
|
|
351
|
+
min-height: 560px;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
.night .n-photo > img,
|
|
355
|
+
.night .n-photo > video {
|
|
356
|
+
position: absolute;
|
|
357
|
+
inset: 0;
|
|
358
|
+
width: 100%;
|
|
359
|
+
height: 100%;
|
|
360
|
+
object-fit: cover;
|
|
361
|
+
filter: saturate(0.8) brightness(0.62);
|
|
362
|
+
}
|
|
363
|
+
.night .n-photo::after {
|
|
364
|
+
content: "";
|
|
365
|
+
position: absolute;
|
|
366
|
+
inset: 0;
|
|
367
|
+
background: linear-gradient(to top, var(--n-shade), var(--n-shade-soft) 65%);
|
|
368
|
+
}
|
|
369
|
+
.night .n-photo-in {
|
|
370
|
+
position: relative;
|
|
371
|
+
z-index: 1;
|
|
372
|
+
display: grid;
|
|
373
|
+
gap: 16px;
|
|
374
|
+
padding: 40px 28px;
|
|
375
|
+
max-width: 640px;
|
|
376
|
+
}
|
|
377
|
+
@media (min-width: 1024px) {
|
|
378
|
+
.night .n-photo-in {
|
|
379
|
+
padding: 64px;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/* 사진 위에서는 받쳐 주는 말을 조금 밝게 둔다(50% 회색은 사진 위에서 묻힌다) */
|
|
383
|
+
.night .n-photo .n-title .lo-inline {
|
|
384
|
+
color: var(--n-on-photo);
|
|
385
|
+
}
|
|
386
|
+
/* 판 안에 버튼 줄이 있으면 문단과 40px 띄운다(안쪽 간격 16px에 더한다) */
|
|
387
|
+
.night .n-photo-in > .n-actions {
|
|
388
|
+
margin-top: 24px;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* 문의 띠: 왼쪽 문장, 오른쪽 버튼 */
|
|
392
|
+
.night .n-cta-band {
|
|
393
|
+
display: grid;
|
|
394
|
+
gap: 24px;
|
|
395
|
+
padding: 56px 0 8px;
|
|
396
|
+
border-top: 1px solid var(--n-line);
|
|
397
|
+
}
|
|
398
|
+
@media (min-width: 768px) {
|
|
399
|
+
.night .n-cta-band {
|
|
400
|
+
grid-template-columns: 1fr auto;
|
|
401
|
+
align-items: center;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
.night .n-cta-band .n-title + .n-body {
|
|
405
|
+
margin-top: 16px;
|
|
406
|
+
max-width: 34em;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* ── controls.css ── */
|
|
410
|
+
/*
|
|
411
|
+
* 조작부. 첫 화면과 띠에는 버튼 하나(알약)와 글자 링크 하나까지만 둔다.
|
|
412
|
+
*/
|
|
413
|
+
|
|
414
|
+
/* 알약 버튼: 높이 48, 흰 면. ghost 는 어두운 면 */
|
|
415
|
+
.night .n-pill {
|
|
416
|
+
display: inline-flex;
|
|
417
|
+
align-items: center;
|
|
418
|
+
justify-content: center;
|
|
419
|
+
gap: 10px;
|
|
420
|
+
height: 48px;
|
|
421
|
+
padding: 0 28px;
|
|
422
|
+
border-radius: var(--n-radius-pill);
|
|
423
|
+
background: var(--n-btn);
|
|
424
|
+
color: var(--n-btn-ink);
|
|
425
|
+
font-size: var(--n-fs-control);
|
|
426
|
+
line-height: 1;
|
|
427
|
+
font-weight: 500;
|
|
428
|
+
letter-spacing: 0.01em;
|
|
429
|
+
transition:
|
|
430
|
+
background-color 0.2s,
|
|
431
|
+
transform 0.2s;
|
|
432
|
+
}
|
|
433
|
+
.night .n-pill:hover {
|
|
434
|
+
background: var(--n-btn-hover);
|
|
435
|
+
}
|
|
436
|
+
.night .n-pill.ghost {
|
|
437
|
+
background: var(--n-ghost);
|
|
438
|
+
color: var(--n-hi);
|
|
439
|
+
}
|
|
440
|
+
.night .n-pill.ghost:hover {
|
|
441
|
+
background: var(--n-ghost-hover);
|
|
442
|
+
}
|
|
443
|
+
.night .n-pill:focus-visible {
|
|
444
|
+
outline: 2px solid var(--n-focus);
|
|
445
|
+
outline-offset: 3px;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* 글자 링크: 버튼 옆의 둘째 행동, 본문 아래의 '더 보기' */
|
|
449
|
+
.night .n-link {
|
|
450
|
+
display: inline-flex;
|
|
451
|
+
align-items: center;
|
|
452
|
+
gap: 8px;
|
|
453
|
+
font-size: var(--n-fs-control);
|
|
454
|
+
/* 줄 높이를 물려받지 않고 정한다(앱마다 바탕 줄 높이가 달라 링크 높이가 달라졌다) */
|
|
455
|
+
line-height: 1.6;
|
|
456
|
+
font-weight: 500;
|
|
457
|
+
color: var(--n-hi);
|
|
458
|
+
transition: opacity 0.2s;
|
|
459
|
+
}
|
|
460
|
+
.night .n-link:hover {
|
|
461
|
+
opacity: 0.7;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* 버튼과 링크를 나란히 놓는 줄 */
|
|
465
|
+
.night .n-actions {
|
|
466
|
+
display: flex;
|
|
467
|
+
flex-wrap: wrap;
|
|
468
|
+
align-items: center;
|
|
469
|
+
gap: 16px 32px;
|
|
470
|
+
}
|
|
471
|
+
.night .n-page-head > .n-actions {
|
|
472
|
+
margin-top: 48px;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/* ── timeline.css ── */
|
|
476
|
+
/*
|
|
477
|
+
* 연도별 이력. 왼쪽에 연도, 오른쪽에 그 해의 줄들. 많으면 앞의 몇 줄만 보이고 '외 N건'으로 접는다.
|
|
478
|
+
*/
|
|
479
|
+
.night .n-timeline {
|
|
480
|
+
border-top: 1px solid var(--n-line);
|
|
481
|
+
}
|
|
482
|
+
.night .n-tl-year {
|
|
483
|
+
display: grid;
|
|
484
|
+
gap: 12px;
|
|
485
|
+
padding: 28px 0;
|
|
486
|
+
border-bottom: 1px solid var(--n-line);
|
|
487
|
+
}
|
|
488
|
+
@media (min-width: 768px) {
|
|
489
|
+
.night .n-tl-year {
|
|
490
|
+
grid-template-columns: 160px 1fr;
|
|
491
|
+
gap: 24px;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
.night .n-tl-year ul {
|
|
495
|
+
display: grid;
|
|
496
|
+
gap: 12px;
|
|
497
|
+
}
|
|
498
|
+
.night .n-tl-more summary {
|
|
499
|
+
display: inline-flex;
|
|
500
|
+
align-items: center;
|
|
501
|
+
gap: 6px;
|
|
502
|
+
width: max-content;
|
|
503
|
+
cursor: pointer;
|
|
504
|
+
list-style: none;
|
|
505
|
+
transition: color 0.2s;
|
|
506
|
+
}
|
|
507
|
+
.night .n-tl-more summary::-webkit-details-marker {
|
|
508
|
+
display: none;
|
|
509
|
+
}
|
|
510
|
+
.night .n-tl-more summary:hover {
|
|
511
|
+
color: var(--n-hi);
|
|
512
|
+
}
|
|
513
|
+
.night .n-tl-more summary .chev {
|
|
514
|
+
display: inline-block;
|
|
515
|
+
transform: rotate(90deg);
|
|
516
|
+
}
|
|
517
|
+
.night .n-tl-more summary:focus-visible {
|
|
518
|
+
outline: 2px solid var(--n-mid);
|
|
519
|
+
outline-offset: 4px;
|
|
520
|
+
}
|
|
521
|
+
.night .n-tl-more details[open] > summary {
|
|
522
|
+
display: none;
|
|
523
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ElementType, ReactNode } from "react";
|
|
2
|
+
import { type PolyProps } from "./util.js";
|
|
3
|
+
type ControlProps = {
|
|
4
|
+
className?: string;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* 알약 버튼. 한 화면의 주 행동 하나에 쓴다. ghost 는 어두운 면(둘째 행동).
|
|
9
|
+
* 링크면 as 로 프레임워크의 링크 컴포넌트를, 단추면 as="button" 을 넘긴다.
|
|
10
|
+
*/
|
|
11
|
+
export declare function Pill<T extends ElementType = "a">({ as, ghost, className, ...rest }: PolyProps<T, ControlProps & {
|
|
12
|
+
ghost?: boolean;
|
|
13
|
+
}>): import("react").JSX.Element;
|
|
14
|
+
/** 글자 링크. 버튼 옆의 둘째 행동이나 본문 아래의 '더 보기'. arrow 를 주면 끝에 화살표가 붙는다. */
|
|
15
|
+
export declare function TextLink<T extends ElementType = "a">({ as, arrow, className, children, ...rest }: PolyProps<T, ControlProps & {
|
|
16
|
+
arrow?: boolean;
|
|
17
|
+
}>): import("react").JSX.Element;
|
|
18
|
+
/** 버튼과 글자 링크를 나란히 놓는 줄. 버튼 하나와 링크 하나까지만 둔다. */
|
|
19
|
+
export declare function Actions({ className, children }: ControlProps): import("react").JSX.Element;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "./util.js";
|
|
3
|
+
/**
|
|
4
|
+
* 알약 버튼. 한 화면의 주 행동 하나에 쓴다. ghost 는 어두운 면(둘째 행동).
|
|
5
|
+
* 링크면 as 로 프레임워크의 링크 컴포넌트를, 단추면 as="button" 을 넘긴다.
|
|
6
|
+
*/
|
|
7
|
+
export function Pill({ as, ghost, className, ...rest }) {
|
|
8
|
+
const Tag = as ?? "a";
|
|
9
|
+
return _jsx(Tag, { className: cx("n-pill", ghost && "ghost", className), ...rest });
|
|
10
|
+
}
|
|
11
|
+
/** 글자 링크. 버튼 옆의 둘째 행동이나 본문 아래의 '더 보기'. arrow 를 주면 끝에 화살표가 붙는다. */
|
|
12
|
+
export function TextLink({ as, arrow, className, children, ...rest }) {
|
|
13
|
+
const Tag = as ?? "a";
|
|
14
|
+
return (_jsxs(Tag, { className: cx("n-link", className), ...rest, children: [children, arrow ? _jsx("span", { "aria-hidden": "true", children: "\u2192" }) : null] }));
|
|
15
|
+
}
|
|
16
|
+
/** 버튼과 글자 링크를 나란히 놓는 줄. 버튼 하나와 링크 하나까지만 둔다. */
|
|
17
|
+
export function Actions({ className, children }) {
|
|
18
|
+
return _jsx("div", { className: cx("n-actions", className), children: children });
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { tone, titleText, hi } from "./tone.js";
|
|
2
|
+
export { cx } from "./util.js";
|
|
3
|
+
export type { PolyProps } from "./util.js";
|
|
4
|
+
export { Display, Title, Body, CardTitle, Small } from "./type.js";
|
|
5
|
+
export { Page, PageHead, Section, SectionHead, Rows, Row, Panel, Media, PhotoPanel, CtaBand } from "./layout.js";
|
|
6
|
+
export { Pill, TextLink, Actions } from "./controls.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { tone, titleText, hi } from "./tone.js";
|
|
2
|
+
export { cx } from "./util.js";
|
|
3
|
+
export { Display, Title, Body, CardTitle, Small } from "./type.js";
|
|
4
|
+
export { Page, PageHead, Section, SectionHead, Rows, Row, Panel, Media, PhotoPanel, CtaBand } from "./layout.js";
|
|
5
|
+
export { Pill, TextLink, Actions } from "./controls.js";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ElementType, ReactNode } from "react";
|
|
2
|
+
import { type PolyProps } from "./util.js";
|
|
3
|
+
type BoxProps = {
|
|
4
|
+
className?: string;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
/** 하위 페이지의 맨 바깥. 검은 바탕과 떠 있는 메뉴 자리(위 여백)를 준다. */
|
|
8
|
+
export declare function Page<T extends ElementType = "div">({ as, className, ...rest }: PolyProps<T, BoxProps>): import("react").JSX.Element;
|
|
9
|
+
/** 페이지 머리: 제목 한 줄, 받쳐 주는 문단 하나, 버튼 줄. */
|
|
10
|
+
export declare function PageHead({ title, label, lead, actions, className, children, }: {
|
|
11
|
+
/** 페이지 제목. "**진한 낱말** 나머지" 꼴이면 두 톤으로 그린다 */
|
|
12
|
+
title: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
lead?: ReactNode;
|
|
15
|
+
/** 버튼 하나와 글자 링크 하나까지 */
|
|
16
|
+
actions?: ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
/**
|
|
21
|
+
* 구간. 안쪽은 내용 폭(1064px)으로 감싼다. bare 를 주면 감싸지 않는다(화면 끝까지 쓰는 내용).
|
|
22
|
+
* rule 은 앞 구간과의 사이에 가는 선을 긋고, slim 은 문장 하나와 버튼 하나뿐인 짧은 구간의 여백을 준다.
|
|
23
|
+
*/
|
|
24
|
+
export declare function Section<T extends ElementType = "section">({ as, bare, rule, slim, className, children, ...rest }: PolyProps<T, BoxProps & {
|
|
25
|
+
bare?: boolean;
|
|
26
|
+
rule?: boolean;
|
|
27
|
+
slim?: boolean;
|
|
28
|
+
}>): import("react").JSX.Element;
|
|
29
|
+
/** 구간 머리: 왼쪽에 한 줄 제목, 오른쪽에 본문(children). 본문이 없으면 제목이 한 줄 전체를 쓴다. */
|
|
30
|
+
export declare function SectionHead({ title, label, rule, as, className, children, }: {
|
|
31
|
+
title: string;
|
|
32
|
+
label?: string;
|
|
33
|
+
/** 위에 가는 선을 긋고 그 아래에서 시작한다 */
|
|
34
|
+
rule?: boolean;
|
|
35
|
+
as?: "h1" | "h2" | "h3";
|
|
36
|
+
className?: string;
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
39
|
+
/** 줄 목록의 바깥. 순서가 있으면 as="ol". */
|
|
40
|
+
export declare function Rows<T extends ElementType = "div">({ as, className, ...rest }: PolyProps<T, BoxProps>): import("react").JSX.Element;
|
|
41
|
+
/** 한 줄: 왼쪽 작은 라벨, 오른쪽 본문. 글만 넘기면 본문 문단으로 그린다. */
|
|
42
|
+
export declare function Row<T extends ElementType = "div">({ as, label, labelAs, wide, className, children, ...rest }: PolyProps<T, BoxProps & {
|
|
43
|
+
label: ReactNode;
|
|
44
|
+
labelAs?: "h3" | "h4" | "p" | "dt";
|
|
45
|
+
wide?: boolean;
|
|
46
|
+
}>): import("react").JSX.Element;
|
|
47
|
+
/** 검은 판. 화면 양옆에서 한 칸 들어온 둥근 면으로 구간 하나를 감싼다. */
|
|
48
|
+
export declare function Panel<T extends ElementType = "section">({ as, className, children, ...rest }: PolyProps<T, BoxProps>): import("react").JSX.Element;
|
|
49
|
+
/** 그림과 영상의 틀. 기본 4:3, wide 는 16:9. 안의 img, video 는 틀을 채운다. */
|
|
50
|
+
export declare function Media<T extends ElementType = "div">({ as, wide, className, ...rest }: PolyProps<T, BoxProps & {
|
|
51
|
+
wide?: boolean;
|
|
52
|
+
}>): import("react").JSX.Element;
|
|
53
|
+
/**
|
|
54
|
+
* 사진 판. 어둡게 누른 사진 위 왼쪽 아래에 제목과 문단을 둔다. media 에 img 나 video(프레임워크의 이미지 컴포넌트)를 넘긴다.
|
|
55
|
+
* 한 페이지에 하나, 밝은 곳이 필요한 자리에 쓴다.
|
|
56
|
+
*/
|
|
57
|
+
export declare function PhotoPanel<T extends ElementType = "div">({ as, media, className, children, ...rest }: PolyProps<T, BoxProps & {
|
|
58
|
+
media?: ReactNode;
|
|
59
|
+
}>): import("react").JSX.Element;
|
|
60
|
+
/** 문의 띠: 왼쪽 한 줄 문장(과 짧은 문단), 오른쪽 버튼 줄. 페이지 끝에 하나 둔다. */
|
|
61
|
+
export declare function CtaBand({ title, label, body, actions, className, }: {
|
|
62
|
+
title: string;
|
|
63
|
+
label?: string;
|
|
64
|
+
body?: ReactNode;
|
|
65
|
+
actions: ReactNode;
|
|
66
|
+
className?: string;
|
|
67
|
+
}): import("react").JSX.Element;
|
|
68
|
+
export {};
|