create-mintly 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.
@@ -0,0 +1,1322 @@
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.0" />
6
+ <title>회의록 대시보드</title>
7
+ <style>
8
+ /* ── Reset & Base ─────────────────────────────────────────── */
9
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
10
+
11
+ :root {
12
+ --bg-start: #faf8f4;
13
+ --bg-end: #f6f2ea;
14
+ --panel: #fffdf8;
15
+ --border: rgba(31,41,51,0.12);
16
+ --radius: 20px;
17
+ --radius-sm: 10px;
18
+ --shadow: 0 2px 12px rgba(31,41,51,0.07), 0 1px 3px rgba(31,41,51,0.05);
19
+ --shadow-md: 0 4px 24px rgba(31,41,51,0.10), 0 1px 4px rgba(31,41,51,0.06);
20
+
21
+ --brand: #0f766e;
22
+ --brand-light: #ccfbf1;
23
+ --brand-text: #065f56;
24
+ --accent: #b45309;
25
+ --accent-light: #fef3c7;
26
+ --accent-text: #92400e;
27
+ --danger: #b91c1c;
28
+ --danger-light: #fee2e2;
29
+ --danger-text: #991b1b;
30
+ --neutral: #64748b;
31
+ --neutral-light: #f1f5f9;
32
+ --neutral-text: #475569;
33
+ --success: #15803d;
34
+ --success-light: #dcfce7;
35
+ --success-text: #166534;
36
+
37
+ --ink: #1f2933;
38
+ --ink-2: #374151;
39
+ --ink-3: #6b7280;
40
+ --ink-4: #9ca3af;
41
+
42
+ --font: "SUIT Variable", "Pretendard", "Apple SD Gothic Neo", "Noto Sans KR", sans-serif;
43
+ }
44
+
45
+ @font-face {
46
+ font-family: "SUIT Variable";
47
+ src: url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css");
48
+ /* Fallback: will use Pretendard or system Korean font */
49
+ }
50
+
51
+ html { scroll-behavior: smooth; }
52
+
53
+ body {
54
+ font-family: var(--font);
55
+ background: linear-gradient(160deg, var(--bg-start) 0%, var(--bg-end) 100%);
56
+ min-height: 100vh;
57
+ color: var(--ink);
58
+ -webkit-font-smoothing: antialiased;
59
+ line-height: 1.6;
60
+ }
61
+
62
+ /* ── Layout ──────────────────────────────────────────────── */
63
+ .page {
64
+ max-width: 1200px;
65
+ margin: 0 auto;
66
+ padding: 40px 24px 80px;
67
+ }
68
+
69
+ .grid-3 {
70
+ display: grid;
71
+ grid-template-columns: repeat(3, 1fr);
72
+ gap: 16px;
73
+ }
74
+ .grid-2 {
75
+ display: grid;
76
+ grid-template-columns: repeat(2, 1fr);
77
+ gap: 16px;
78
+ }
79
+
80
+ @media (max-width: 880px) {
81
+ .grid-3, .grid-2 { grid-template-columns: 1fr; }
82
+ .page { padding: 24px 16px 60px; }
83
+ }
84
+
85
+ /* ── Panel ───────────────────────────────────────────────── */
86
+ .panel {
87
+ background: var(--panel);
88
+ border: 1px solid var(--border);
89
+ border-radius: var(--radius);
90
+ box-shadow: var(--shadow);
91
+ padding: 24px;
92
+ }
93
+ .panel-sm {
94
+ background: var(--panel);
95
+ border: 1px solid var(--border);
96
+ border-radius: var(--radius-sm);
97
+ box-shadow: var(--shadow);
98
+ padding: 18px 20px;
99
+ }
100
+
101
+ /* ── Section wrapper ─────────────────────────────────────── */
102
+ .section-wrapper {
103
+ margin-top: 32px;
104
+ }
105
+ .section-header {
106
+ display: flex;
107
+ align-items: center;
108
+ gap: 10px;
109
+ margin-bottom: 16px;
110
+ }
111
+ .section-title {
112
+ font-size: 15px;
113
+ font-weight: 700;
114
+ color: var(--ink-2);
115
+ letter-spacing: -0.01em;
116
+ }
117
+ .section-icon {
118
+ width: 28px;
119
+ height: 28px;
120
+ border-radius: 8px;
121
+ background: var(--brand-light);
122
+ color: var(--brand-text);
123
+ display: flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ font-size: 14px;
127
+ flex-shrink: 0;
128
+ }
129
+ .section-count {
130
+ font-size: 11px;
131
+ font-weight: 600;
132
+ color: var(--ink-4);
133
+ background: var(--neutral-light);
134
+ padding: 2px 8px;
135
+ border-radius: 20px;
136
+ margin-left: auto;
137
+ }
138
+
139
+ /* ── Hero ────────────────────────────────────────────────── */
140
+ .hero {
141
+ padding: 40px 40px 36px;
142
+ position: relative;
143
+ overflow: hidden;
144
+ }
145
+ .hero::before {
146
+ content: "";
147
+ position: absolute;
148
+ inset: 0;
149
+ background: linear-gradient(135deg, rgba(15,118,110,0.04) 0%, transparent 60%);
150
+ pointer-events: none;
151
+ }
152
+ .hero-eyebrow {
153
+ display: flex;
154
+ align-items: center;
155
+ gap: 10px;
156
+ margin-bottom: 14px;
157
+ }
158
+ .hero-title {
159
+ font-size: clamp(22px, 3vw, 30px);
160
+ font-weight: 800;
161
+ color: var(--ink);
162
+ letter-spacing: -0.03em;
163
+ line-height: 1.25;
164
+ margin-bottom: 8px;
165
+ }
166
+ .hero-subtitle {
167
+ font-size: 15px;
168
+ color: var(--ink-3);
169
+ font-weight: 400;
170
+ margin-bottom: 20px;
171
+ line-height: 1.55;
172
+ }
173
+ .hero-meta {
174
+ display: flex;
175
+ align-items: center;
176
+ flex-wrap: wrap;
177
+ gap: 12px;
178
+ }
179
+ .hero-date {
180
+ font-size: 13px;
181
+ color: var(--ink-3);
182
+ font-weight: 500;
183
+ display: flex;
184
+ align-items: center;
185
+ gap: 5px;
186
+ }
187
+ .hero-sources {
188
+ display: flex;
189
+ flex-wrap: wrap;
190
+ gap: 6px;
191
+ }
192
+ .source-chip {
193
+ font-size: 11px;
194
+ font-weight: 500;
195
+ color: var(--neutral-text);
196
+ background: var(--neutral-light);
197
+ border: 1px solid var(--border);
198
+ border-radius: 6px;
199
+ padding: 3px 9px;
200
+ display: flex;
201
+ align-items: center;
202
+ gap: 4px;
203
+ }
204
+
205
+ /* ── Badges ──────────────────────────────────────────────── */
206
+ .badge {
207
+ display: inline-flex;
208
+ align-items: center;
209
+ gap: 4px;
210
+ padding: 3px 10px;
211
+ border-radius: 20px;
212
+ font-size: 11px;
213
+ font-weight: 700;
214
+ letter-spacing: 0.02em;
215
+ white-space: nowrap;
216
+ }
217
+ .badge.brand { background: var(--brand-light); color: var(--brand-text); }
218
+ .badge.warn { background: var(--accent-light); color: var(--accent-text); }
219
+ .badge.danger { background: var(--danger-light); color: var(--danger-text); }
220
+ .badge.neutral { background: var(--neutral-light); color: var(--neutral-text); }
221
+ .badge.success { background: var(--success-light); color: var(--success-text); }
222
+ .badge.type {
223
+ background: rgba(15,118,110,0.10);
224
+ color: var(--brand);
225
+ border: 1px solid rgba(15,118,110,0.20);
226
+ font-size: 12px;
227
+ font-weight: 700;
228
+ padding: 4px 12px;
229
+ border-radius: 8px;
230
+ }
231
+
232
+ /* ── Summary cards ───────────────────────────────────────── */
233
+ .summary-card {
234
+ display: flex;
235
+ flex-direction: column;
236
+ gap: 8px;
237
+ }
238
+ .summary-label {
239
+ font-size: 11px;
240
+ font-weight: 700;
241
+ color: var(--ink-4);
242
+ text-transform: uppercase;
243
+ letter-spacing: 0.08em;
244
+ }
245
+ .summary-value {
246
+ font-size: 14px;
247
+ font-weight: 600;
248
+ color: var(--ink);
249
+ line-height: 1.5;
250
+ }
251
+ .summary-card .icon {
252
+ width: 36px;
253
+ height: 36px;
254
+ border-radius: 10px;
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ font-size: 18px;
259
+ margin-bottom: 4px;
260
+ }
261
+ .summary-card.conclusion .icon { background: rgba(15,118,110,0.10); }
262
+ .summary-card.status .icon { background: var(--accent-light); }
263
+ .summary-card.next .icon { background: var(--danger-light); }
264
+
265
+ /* ── Signal / Risk list ──────────────────────────────────── */
266
+ .signal-list {
267
+ display: flex;
268
+ flex-direction: column;
269
+ gap: 10px;
270
+ }
271
+ .signal-item {
272
+ display: flex;
273
+ flex-direction: column;
274
+ gap: 5px;
275
+ padding: 14px 16px;
276
+ border-radius: var(--radius-sm);
277
+ border: 1px solid var(--border);
278
+ background: var(--bg-start);
279
+ transition: box-shadow 0.15s;
280
+ }
281
+ .signal-item:hover { box-shadow: var(--shadow-md); }
282
+ .signal-header {
283
+ display: flex;
284
+ align-items: flex-start;
285
+ justify-content: space-between;
286
+ gap: 10px;
287
+ }
288
+ .signal-title {
289
+ font-size: 13px;
290
+ font-weight: 600;
291
+ color: var(--ink);
292
+ line-height: 1.45;
293
+ }
294
+ .signal-evidence {
295
+ font-size: 12px;
296
+ color: var(--ink-3);
297
+ line-height: 1.5;
298
+ }
299
+
300
+ /* ── Table ───────────────────────────────────────────────── */
301
+ .table-wrap {
302
+ overflow-x: auto;
303
+ border-radius: var(--radius-sm);
304
+ border: 1px solid var(--border);
305
+ }
306
+ table {
307
+ width: 100%;
308
+ border-collapse: collapse;
309
+ font-size: 13px;
310
+ }
311
+ thead th {
312
+ background: rgba(31,41,51,0.03);
313
+ color: var(--ink-3);
314
+ font-weight: 700;
315
+ font-size: 11px;
316
+ text-transform: uppercase;
317
+ letter-spacing: 0.06em;
318
+ padding: 11px 14px;
319
+ text-align: left;
320
+ border-bottom: 1px solid var(--border);
321
+ white-space: nowrap;
322
+ }
323
+ tbody tr {
324
+ border-bottom: 1px solid rgba(31,41,51,0.06);
325
+ transition: background 0.1s;
326
+ }
327
+ tbody tr:last-child { border-bottom: none; }
328
+ tbody tr:hover { background: rgba(15,118,110,0.03); }
329
+ tbody td {
330
+ padding: 12px 14px;
331
+ color: var(--ink-2);
332
+ vertical-align: top;
333
+ line-height: 1.5;
334
+ }
335
+ .td-primary {
336
+ font-weight: 600;
337
+ color: var(--ink);
338
+ }
339
+ .td-muted {
340
+ color: var(--ink-3);
341
+ font-size: 12px;
342
+ }
343
+ .td-type-dot {
344
+ display: inline-flex;
345
+ align-items: center;
346
+ gap: 5px;
347
+ }
348
+ .dot {
349
+ width: 7px;
350
+ height: 7px;
351
+ border-radius: 50%;
352
+ flex-shrink: 0;
353
+ }
354
+ .dot-meeting { background: var(--brand); }
355
+ .dot-milestone { background: var(--accent); }
356
+ .dot-target { background: var(--danger); }
357
+ .dot-default { background: var(--neutral); }
358
+
359
+ /* ── Action status ───────────────────────────────────────── */
360
+ .status-todo { background: var(--neutral-light); color: var(--neutral-text); }
361
+ .status-doing { background: var(--brand-light); color: var(--brand-text); }
362
+ .status-done { background: var(--success-light); color: var(--success-text); }
363
+
364
+ /* ── Decision cards ──────────────────────────────────────── */
365
+ .decision-card {
366
+ padding: 20px;
367
+ border: 1px solid var(--border);
368
+ border-radius: var(--radius-sm);
369
+ background: var(--bg-start);
370
+ display: flex;
371
+ flex-direction: column;
372
+ gap: 12px;
373
+ }
374
+ .decision-topic {
375
+ font-size: 14px;
376
+ font-weight: 700;
377
+ color: var(--ink);
378
+ }
379
+ .decision-options {
380
+ display: flex;
381
+ flex-wrap: wrap;
382
+ gap: 6px;
383
+ }
384
+ .option-chip {
385
+ font-size: 12px;
386
+ padding: 3px 10px;
387
+ border-radius: 6px;
388
+ border: 1px solid var(--border);
389
+ color: var(--ink-3);
390
+ background: var(--panel);
391
+ }
392
+ .option-chip.chosen {
393
+ background: var(--brand-light);
394
+ color: var(--brand-text);
395
+ border-color: rgba(15,118,110,0.25);
396
+ font-weight: 700;
397
+ }
398
+ .decision-rationale {
399
+ font-size: 13px;
400
+ color: var(--ink-2);
401
+ line-height: 1.55;
402
+ padding: 10px 12px;
403
+ background: rgba(15,118,110,0.04);
404
+ border-left: 3px solid var(--brand);
405
+ border-radius: 0 6px 6px 0;
406
+ }
407
+
408
+ /* ── Retro columns ───────────────────────────────────────── */
409
+ .retro-col {
410
+ display: flex;
411
+ flex-direction: column;
412
+ gap: 8px;
413
+ }
414
+ .retro-label {
415
+ font-size: 11px;
416
+ font-weight: 700;
417
+ text-transform: uppercase;
418
+ letter-spacing: 0.07em;
419
+ padding: 4px 10px;
420
+ border-radius: 6px;
421
+ width: fit-content;
422
+ margin-bottom: 2px;
423
+ }
424
+ .retro-well .retro-label { background: var(--success-light); color: var(--success-text); }
425
+ .retro-improve .retro-label { background: var(--accent-light); color: var(--accent-text); }
426
+ .retro-commit .retro-label { background: var(--brand-light); color: var(--brand-text); }
427
+ .retro-item {
428
+ font-size: 13px;
429
+ color: var(--ink-2);
430
+ padding: 10px 12px;
431
+ border-radius: 8px;
432
+ border: 1px solid var(--border);
433
+ background: var(--panel);
434
+ line-height: 1.5;
435
+ display: flex;
436
+ align-items: flex-start;
437
+ gap: 8px;
438
+ }
439
+ .retro-item::before {
440
+ content: "·";
441
+ color: var(--ink-4);
442
+ font-size: 18px;
443
+ line-height: 1.1;
444
+ flex-shrink: 0;
445
+ }
446
+
447
+ /* ── Project scope ───────────────────────────────────────── */
448
+ .scope-section {
449
+ display: flex;
450
+ flex-direction: column;
451
+ gap: 6px;
452
+ }
453
+ .scope-label {
454
+ font-size: 11px;
455
+ font-weight: 700;
456
+ color: var(--ink-4);
457
+ text-transform: uppercase;
458
+ letter-spacing: 0.07em;
459
+ margin-bottom: 4px;
460
+ }
461
+ .scope-item {
462
+ font-size: 13px;
463
+ color: var(--ink-2);
464
+ padding: 9px 12px;
465
+ border-radius: 8px;
466
+ border: 1px solid var(--border);
467
+ background: var(--panel);
468
+ line-height: 1.5;
469
+ }
470
+
471
+ /* ── Feedback ─────────────────────────────────────────────── */
472
+ .feedback-card {
473
+ padding: 14px 16px;
474
+ border-radius: 8px;
475
+ border: 1px solid var(--border);
476
+ background: var(--panel);
477
+ font-size: 13px;
478
+ color: var(--ink-2);
479
+ line-height: 1.5;
480
+ }
481
+ .feedback-card + .feedback-card { margin-top: 8px; }
482
+ .goal-row {
483
+ display: flex;
484
+ align-items: center;
485
+ justify-content: space-between;
486
+ gap: 12px;
487
+ padding: 10px 14px;
488
+ border-radius: 8px;
489
+ border: 1px solid var(--border);
490
+ background: var(--panel);
491
+ font-size: 13px;
492
+ }
493
+ .goal-row + .goal-row { margin-top: 8px; }
494
+
495
+ /* ── KPI cards ───────────────────────────────────────────── */
496
+ .kpi-card {
497
+ padding: 20px;
498
+ border-radius: var(--radius-sm);
499
+ border: 1px solid var(--border);
500
+ background: var(--bg-start);
501
+ display: flex;
502
+ flex-direction: column;
503
+ gap: 6px;
504
+ }
505
+ .kpi-name {
506
+ font-size: 11px;
507
+ font-weight: 700;
508
+ color: var(--ink-4);
509
+ text-transform: uppercase;
510
+ letter-spacing: 0.07em;
511
+ }
512
+ .kpi-value {
513
+ font-size: 26px;
514
+ font-weight: 800;
515
+ color: var(--ink);
516
+ letter-spacing: -0.03em;
517
+ line-height: 1.1;
518
+ }
519
+ .kpi-target {
520
+ font-size: 12px;
521
+ color: var(--ink-3);
522
+ }
523
+ .kpi-trend {
524
+ font-size: 13px;
525
+ font-weight: 700;
526
+ display: flex;
527
+ align-items: center;
528
+ gap: 4px;
529
+ margin-top: 4px;
530
+ }
531
+ .trend-up { color: var(--success); }
532
+ .trend-down { color: var(--danger); }
533
+ .trend-flat { color: var(--ink-4); }
534
+
535
+ /* ── Idea cards ──────────────────────────────────────────── */
536
+ .idea-card {
537
+ padding: 16px 18px;
538
+ border-radius: var(--radius-sm);
539
+ border: 1px solid var(--border);
540
+ background: var(--panel);
541
+ display: flex;
542
+ flex-direction: column;
543
+ gap: 8px;
544
+ transition: box-shadow 0.15s, transform 0.15s;
545
+ }
546
+ .idea-card:hover {
547
+ box-shadow: var(--shadow-md);
548
+ transform: translateY(-1px);
549
+ }
550
+ .idea-header {
551
+ display: flex;
552
+ align-items: flex-start;
553
+ justify-content: space-between;
554
+ gap: 10px;
555
+ }
556
+ .idea-title {
557
+ font-size: 14px;
558
+ font-weight: 600;
559
+ color: var(--ink);
560
+ line-height: 1.4;
561
+ }
562
+ .idea-votes {
563
+ font-size: 13px;
564
+ font-weight: 700;
565
+ color: var(--brand);
566
+ background: var(--brand-light);
567
+ padding: 2px 9px;
568
+ border-radius: 20px;
569
+ white-space: nowrap;
570
+ flex-shrink: 0;
571
+ }
572
+ .idea-note {
573
+ font-size: 12px;
574
+ color: var(--ink-3);
575
+ line-height: 1.5;
576
+ }
577
+
578
+ /* ── Unresolved / Blockers ───────────────────────────────── */
579
+ .list-card-item {
580
+ display: flex;
581
+ align-items: flex-start;
582
+ justify-content: space-between;
583
+ gap: 12px;
584
+ padding: 13px 16px;
585
+ border-radius: 8px;
586
+ border: 1px solid var(--border);
587
+ background: var(--panel);
588
+ transition: box-shadow 0.1s;
589
+ }
590
+ .list-card-item:hover { box-shadow: var(--shadow); }
591
+ .list-card-item + .list-card-item { margin-top: 8px; }
592
+ .list-card-left {
593
+ display: flex;
594
+ flex-direction: column;
595
+ gap: 4px;
596
+ flex: 1;
597
+ min-width: 0;
598
+ }
599
+ .list-card-title {
600
+ font-size: 13px;
601
+ font-weight: 600;
602
+ color: var(--ink);
603
+ line-height: 1.45;
604
+ }
605
+ .list-card-meta {
606
+ font-size: 12px;
607
+ color: var(--ink-3);
608
+ }
609
+ .list-card-note {
610
+ font-size: 12px;
611
+ color: var(--ink-3);
612
+ margin-top: 2px;
613
+ line-height: 1.4;
614
+ }
615
+
616
+ /* ── Pipeline table ──────────────────────────────────────── */
617
+ .confidence-bar {
618
+ display: flex;
619
+ align-items: center;
620
+ gap: 6px;
621
+ }
622
+ .bar-bg {
623
+ width: 60px;
624
+ height: 5px;
625
+ border-radius: 3px;
626
+ background: var(--neutral-light);
627
+ overflow: hidden;
628
+ }
629
+ .bar-fill {
630
+ height: 100%;
631
+ border-radius: 3px;
632
+ }
633
+ .bar-fill.high { background: var(--success); width: 90%; }
634
+ .bar-fill.medium { background: var(--accent); width: 55%; }
635
+ .bar-fill.low { background: var(--danger); width: 25%; }
636
+
637
+ /* ── Profile cards ───────────────────────────────────────── */
638
+ .profile-card {
639
+ padding: 20px;
640
+ border-radius: var(--radius-sm);
641
+ border: 1px solid var(--border);
642
+ background: var(--panel);
643
+ display: flex;
644
+ flex-direction: column;
645
+ gap: 10px;
646
+ transition: box-shadow 0.15s;
647
+ }
648
+ .profile-card:hover { box-shadow: var(--shadow-md); }
649
+ .profile-header {
650
+ display: flex;
651
+ align-items: center;
652
+ justify-content: space-between;
653
+ gap: 10px;
654
+ }
655
+ .profile-name {
656
+ font-size: 15px;
657
+ font-weight: 700;
658
+ color: var(--ink);
659
+ }
660
+ .profile-type {
661
+ font-size: 11px;
662
+ color: var(--ink-3);
663
+ font-weight: 500;
664
+ }
665
+ .profile-summary {
666
+ font-size: 13px;
667
+ color: var(--ink-2);
668
+ line-height: 1.55;
669
+ }
670
+ .profile-footer {
671
+ display: flex;
672
+ align-items: center;
673
+ justify-content: space-between;
674
+ gap: 8px;
675
+ padding-top: 8px;
676
+ border-top: 1px solid var(--border);
677
+ }
678
+ .profile-owner {
679
+ font-size: 12px;
680
+ color: var(--ink-3);
681
+ }
682
+
683
+ /* ── Follow-up ───────────────────────────────────────────── */
684
+ .followup-wrap {
685
+ border-radius: var(--radius-sm);
686
+ border: 1px solid var(--border);
687
+ overflow: hidden;
688
+ background: var(--panel);
689
+ }
690
+ .followup-header {
691
+ padding: 12px 16px;
692
+ background: rgba(15,118,110,0.06);
693
+ border-bottom: 1px solid var(--border);
694
+ display: flex;
695
+ align-items: center;
696
+ gap: 8px;
697
+ }
698
+ .followup-header-label {
699
+ font-size: 11px;
700
+ font-weight: 700;
701
+ color: var(--brand-text);
702
+ text-transform: uppercase;
703
+ letter-spacing: 0.06em;
704
+ }
705
+ .followup-header-value {
706
+ font-size: 13px;
707
+ font-weight: 600;
708
+ color: var(--ink);
709
+ }
710
+ .followup-subject {
711
+ padding: 14px 16px;
712
+ border-bottom: 1px solid var(--border);
713
+ }
714
+ .followup-subject-label {
715
+ font-size: 11px;
716
+ font-weight: 700;
717
+ color: var(--ink-4);
718
+ text-transform: uppercase;
719
+ letter-spacing: 0.07em;
720
+ margin-bottom: 4px;
721
+ }
722
+ .followup-subject-text {
723
+ font-size: 14px;
724
+ font-weight: 600;
725
+ color: var(--ink);
726
+ }
727
+ .followup-body {
728
+ padding: 16px;
729
+ font-size: 13px;
730
+ color: var(--ink-2);
731
+ line-height: 1.7;
732
+ white-space: pre-wrap;
733
+ }
734
+
735
+ /* ── Footer ──────────────────────────────────────────────── */
736
+ .footer {
737
+ margin-top: 60px;
738
+ text-align: center;
739
+ font-size: 12px;
740
+ color: var(--ink-4);
741
+ letter-spacing: 0.01em;
742
+ }
743
+ .footer a {
744
+ color: var(--brand);
745
+ text-decoration: none;
746
+ font-weight: 500;
747
+ }
748
+
749
+ /* ── Animations ──────────────────────────────────────────── */
750
+ @keyframes fadeUp {
751
+ from { opacity: 0; transform: translateY(14px); }
752
+ to { opacity: 1; transform: translateY(0); }
753
+ }
754
+ .section-wrapper {
755
+ animation: fadeUp 0.35s ease both;
756
+ }
757
+ .section-wrapper:nth-child(1) { animation-delay: 0.05s; }
758
+ .section-wrapper:nth-child(2) { animation-delay: 0.10s; }
759
+ .section-wrapper:nth-child(3) { animation-delay: 0.15s; }
760
+ .section-wrapper:nth-child(4) { animation-delay: 0.20s; }
761
+ .section-wrapper:nth-child(5) { animation-delay: 0.25s; }
762
+ .section-wrapper:nth-child(6) { animation-delay: 0.30s; }
763
+ .section-wrapper:nth-child(7) { animation-delay: 0.35s; }
764
+ .section-wrapper:nth-child(8) { animation-delay: 0.40s; }
765
+ .section-wrapper:nth-child(9) { animation-delay: 0.45s; }
766
+ .section-wrapper:nth-child(10) { animation-delay: 0.50s; }
767
+
768
+ /* ── Print ───────────────────────────────────────────────── */
769
+ @media print {
770
+ body { background: #fff; }
771
+ .page { max-width: 100%; padding: 20px; }
772
+ .panel, .panel-sm { box-shadow: none; border-color: #ccc; }
773
+ .section-wrapper { animation: none !important; }
774
+ .hero::before { display: none; }
775
+ .signal-item, .decision-card, .kpi-card, .idea-card,
776
+ .list-card-item, .profile-card { break-inside: avoid; }
777
+ .table-wrap { overflow: visible; }
778
+ @page { margin: 20mm; }
779
+ }
780
+
781
+ /* ── Divider ─────────────────────────────────────────────── */
782
+ .divider {
783
+ height: 1px;
784
+ background: var(--border);
785
+ margin: 28px 0;
786
+ }
787
+
788
+ /* ── Sub-label in panels ─────────────────────────────────── */
789
+ .sub-label {
790
+ font-size: 11px;
791
+ font-weight: 700;
792
+ color: var(--ink-4);
793
+ text-transform: uppercase;
794
+ letter-spacing: 0.07em;
795
+ margin-bottom: 10px;
796
+ }
797
+ </style>
798
+ </head>
799
+ <body>
800
+
801
+ <!-- ╔══════════════════════════════════════════════════════════╗
802
+ ║ MEETING DATA — replace only this JSON block ║
803
+ ║ IMPORTANT: escape </ as <\/ in all string values ║
804
+ ║ to prevent premature script block termination. ║
805
+ ╚══════════════════════════════════════════════════════════╝ -->
806
+ <script id="dashboard-data" type="application/json">
807
+ {
808
+ "meta": {
809
+ "title": "모바일 온보딩 개선 회의",
810
+ "subtitle": "활성화율 개선을 위한 실험안 정리",
811
+ "type": "team-sync",
812
+ "date": "2026-03-15",
813
+ "sources": ["onboarding-review.txt", "onboarding-summary.md"]
814
+ },
815
+ "summary": {
816
+ "conclusion": "신규 온보딩 플로우를 A/B 2가지 버전으로 실험",
817
+ "status": "planning",
818
+ "next_action": "QA 범위 확정 후 실험 시작"
819
+ },
820
+ "signals": [
821
+ { "title": "첫 화면 복잡성이 이탈의 주요 원인", "severity": "high", "evidence": "사용자 피드백과 회의 내 반복 발언 일치" },
822
+ { "title": "실험 자체에는 전원 합의", "severity": "low", "evidence": "팀 전체 동의" }
823
+ ],
824
+ "risks": [
825
+ { "title": "QA 일정 부족으로 실험 지연 가능", "severity": "high", "evidence": "QA 슬롯 확보가 선행 조건" }
826
+ ],
827
+ "timeline": [
828
+ { "date": "2026-03-15", "title": "온보딩 개선 회의", "type": "meeting", "note": "실험안 2개 도출" },
829
+ { "date": "2026-03-18", "title": "QA 범위 확정", "type": "milestone", "note": "테스트 범위 정리" },
830
+ { "date": "2026-03-22", "title": "실험 시작", "type": "target", "note": "A/B 테스트 배포" }
831
+ ],
832
+ "actions": [
833
+ { "title": "실험안 A/B 화면 확정", "owner": "디자이너", "due_date": "2026-03-17", "priority": "high", "status": "doing" },
834
+ { "title": "활성화 지표 정의", "owner": "PM", "due_date": "2026-03-18", "priority": "high", "status": "todo" },
835
+ { "title": "QA 범위 협의", "owner": "QA", "due_date": "2026-03-18", "priority": "medium", "status": "todo" }
836
+ ],
837
+ "decisions": [
838
+ { "topic": "실험 방식", "options": ["A/B 테스트", "단계적 롤아웃", "카나리 배포"], "chosen": "A/B 테스트", "rationale": "가장 빠르게 통계적 유의성 확보 가능" }
839
+ ],
840
+ "unresolved": [
841
+ { "title": "활성화 지표 범위 확정", "owner": "PM", "status": "pending" }
842
+ ],
843
+ "blockers": [
844
+ { "title": "QA 슬롯 미확보", "owner": "QA", "status": "active", "note": "테스트 범위 확인 필요" }
845
+ ]
846
+ }
847
+ </script>
848
+
849
+ <div class="page" id="app">
850
+ <!-- sections injected by JS -->
851
+ </div>
852
+
853
+ <script>
854
+ (function () {
855
+ /* ── helpers ──────────────────────────────────────────── */
856
+ const esc = s => String(s ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
857
+
858
+ const MEETING_TYPES = {
859
+ 'team-sync': '팀 회의',
860
+ 'one-on-one': '1:1 미팅',
861
+ 'client-call': '고객 미팅',
862
+ 'brainstorm': '브레인스토밍',
863
+ 'retrospective': '회고',
864
+ 'kickoff': '킥오프',
865
+ 'decision': '의사결정',
866
+ 'board': '경영회의',
867
+ 'sales': '영업/제휴',
868
+ 'workshop': '워크숍',
869
+ };
870
+
871
+ function typeLabel(t) { return MEETING_TYPES[t] || (t ? esc(t) : '회의'); }
872
+
873
+ function severityBadge(s) {
874
+ const map = { high: ['danger','높음'], medium: ['warn','중간'], low: ['brand','낮음'] };
875
+ const [cls, lbl] = map[s] || ['neutral', s];
876
+ return `<span class="badge ${cls}">${lbl}</span>`;
877
+ }
878
+ function priorityBadge(p) {
879
+ const map = { high: ['danger','높음'], medium: ['warn','중간'], low: ['brand','낮음'] };
880
+ const [cls, lbl] = map[p] || ['neutral', p];
881
+ return `<span class="badge ${cls}">${lbl}</span>`;
882
+ }
883
+ function statusBadge(s) {
884
+ const map = { todo: ['neutral','대기'], doing: ['brand','진행중'], done: ['success','완료'] };
885
+ const [cls, lbl] = map[s] || ['neutral', s];
886
+ return `<span class="badge ${cls}">${lbl}</span>`;
887
+ }
888
+ function trendIndicator(t) {
889
+ if (t === 'up') return `<span class="kpi-trend trend-up">↑ 상승</span>`;
890
+ if (t === 'down') return `<span class="kpi-trend trend-down">↓ 하락</span>`;
891
+ return `<span class="kpi-trend trend-flat">— 유지</span>`;
892
+ }
893
+
894
+ function isEmpty(v) {
895
+ if (v === null || v === undefined) return true;
896
+ if (Array.isArray(v)) return v.length === 0;
897
+ if (typeof v === 'object') return Object.keys(v).length === 0;
898
+ return false;
899
+ }
900
+
901
+ /* ── section factory ─────────────────────────────────── */
902
+ function section(icon, title, countVal, innerHtml, extraClass) {
903
+ const countBadge = countVal != null ? `<span class="section-count">${countVal}</span>` : '';
904
+ return `
905
+ <div class="section-wrapper${extraClass ? ' '+extraClass : ''}">
906
+ <div class="section-header">
907
+ <div class="section-icon">${icon}</div>
908
+ <span class="section-title">${title}</span>
909
+ ${countBadge}
910
+ </div>
911
+ ${innerHtml}
912
+ </div>`;
913
+ }
914
+
915
+ /* ── renderers ────────────────────────────────────────── */
916
+ function renderHero(meta) {
917
+ const sources = (meta.sources || []).map(s =>
918
+ `<span class="source-chip">📄 ${esc(s)}</span>`
919
+ ).join('');
920
+
921
+ return `
922
+ <div class="section-wrapper">
923
+ <div class="panel hero">
924
+ <div class="hero-eyebrow">
925
+ <span class="badge type">${typeLabel(meta.type)}</span>
926
+ </div>
927
+ <h1 class="hero-title">${esc(meta.title)}</h1>
928
+ ${meta.subtitle ? `<p class="hero-subtitle">${esc(meta.subtitle)}</p>` : ''}
929
+ <div class="hero-meta">
930
+ <span class="hero-date">📅 ${esc(meta.date)}</span>
931
+ ${sources ? `<div class="hero-sources">${sources}</div>` : ''}
932
+ </div>
933
+ </div>
934
+ </div>`;
935
+ }
936
+
937
+ function renderSummary(summary) {
938
+ return section('📋', '요약', null, `
939
+ <div class="grid-3">
940
+ <div class="panel-sm summary-card conclusion">
941
+ <div class="icon">💡</div>
942
+ <div class="summary-label">결론</div>
943
+ <div class="summary-value">${esc(summary.conclusion || '—')}</div>
944
+ </div>
945
+ <div class="panel-sm summary-card status">
946
+ <div class="icon">🔖</div>
947
+ <div class="summary-label">상태</div>
948
+ <div class="summary-value">${esc(summary.status || '—')}</div>
949
+ </div>
950
+ <div class="panel-sm summary-card next">
951
+ <div class="icon">⚡</div>
952
+ <div class="summary-label">다음 액션</div>
953
+ <div class="summary-value">${esc(summary.next_action || '—')}</div>
954
+ </div>
955
+ </div>`);
956
+ }
957
+
958
+ function renderSignalsRisks(signals, risks) {
959
+ if (isEmpty(signals) && isEmpty(risks)) return '';
960
+ const sigHtml = isEmpty(signals) ? '' : `
961
+ <div class="panel">
962
+ <div class="sub-label">🔍 신호 · ${signals.length}건</div>
963
+ <div class="signal-list">
964
+ ${signals.map(s => `
965
+ <div class="signal-item">
966
+ <div class="signal-header">
967
+ <span class="signal-title">${esc(s.title)}</span>
968
+ ${severityBadge(s.severity)}
969
+ </div>
970
+ ${s.evidence ? `<div class="signal-evidence">${esc(s.evidence)}</div>` : ''}
971
+ </div>`).join('')}
972
+ </div>
973
+ </div>`;
974
+
975
+ const riskHtml = isEmpty(risks) ? '' : `
976
+ <div class="panel">
977
+ <div class="sub-label">⚠️ 리스크 · ${risks.length}건</div>
978
+ <div class="signal-list">
979
+ ${risks.map(r => `
980
+ <div class="signal-item">
981
+ <div class="signal-header">
982
+ <span class="signal-title">${esc(r.title)}</span>
983
+ ${severityBadge(r.severity)}
984
+ </div>
985
+ ${r.evidence ? `<div class="signal-evidence">${esc(r.evidence)}</div>` : ''}
986
+ </div>`).join('')}
987
+ </div>
988
+ </div>`;
989
+
990
+ const inner = sigHtml && riskHtml
991
+ ? `<div class="grid-2">${sigHtml}${riskHtml}</div>`
992
+ : sigHtml || riskHtml;
993
+
994
+ return section('🎯', '신호 &amp; 리스크', (signals.length||0)+(risks.length||0), inner);
995
+ }
996
+
997
+ function renderTimeline(timeline) {
998
+ if (isEmpty(timeline)) return '';
999
+ const TYPE_DOT = { meeting:'dot-meeting', milestone:'dot-milestone', target:'dot-target' };
1000
+ const TYPE_KR = { meeting:'회의', milestone:'마일스톤', target:'목표일' };
1001
+ return section('📅', '타임라인', timeline.length, `
1002
+ <div class="table-wrap">
1003
+ <table>
1004
+ <thead>
1005
+ <tr>
1006
+ <th>날짜</th>
1007
+ <th>이벤트</th>
1008
+ <th>유형</th>
1009
+ <th>비고</th>
1010
+ </tr>
1011
+ </thead>
1012
+ <tbody>
1013
+ ${timeline.map(t => `
1014
+ <tr>
1015
+ <td class="td-muted" style="white-space:nowrap">${esc(t.date)}</td>
1016
+ <td class="td-primary">${esc(t.title)}</td>
1017
+ <td>
1018
+ <span class="td-type-dot">
1019
+ <span class="dot ${TYPE_DOT[t.type]||'dot-default'}"></span>
1020
+ ${TYPE_KR[t.type] || esc(t.type)}
1021
+ </span>
1022
+ </td>
1023
+ <td class="td-muted">${esc(t.note||'')}</td>
1024
+ </tr>`).join('')}
1025
+ </tbody>
1026
+ </table>
1027
+ </div>`);
1028
+ }
1029
+
1030
+ function renderActions(actions) {
1031
+ if (isEmpty(actions)) return '';
1032
+ return section('✅', '액션 아이템', actions.length, `
1033
+ <div class="table-wrap">
1034
+ <table>
1035
+ <thead>
1036
+ <tr>
1037
+ <th>액션</th>
1038
+ <th>담당자</th>
1039
+ <th>기한</th>
1040
+ <th>우선순위</th>
1041
+ <th>상태</th>
1042
+ </tr>
1043
+ </thead>
1044
+ <tbody>
1045
+ ${actions.map(a => `
1046
+ <tr>
1047
+ <td class="td-primary">${esc(a.title)}</td>
1048
+ <td class="td-muted">${esc(a.owner||'—')}</td>
1049
+ <td class="td-muted" style="white-space:nowrap">${esc(a.due_date||'—')}</td>
1050
+ <td>${priorityBadge(a.priority)}</td>
1051
+ <td>${statusBadge(a.status)}</td>
1052
+ </tr>`).join('')}
1053
+ </tbody>
1054
+ </table>
1055
+ </div>`);
1056
+ }
1057
+
1058
+ function renderDecisions(decisions) {
1059
+ if (isEmpty(decisions)) return '';
1060
+ return section('⚖️', '결정 사항', decisions.length, `
1061
+ <div style="display:flex;flex-direction:column;gap:12px;">
1062
+ ${decisions.map(d => `
1063
+ <div class="decision-card">
1064
+ <div class="decision-topic">${esc(d.topic)}</div>
1065
+ <div class="decision-options">
1066
+ ${(d.options||[]).map(o => `
1067
+ <span class="option-chip${o === d.chosen ? ' chosen' : ''}">
1068
+ ${o === d.chosen ? '✓ ' : ''}${esc(o)}
1069
+ </span>`).join('')}
1070
+ </div>
1071
+ ${d.rationale ? `<div class="decision-rationale">${esc(d.rationale)}</div>` : ''}
1072
+ </div>`).join('')}
1073
+ </div>`);
1074
+ }
1075
+
1076
+ function renderPipeline(pipeline) {
1077
+ if (isEmpty(pipeline)) return '';
1078
+ return section('📊', '파이프라인', pipeline.length, `
1079
+ <div class="table-wrap">
1080
+ <table>
1081
+ <thead>
1082
+ <tr>
1083
+ <th>기회</th>
1084
+ <th>단계</th>
1085
+ <th>신뢰도</th>
1086
+ <th>규모</th>
1087
+ <th>주요 이슈</th>
1088
+ <th>담당자</th>
1089
+ </tr>
1090
+ </thead>
1091
+ <tbody>
1092
+ ${pipeline.map(p => `
1093
+ <tr>
1094
+ <td class="td-primary">${esc(p.label||p.id||'—')}</td>
1095
+ <td>${esc(p.stage||'—')}</td>
1096
+ <td>
1097
+ <div class="confidence-bar">
1098
+ <div class="bar-bg"><div class="bar-fill ${p.confidence||'medium'}"></div></div>
1099
+ <span class="td-muted">${p.confidence==='high'?'높음':p.confidence==='low'?'낮음':'보통'}</span>
1100
+ </div>
1101
+ </td>
1102
+ <td class="td-muted">${esc(p.volume||'—')}</td>
1103
+ <td class="td-muted">${esc(p.key_issue||'—')}</td>
1104
+ <td class="td-muted">${esc(p.owner||'—')}</td>
1105
+ </tr>`).join('')}
1106
+ </tbody>
1107
+ </table>
1108
+ </div>`);
1109
+ }
1110
+
1111
+ function renderProfiles(profiles) {
1112
+ if (isEmpty(profiles)) return '';
1113
+ return section('👤', '프로필', profiles.length, `
1114
+ <div class="grid-3">
1115
+ ${profiles.map(p => `
1116
+ <div class="profile-card">
1117
+ <div class="profile-header">
1118
+ <div>
1119
+ <div class="profile-name">${esc(p.name||p.id||'—')}</div>
1120
+ <div class="profile-type">${esc(p.type||'')}</div>
1121
+ </div>
1122
+ ${statusBadge(p.status||'todo')}
1123
+ </div>
1124
+ ${p.summary ? `<div class="profile-summary">${esc(p.summary)}</div>` : ''}
1125
+ <div class="profile-footer">
1126
+ <span class="profile-owner">담당: ${esc(p.owner||'—')}</span>
1127
+ </div>
1128
+ </div>`).join('')}
1129
+ </div>`);
1130
+ }
1131
+
1132
+ function renderRetrospective(retro) {
1133
+ if (isEmpty(retro)) return '';
1134
+ const listItems = arr => (arr||[]).map(i => `
1135
+ <div class="retro-item">${esc(i)}</div>`).join('');
1136
+ return section('🔄', '회고', null, `
1137
+ <div class="grid-3">
1138
+ <div class="panel retro-col retro-well">
1139
+ <div class="retro-label">잘된 점</div>
1140
+ ${listItems(retro.went_well)}
1141
+ </div>
1142
+ <div class="panel retro-col retro-improve">
1143
+ <div class="retro-label">개선할 점</div>
1144
+ ${listItems(retro.to_improve)}
1145
+ </div>
1146
+ <div class="panel retro-col retro-commit">
1147
+ <div class="retro-label">다짐 / 커밋</div>
1148
+ ${listItems(retro.commitments)}
1149
+ </div>
1150
+ </div>`);
1151
+ }
1152
+
1153
+ function renderProjectScope(scope) {
1154
+ if (isEmpty(scope)) return '';
1155
+ const listItems = (arr, label) => isEmpty(arr) ? '' : `
1156
+ <div class="scope-section">
1157
+ <div class="scope-label">${label}</div>
1158
+ ${arr.map(i => `<div class="scope-item">${esc(i)}</div>`).join('')}
1159
+ </div>`;
1160
+ return section('🚀', '프로젝트 범위', null, `
1161
+ <div class="panel" style="display:flex;flex-direction:column;gap:20px;">
1162
+ ${listItems(scope.goals, '목표')}
1163
+ ${scope.goals && scope.roles ? '<div class="divider"></div>' : ''}
1164
+ ${listItems(scope.roles, '역할')}
1165
+ ${(scope.goals||scope.roles) && scope.milestones ? '<div class="divider"></div>' : ''}
1166
+ ${listItems(scope.milestones, '마일스톤')}
1167
+ </div>`);
1168
+ }
1169
+
1170
+ function renderFeedback(feedback) {
1171
+ if (isEmpty(feedback)) return '';
1172
+ const cards = (arr, label) => isEmpty(arr) ? '' : `
1173
+ <div>
1174
+ <div class="sub-label">${label}</div>
1175
+ ${arr.map(f => `<div class="feedback-card">${esc(f)}</div>`).join('')}
1176
+ </div>`;
1177
+ const goalsHtml = isEmpty(feedback.goals_progress) ? '' : `
1178
+ <div>
1179
+ <div class="sub-label">목표 진행</div>
1180
+ ${feedback.goals_progress.map(g => `
1181
+ <div class="goal-row">
1182
+ <span>${esc(typeof g === 'object' ? g.goal||JSON.stringify(g) : g)}</span>
1183
+ ${typeof g === 'object' && g.status ? statusBadge(g.status) : ''}
1184
+ </div>`).join('')}
1185
+ </div>`;
1186
+ return section('💬', '피드백', null, `
1187
+ <div class="panel" style="display:flex;flex-direction:column;gap:20px;">
1188
+ ${cards(feedback.given, '전달한 피드백')}
1189
+ ${!isEmpty(feedback.given) && !isEmpty(feedback.received) ? '<div class="divider"></div>' : ''}
1190
+ ${cards(feedback.received, '받은 피드백')}
1191
+ ${(!isEmpty(feedback.given)||!isEmpty(feedback.received)) && !isEmpty(feedback.goals_progress) ? '<div class="divider"></div>' : ''}
1192
+ ${goalsHtml}
1193
+ </div>`);
1194
+ }
1195
+
1196
+ function renderKpis(kpis) {
1197
+ if (isEmpty(kpis)) return '';
1198
+ return section('📈', 'KPI', kpis.length, `
1199
+ <div class="grid-3">
1200
+ ${kpis.map(k => `
1201
+ <div class="kpi-card">
1202
+ <div class="kpi-name">${esc(k.name)}</div>
1203
+ <div class="kpi-value">${esc(k.value)}</div>
1204
+ ${k.target ? `<div class="kpi-target">목표: ${esc(k.target)}</div>` : ''}
1205
+ ${trendIndicator(k.trend)}
1206
+ </div>`).join('')}
1207
+ </div>`);
1208
+ }
1209
+
1210
+ function renderIdeas(ideas) {
1211
+ if (isEmpty(ideas)) return '';
1212
+ const sorted = [...ideas].sort((a,b) => (b.votes||0) - (a.votes||0));
1213
+ return section('💡', '아이디어', ideas.length, `
1214
+ <div class="grid-3">
1215
+ ${sorted.map(i => `
1216
+ <div class="idea-card">
1217
+ <div class="idea-header">
1218
+ <div class="idea-title">${esc(i.title)}</div>
1219
+ ${i.votes != null ? `<span class="idea-votes">👍 ${i.votes}</span>` : ''}
1220
+ </div>
1221
+ ${i.category ? `${priorityBadge('low').replace('낮음', esc(i.category))}` : ''}
1222
+ ${i.note ? `<div class="idea-note">${esc(i.note)}</div>` : ''}
1223
+ </div>`).join('')}
1224
+ </div>`);
1225
+ }
1226
+
1227
+ function renderUnresolved(unresolved) {
1228
+ if (isEmpty(unresolved)) return '';
1229
+ return section('❓', '미결 사항', unresolved.length, `
1230
+ <div style="display:flex;flex-direction:column;">
1231
+ ${unresolved.map(u => `
1232
+ <div class="list-card-item">
1233
+ <div class="list-card-left">
1234
+ <div class="list-card-title">${esc(u.title)}</div>
1235
+ ${u.owner ? `<div class="list-card-meta">담당: ${esc(u.owner)}</div>` : ''}
1236
+ </div>
1237
+ <span class="badge neutral">${esc(u.status||'미결')}</span>
1238
+ </div>`).join('')}
1239
+ </div>`);
1240
+ }
1241
+
1242
+ function renderBlockers(blockers) {
1243
+ if (isEmpty(blockers)) return '';
1244
+ return section('🚧', '블로커', blockers.length, `
1245
+ <div style="display:flex;flex-direction:column;">
1246
+ ${blockers.map(b => `
1247
+ <div class="list-card-item">
1248
+ <div class="list-card-left">
1249
+ <div class="list-card-title">${esc(b.title)}</div>
1250
+ ${b.owner ? `<div class="list-card-meta">담당: ${esc(b.owner)}</div>` : ''}
1251
+ ${b.note ? `<div class="list-card-note">${esc(b.note)}</div>` : ''}
1252
+ </div>
1253
+ <span class="badge ${b.status==='active'?'danger':'warn'}">${
1254
+ b.status==='active'?'활성':'해결중'
1255
+ }</span>
1256
+ </div>`).join('')}
1257
+ </div>`);
1258
+ }
1259
+
1260
+ function renderFollowUp(followUp) {
1261
+ if (isEmpty(followUp)) return '';
1262
+ return section('📨', '후속 메시지', null, `
1263
+ <div class="followup-wrap">
1264
+ <div class="followup-header">
1265
+ <span class="followup-header-label">채널</span>
1266
+ <span class="followup-header-value">${esc(followUp.channel||'—')}</span>
1267
+ </div>
1268
+ <div class="followup-subject">
1269
+ <div class="followup-subject-label">제목</div>
1270
+ <div class="followup-subject-text">${esc(followUp.subject||'—')}</div>
1271
+ </div>
1272
+ <div class="followup-body">${esc(followUp.body||'')}</div>
1273
+ </div>`);
1274
+ }
1275
+
1276
+ /* ── main render ─────────────────────────────────────── */
1277
+ function render() {
1278
+ const raw = document.getElementById('dashboard-data');
1279
+ if (!raw) { document.getElementById('app').innerHTML = '<p style="padding:40px;color:#b91c1c;">⚠ dashboard-data 스크립트 블록을 찾을 수 없습니다.</p>'; return; }
1280
+
1281
+ let data;
1282
+ try { data = JSON.parse(raw.textContent); }
1283
+ catch (e) { document.getElementById('app').innerHTML = `<p style="padding:40px;color:#b91c1c;">⚠ JSON 파싱 오류: ${esc(e.message)}</p>`; return; }
1284
+
1285
+ const { meta = {}, summary = {}, signals = [], risks = [], timeline = [], actions = [],
1286
+ decisions = [], pipeline = [], profiles = [], retrospective = {},
1287
+ project_scope = {}, feedback = {}, kpis = [], ideas = [],
1288
+ unresolved = [], blockers = [], follow_up = {} } = data;
1289
+
1290
+ const parts = [
1291
+ renderHero(meta),
1292
+ renderSummary(summary),
1293
+ renderSignalsRisks(signals, risks),
1294
+ renderTimeline(timeline),
1295
+ renderActions(actions),
1296
+ renderDecisions(decisions),
1297
+ renderPipeline(pipeline),
1298
+ renderProfiles(profiles),
1299
+ renderRetrospective(retrospective),
1300
+ renderProjectScope(project_scope),
1301
+ renderFeedback(feedback),
1302
+ renderKpis(kpis),
1303
+ renderIdeas(ideas),
1304
+ renderUnresolved(unresolved),
1305
+ renderBlockers(blockers),
1306
+ renderFollowUp(follow_up),
1307
+ `<div class="footer">Mintly으로 생성 · ${esc(meta.date||'')}</div>`,
1308
+ ].filter(Boolean);
1309
+
1310
+ document.getElementById('app').innerHTML = parts.join('\n');
1311
+ }
1312
+
1313
+ if (document.readyState === 'loading') {
1314
+ document.addEventListener('DOMContentLoaded', render);
1315
+ } else {
1316
+ render();
1317
+ }
1318
+ })();
1319
+ </script>
1320
+
1321
+ </body>
1322
+ </html>