@leviosa-ai/detail-page-editor 0.2.2 → 0.3.1

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.
Files changed (60) hide show
  1. package/components/cardnews/color-input.tsx +9 -9
  2. package/components/detail-page/ai-generate-panel.tsx +37 -37
  3. package/components/detail-page/annotation-canvas.tsx +9 -9
  4. package/components/detail-page/annotation-dialog.tsx +18 -18
  5. package/components/detail-page/canvas-context-menu.tsx +4 -4
  6. package/components/detail-page/chart-inspector.tsx +23 -23
  7. package/components/detail-page/design-reference-picker.tsx +22 -22
  8. package/components/detail-page/detail-page-basic-shapes.tsx +4 -4
  9. package/components/detail-page/detail-page-brand-gifs-panel.tsx +10 -10
  10. package/components/detail-page/detail-page-brand-kit-panel.tsx +5 -5
  11. package/components/detail-page/detail-page-brand-panel-header.tsx +7 -4
  12. package/components/detail-page/detail-page-charts-panel.tsx +4 -4
  13. package/components/detail-page/detail-page-decorations-panel.tsx +5 -5
  14. package/components/detail-page/detail-page-download-dialog.tsx +19 -19
  15. package/components/detail-page/detail-page-editor.tsx +131 -76
  16. package/components/detail-page/detail-page-elements-panel.tsx +4 -4
  17. package/components/detail-page/detail-page-font-picker.tsx +14 -14
  18. package/components/detail-page/detail-page-history-buttons.tsx +3 -3
  19. package/components/detail-page/detail-page-host-context.tsx +63 -0
  20. package/components/detail-page/detail-page-icons-panel.tsx +13 -13
  21. package/components/detail-page/detail-page-konva-json-preview.tsx +9 -9
  22. package/components/detail-page/detail-page-layers-panel.tsx +14 -14
  23. package/components/detail-page/detail-page-my-images-panel.tsx +29 -14
  24. package/components/detail-page/detail-page-my-shapes-panel.tsx +8 -8
  25. package/components/detail-page/detail-page-page-toolbar.tsx +10 -10
  26. package/components/detail-page/detail-page-pages-panel.tsx +13 -13
  27. package/components/detail-page/detail-page-pages-timeline.tsx +4 -4
  28. package/components/detail-page/detail-page-photos-panel.tsx +13 -13
  29. package/components/detail-page/detail-page-properties-panel.tsx +55 -55
  30. package/components/detail-page/detail-page-qr-panel.tsx +13 -13
  31. package/components/detail-page/detail-page-references-panel.tsx +9 -9
  32. package/components/detail-page/detail-page-sidebar-sections.tsx +1 -1
  33. package/components/detail-page/detail-page-tables-panel.tsx +4 -4
  34. package/components/detail-page/detail-page-text-panel.tsx +4 -4
  35. package/components/detail-page/edit-quota-ui.tsx +3 -3
  36. package/components/detail-page/element-recents-strip.tsx +6 -6
  37. package/components/detail-page/fill-control.tsx +12 -12
  38. package/components/detail-page/find-replace-panel.tsx +8 -8
  39. package/components/detail-page/generation-progress-fill.tsx +4 -4
  40. package/components/detail-page/gif-effect-picker.tsx +9 -9
  41. package/components/detail-page/group-prompt-edit-panel.tsx +3 -3
  42. package/components/detail-page/inspector-controls.tsx +9 -9
  43. package/components/detail-page/panel-search-input.tsx +2 -2
  44. package/components/detail-page/prompt-edit-panel.tsx +3 -3
  45. package/components/detail-page/section-reauthor-controller.tsx +2 -2
  46. package/components/detail-page/spec-prompt-edit-panel.tsx +3 -3
  47. package/components/detail-page/svg-prompt-edit-panel.tsx +3 -3
  48. package/components/detail-page/table-inspector.tsx +24 -24
  49. package/components/ui/button.tsx +6 -6
  50. package/components/ui/command.tsx +5 -5
  51. package/components/ui/dialog.tsx +3 -3
  52. package/components/ui/label.tsx +1 -1
  53. package/components/ui/select.tsx +3 -3
  54. package/components/ui/slider.tsx +1 -1
  55. package/components/ui/tooltip.tsx +1 -1
  56. package/lib/detail-page/use-progressive-reveal.ts +82 -0
  57. package/lib/detail-page-canvas/konva-fallback.ts +10 -4
  58. package/package.json +4 -1
  59. package/styles/canvas-bridge.css +35 -0
  60. package/styles/tokens.css +103 -0
@@ -80,7 +80,7 @@ function Cell({
80
80
  (e.target as HTMLInputElement).blur();
81
81
  }
82
82
  }}
83
- className={`w-full min-w-0 rounded-md border border-neutral-200 bg-white px-2 py-1.5 text-sm text-neutral-900 outline-none focus:border-neutral-400 ${
83
+ className={`w-full min-w-0 rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface px-2 py-1.5 text-sm text-dpe-ink-900 outline-none focus:border-dpe-ink-400 ${
84
84
  align === "right" ? "text-right tabular-nums" : ""
85
85
  }`}
86
86
  />
@@ -220,10 +220,10 @@ export const ChartInspector = observer(function ChartInspector({
220
220
  type="button"
221
221
  aria-pressed={kind === spec.kind}
222
222
  onClick={() => apply({ kind })}
223
- className={`h-8 rounded-md border text-xs transition-colors ${
223
+ className={`h-8 rounded-dpe-md border text-xs transition-colors ${
224
224
  kind === spec.kind
225
- ? "border-neutral-300 bg-neutral-100 font-semibold text-neutral-900"
226
- : "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
225
+ ? "border-dpe-ink-300 bg-dpe-ink-100 font-dpe-semibold text-dpe-ink-900"
226
+ : "border-dpe-ink-200 bg-dpe-surface text-dpe-ink-600 hover:bg-dpe-ink-50"
227
227
  }`}
228
228
  >
229
229
  {t(`detailPage.chart.kinds.${kind}`)}
@@ -231,7 +231,7 @@ export const ChartInspector = observer(function ChartInspector({
231
231
  ))}
232
232
  </div>
233
233
  {resolved.hiddenSeries > 0 ? (
234
- <p className="mt-2 text-[11px] text-amber-600">
234
+ <p className="mt-2 text-[11px] text-dpe-warn-600">
235
235
  {t("detailPage.chart.hiddenSeries", { count: resolved.hiddenSeries })}
236
236
  </p>
237
237
  ) : null}
@@ -239,7 +239,7 @@ export const ChartInspector = observer(function ChartInspector({
239
239
 
240
240
  <Section title={t("detailPage.chart.data")}>
241
241
  <div onPaste={onPaste}>
242
- <div className="flex items-center gap-1.5 pb-1 text-[11px] text-neutral-400">
242
+ <div className="flex items-center gap-1.5 pb-1 text-[11px] text-dpe-ink-400">
243
243
  <span className="flex-1">{t("detailPage.chart.columnLabel")}</span>
244
244
  {spec.data.series.map((series) => (
245
245
  <span key={series.name} className="w-20 text-right">
@@ -296,7 +296,7 @@ export const ChartInspector = observer(function ChartInspector({
296
296
  },
297
297
  })
298
298
  }
299
- className="flex h-8 w-6 shrink-0 items-center justify-center rounded-md text-neutral-400 hover:text-red-500 disabled:cursor-not-allowed disabled:opacity-30"
299
+ className="flex h-8 w-6 shrink-0 items-center justify-center rounded-dpe-md text-dpe-ink-400 hover:text-dpe-danger-500 disabled:cursor-not-allowed disabled:opacity-30"
300
300
  >
301
301
  <Trash2 size={14} />
302
302
  </button>
@@ -306,12 +306,12 @@ export const ChartInspector = observer(function ChartInspector({
306
306
  <button
307
307
  type="button"
308
308
  onClick={() => setData(withRowAdded(spec.data))}
309
- className="mt-2 inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-md border border-neutral-200 bg-white text-xs font-medium text-neutral-600 hover:bg-neutral-50"
309
+ className="mt-2 inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface text-xs font-dpe-medium text-dpe-ink-600 hover:bg-dpe-ink-50"
310
310
  >
311
311
  <Plus size={14} />
312
312
  {t("detailPage.chart.addRow")}
313
313
  </button>
314
- <p className="mt-1.5 text-[11px] text-neutral-400">
314
+ <p className="mt-1.5 text-[11px] text-dpe-ink-400">
315
315
  {notice ?? t("detailPage.chart.pasteHint")}
316
316
  </p>
317
317
  </div>
@@ -320,7 +320,7 @@ export const ChartInspector = observer(function ChartInspector({
320
320
  <Section title={t("detailPage.chart.display")}>
321
321
  <div className="grid grid-cols-2 gap-2">
322
322
  <label className="flex items-center gap-2">
323
- <span className="w-10 shrink-0 text-xs text-neutral-500">
323
+ <span className="w-10 shrink-0 text-xs text-dpe-ink-500">
324
324
  {t("detailPage.chart.unit")}
325
325
  </span>
326
326
  <Cell
@@ -331,7 +331,7 @@ export const ChartInspector = observer(function ChartInspector({
331
331
  />
332
332
  </label>
333
333
  <label className="flex items-center gap-2">
334
- <span className="w-10 shrink-0 text-xs text-neutral-500">
334
+ <span className="w-10 shrink-0 text-xs text-dpe-ink-500">
335
335
  {t("detailPage.chart.decimals")}
336
336
  </span>
337
337
  <NumberField
@@ -375,14 +375,14 @@ export const ChartInspector = observer(function ChartInspector({
375
375
 
376
376
  <div className="mt-2 grid grid-cols-2 gap-2">
377
377
  <label className="flex items-center gap-2">
378
- <span className="w-10 shrink-0 text-xs text-neutral-500">
378
+ <span className="w-10 shrink-0 text-xs text-dpe-ink-500">
379
379
  {t("detailPage.chart.max")}
380
380
  </span>
381
381
  {spec.options.max === "auto" ? (
382
382
  <button
383
383
  type="button"
384
384
  onClick={() => setOptions({ max: Math.round(resolved.max) })}
385
- className="h-8 min-w-0 flex-1 rounded-md border border-neutral-200 bg-white text-xs text-neutral-500 hover:bg-neutral-50"
385
+ className="h-8 min-w-0 flex-1 rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface text-xs text-dpe-ink-500 hover:bg-dpe-ink-50"
386
386
  >
387
387
  {t("detailPage.chart.maxAuto")}
388
388
  </button>
@@ -398,7 +398,7 @@ export const ChartInspector = observer(function ChartInspector({
398
398
  <button
399
399
  type="button"
400
400
  onClick={() => setOptions({ max: "auto" })}
401
- className="h-8 rounded-md border border-neutral-200 bg-white text-xs text-neutral-500 hover:bg-neutral-50"
401
+ className="h-8 rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface text-xs text-dpe-ink-500 hover:bg-dpe-ink-50"
402
402
  >
403
403
  {t("detailPage.chart.maxToAuto")}
404
404
  </button>
@@ -406,7 +406,7 @@ export const ChartInspector = observer(function ChartInspector({
406
406
  </div>
407
407
 
408
408
  <label className="mt-2 flex items-center gap-2">
409
- <span className="w-10 shrink-0 text-xs text-neutral-500">
409
+ <span className="w-10 shrink-0 text-xs text-dpe-ink-500">
410
410
  {t("detailPage.chart.highlight")}
411
411
  </span>
412
412
  <select
@@ -418,7 +418,7 @@ export const ChartInspector = observer(function ChartInspector({
418
418
  event.target.value === "" ? null : Number(event.target.value),
419
419
  })
420
420
  }
421
- className="h-8 min-w-0 flex-1 rounded-md border border-neutral-200 bg-white px-2 text-xs text-neutral-700 outline-none focus:border-neutral-400"
421
+ className="h-8 min-w-0 flex-1 rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface px-2 text-xs text-dpe-ink-700 outline-none focus:border-dpe-ink-400"
422
422
  >
423
423
  <option value="">{t("detailPage.chart.highlightNone")}</option>
424
424
  {spec.data.labels.map((label, index) => (
@@ -437,7 +437,7 @@ export const ChartInspector = observer(function ChartInspector({
437
437
  <div className="flex flex-col gap-2">
438
438
  {slots.map((slot) => (
439
439
  <label key={slot.index} className="flex items-center gap-2">
440
- <span className="w-16 shrink-0 truncate text-xs text-neutral-500">
440
+ <span className="w-16 shrink-0 truncate text-xs text-dpe-ink-500">
441
441
  {scope === "highlight"
442
442
  ? t("detailPage.chart.color.accent")
443
443
  : slot.name ||
@@ -457,7 +457,7 @@ export const ChartInspector = observer(function ChartInspector({
457
457
  </label>
458
458
  ))}
459
459
  {scope !== "highlight" && slotsTruncated ? (
460
- <p className="text-[11px] leading-relaxed text-neutral-400">
460
+ <p className="text-[11px] leading-relaxed text-dpe-ink-400">
461
461
  {t("detailPage.chart.color.moreSlots", { count: MAX_PALETTE_SLOTS })}
462
462
  </p>
463
463
  ) : null}
@@ -471,7 +471,7 @@ export const ChartInspector = observer(function ChartInspector({
471
471
  ] as const
472
472
  ).map(([key, value]) => (
473
473
  <label key={key} className="flex items-center gap-2">
474
- <span className="w-16 shrink-0 text-xs text-neutral-500">
474
+ <span className="w-16 shrink-0 text-xs text-dpe-ink-500">
475
475
  {t(`detailPage.chart.color.${key}`)}
476
476
  </span>
477
477
  <ColorInput
@@ -491,7 +491,7 @@ export const ChartInspector = observer(function ChartInspector({
491
491
  <Section title={t("detailPage.chart.shape")}>
492
492
  <div className="grid grid-cols-2 gap-2">
493
493
  <label className="flex items-center gap-2">
494
- <span className="w-10 shrink-0 text-xs text-neutral-500">
494
+ <span className="w-10 shrink-0 text-xs text-dpe-ink-500">
495
495
  {t("detailPage.chart.barSize")}
496
496
  </span>
497
497
  <NumberField
@@ -501,7 +501,7 @@ export const ChartInspector = observer(function ChartInspector({
501
501
  />
502
502
  </label>
503
503
  <label className="flex items-center gap-2">
504
- <span className="w-10 shrink-0 text-xs text-neutral-500">
504
+ <span className="w-10 shrink-0 text-xs text-dpe-ink-500">
505
505
  {t("detailPage.chart.gap")}
506
506
  </span>
507
507
  <NumberField
@@ -517,12 +517,12 @@ export const ChartInspector = observer(function ChartInspector({
517
517
  <button
518
518
  type="button"
519
519
  onClick={() => detachChart(el)}
520
- className="inline-flex h-9 w-full items-center justify-center gap-2 rounded-md border border-neutral-200 bg-white text-sm font-semibold text-neutral-700 hover:bg-neutral-50"
520
+ className="inline-flex h-9 w-full items-center justify-center gap-2 rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface text-sm font-dpe-semibold text-dpe-ink-700 hover:bg-dpe-ink-50"
521
521
  >
522
522
  <Unlink aria-hidden="true" size={15} />
523
523
  {t("detailPage.chart.detach")}
524
524
  </button>
525
- <p className="mt-1.5 text-[11px] text-neutral-400">
525
+ <p className="mt-1.5 text-[11px] text-dpe-ink-400">
526
526
  {t("detailPage.chart.detachHint")}
527
527
  </p>
528
528
  </Section>
@@ -198,10 +198,10 @@ export function DesignReferencePicker({
198
198
  }, [api, busy, credits, instruction, items, onBriefChange]);
199
199
 
200
200
  return (
201
- <div className="space-y-3 rounded-lg bg-neutral-50 p-3">
201
+ <div className="space-y-3 rounded-dpe-lg bg-dpe-ink-50 p-3">
202
202
  <div>
203
- <p className="text-xs font-medium text-neutral-700">디자인 레퍼런스</p>
204
- <p className="mt-1 text-[11px] text-neutral-500">
203
+ <p className="text-xs font-dpe-medium text-dpe-ink-700">디자인 레퍼런스</p>
204
+ <p className="mt-1 text-[11px] text-dpe-ink-500">
205
205
  &ldquo;이런 디자인으로&rdquo; 참고할 그림이에요. 상품 사진과 달리 페이지에
206
206
  들어가지 않고, 구조와 색을 고르는 근거로만 써요. 최대{" "}
207
207
  {MAX_DESIGN_REFERENCES}장.
@@ -212,13 +212,13 @@ export function DesignReferencePicker({
212
212
  {items.map((item, index) => (
213
213
  <div
214
214
  key={item.id}
215
- className="flex items-start gap-2 rounded-lg border border-neutral-200 bg-white p-1.5"
215
+ className="flex items-start gap-2 rounded-dpe-lg border border-dpe-ink-200 bg-dpe-surface p-1.5"
216
216
  >
217
- <div className="relative h-14 w-14 shrink-0 overflow-hidden rounded-md bg-neutral-100">
217
+ <div className="relative h-14 w-14 shrink-0 overflow-hidden rounded-dpe-md bg-dpe-ink-100">
218
218
  {/* 붙인 사진은 data URI라 next/image 로 최적화할 것이 없다. */}
219
219
  {/* eslint-disable-next-line @next/next/no-img-element */}
220
220
  <img src={item.uri} alt="디자인 레퍼런스" className="h-full w-full object-cover" />
221
- <span className="absolute left-0.5 top-0.5 rounded bg-neutral-900/75 px-1 text-[10px] font-semibold leading-4 text-white">
221
+ <span className="absolute left-0.5 top-0.5 rounded bg-dpe-ink-900/75 px-1 text-[10px] font-dpe-semibold leading-4 text-dpe-on-accent">
222
222
  {referenceOrdinal(index)}
223
223
  </span>
224
224
  <button
@@ -226,7 +226,7 @@ export function DesignReferencePicker({
226
226
  disabled={disabled || busy}
227
227
  onClick={() => remove(item.id)}
228
228
  aria-label={`${referenceOrdinal(index)} 레퍼런스 빼기`}
229
- className="absolute right-0.5 top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-neutral-900/70 text-white disabled:opacity-40"
229
+ className="absolute right-0.5 top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-dpe-ink-900/70 text-dpe-on-accent disabled:opacity-40"
230
230
  >
231
231
  <X size={10} />
232
232
  </button>
@@ -245,8 +245,8 @@ export function DesignReferencePicker({
245
245
  onClick={() => toggleAspect(item.id, aspect.key)}
246
246
  className={`rounded-full border px-2 py-0.5 text-[11px] transition-colors disabled:opacity-40 ${
247
247
  on
248
- ? "border-neutral-900 bg-neutral-900 text-white"
249
- : "border-neutral-200 text-neutral-500 hover:border-neutral-400 hover:text-neutral-700"
248
+ ? "border-dpe-ink-900 bg-dpe-ink-900 text-dpe-on-accent"
249
+ : "border-dpe-ink-200 text-dpe-ink-500 hover:border-dpe-ink-400 hover:text-dpe-ink-700"
250
250
  }`}
251
251
  >
252
252
  {aspect.label}
@@ -261,7 +261,7 @@ export function DesignReferencePicker({
261
261
  type="button"
262
262
  disabled={disabled || busy}
263
263
  onClick={() => fileRef.current?.click()}
264
- className="flex h-[4.75rem] items-center gap-1.5 rounded-lg border border-dashed border-neutral-300 bg-white px-3 text-xs text-neutral-500 transition-colors hover:border-neutral-400 hover:text-neutral-700 disabled:opacity-40"
264
+ className="flex h-[4.75rem] items-center gap-1.5 rounded-dpe-lg border border-dashed border-dpe-ink-300 bg-dpe-surface px-3 text-xs text-dpe-ink-500 transition-colors hover:border-dpe-ink-400 hover:text-dpe-ink-700 disabled:opacity-40"
265
265
  >
266
266
  <ImagePlus size={14} />
267
267
  레퍼런스 추가
@@ -285,13 +285,13 @@ export function DesignReferencePicker({
285
285
  rows={2}
286
286
  disabled={disabled || busy}
287
287
  placeholder="1번 이미지의 색감과 폰트, 2번 이미지의 레이아웃을 참고해 주세요"
288
- className="w-full resize-none rounded-lg border border-neutral-200 bg-white px-3 py-2 text-xs text-neutral-900 outline-none placeholder:text-neutral-400 focus:border-neutral-400 disabled:bg-neutral-100"
288
+ className="w-full resize-none rounded-dpe-lg border border-dpe-ink-200 bg-dpe-surface px-3 py-2 text-xs text-dpe-ink-900 outline-none placeholder:text-dpe-ink-400 focus:border-dpe-ink-400 disabled:bg-dpe-ink-100"
289
289
  />
290
290
  <button
291
291
  type="button"
292
292
  disabled={disabled || busy}
293
293
  onClick={() => void analyze()}
294
- className="inline-flex items-center gap-1.5 rounded-lg border border-neutral-900 px-3 py-1.5 text-xs font-medium text-neutral-900 transition-colors hover:bg-neutral-900 hover:text-white disabled:opacity-40"
294
+ className="inline-flex items-center gap-1.5 rounded-dpe-lg border border-dpe-ink-900 px-3 py-1.5 text-xs font-dpe-medium text-dpe-ink-900 transition-colors hover:bg-dpe-ink-900 hover:text-dpe-on-accent disabled:opacity-40"
295
295
  >
296
296
  {busy ? <Loader2 size={13} className="animate-spin" /> : null}
297
297
  {busy ? "읽는 중…" : brief ? "다시 읽기" : "레퍼런스 읽기"}
@@ -299,7 +299,7 @@ export function DesignReferencePicker({
299
299
  {/* 누르기 전에 값을 알려 준다 — 누른 뒤 402 로 알게 되면 이미 축 고르고
300
300
  지시까지 다 적은 뒤다. 장수가 아니라 그림 크기에 붙는 값이라, 큰 그림을
301
301
  작은 것으로 바꾸면 줄어든다. */}
302
- <p className="text-[11px] text-neutral-500">
302
+ <p className="text-[11px] text-dpe-ink-500">
303
303
  읽을 때마다 {credits}크레딧이 들어요 (레퍼런스 {items.length}장 · 그림이 크고
304
304
  길수록 올라가요).
305
305
  </p>
@@ -307,7 +307,7 @@ export function DesignReferencePicker({
307
307
  ) : null}
308
308
 
309
309
  {brief ? <BriefSummary brief={brief} /> : null}
310
- {error ? <p className="text-[11px] text-red-600">{error}</p> : null}
310
+ {error ? <p className="text-[11px] text-dpe-danger-600">{error}</p> : null}
311
311
  </div>
312
312
  );
313
313
  }
@@ -339,22 +339,22 @@ function BriefSummary({ brief }: { brief: DetailPageDesignBrief }) {
339
339
 
340
340
  if (nothingRead) {
341
341
  return (
342
- <p className="text-[11px] text-neutral-500">
342
+ <p className="text-[11px] text-dpe-ink-500">
343
343
  레퍼런스에서 읽어낸 것이 없어요. 판면이 잘 보이는 그림으로 바꿔 보세요.
344
344
  </p>
345
345
  );
346
346
  }
347
347
 
348
348
  return (
349
- <div className="space-y-1.5 rounded-lg border border-neutral-200 bg-white p-2.5">
349
+ <div className="space-y-1.5 rounded-dpe-lg border border-dpe-ink-200 bg-dpe-surface p-2.5">
350
350
  <div className="flex flex-wrap items-center gap-1.5">
351
351
  {brief.tone ? (
352
- <span className="rounded-full bg-neutral-900 px-2 py-0.5 text-[11px] text-white">
352
+ <span className="rounded-full bg-dpe-ink-900 px-2 py-0.5 text-[11px] text-dpe-on-accent">
353
353
  {TONE_LABELS[brief.tone] ?? brief.tone}
354
354
  </span>
355
355
  ) : null}
356
356
  {brief.density ? (
357
- <span className="rounded-full border border-neutral-300 px-2 py-0.5 text-[11px] text-neutral-600">
357
+ <span className="rounded-full border border-dpe-ink-300 px-2 py-0.5 text-[11px] text-dpe-ink-600">
358
358
  {DENSITY_LABELS[brief.density] ?? brief.density}
359
359
  </span>
360
360
  ) : null}
@@ -363,19 +363,19 @@ function BriefSummary({ brief }: { brief: DetailPageDesignBrief }) {
363
363
  key={color}
364
364
  title={color}
365
365
  style={{ backgroundColor: color }}
366
- className="h-4 w-4 rounded-full border border-neutral-300"
366
+ className="h-4 w-4 rounded-full border border-dpe-ink-300"
367
367
  />
368
368
  ))}
369
369
  </div>
370
370
  {brief.summary ? (
371
- <p className="text-[11px] text-neutral-600">{brief.summary}</p>
371
+ <p className="text-[11px] text-dpe-ink-600">{brief.summary}</p>
372
372
  ) : null}
373
373
  {brief.typography || brief.layout || brief.content ? (
374
- <p className="text-[11px] text-neutral-500">
374
+ <p className="text-[11px] text-dpe-ink-500">
375
375
  {[brief.typography, brief.layout, brief.content].filter(Boolean).join(" · ")}
376
376
  </p>
377
377
  ) : null}
378
- <p className="text-[11px] text-neutral-400">
378
+ <p className="text-[11px] text-dpe-ink-400">
379
379
  읽은 결과는 제안이에요. 위 디자인 톤을 직접 바꾸면 그 선택이 우선해요.
380
380
  </p>
381
381
  </div>
@@ -65,7 +65,7 @@ const FIGURE_PATCH: Record<string, Record<string, unknown>> = {
65
65
  };
66
66
 
67
67
  const CELL =
68
- "flex aspect-square items-center justify-center rounded-lg border border-neutral-200 p-2 hover:border-neutral-400";
68
+ "flex aspect-square items-center justify-center rounded-dpe-lg border border-dpe-ink-200 p-2 hover:border-dpe-ink-400";
69
69
 
70
70
  function FigureCell({ id, store }: { id: string; store: unknown }) {
71
71
  const { t } = useTranslation("branding");
@@ -79,7 +79,7 @@ function FigureCell({ id, store }: { id: string; store: unknown }) {
79
79
  >
80
80
  <span
81
81
  aria-hidden="true"
82
- className="block h-7 w-7 bg-neutral-300"
82
+ className="block h-7 w-7 bg-dpe-ink-300"
83
83
  style={{
84
84
  borderRadius: id === "circle" ? "9999px" : id === "rounded" ? 8 : 0,
85
85
  }}
@@ -135,7 +135,7 @@ export function DetailPageBasicShapes({ store }: { store: unknown }) {
135
135
  <div className="min-h-0 flex-1 overflow-y-auto p-3">
136
136
  {found ? (
137
137
  found.figures.length + found.shapes.length === 0 ? (
138
- <div className="flex flex-col items-center gap-2 py-6 text-center text-neutral-400">
138
+ <div className="flex flex-col items-center gap-2 py-6 text-center text-dpe-ink-400">
139
139
  <ImageOff aria-hidden="true" size={20} />
140
140
  <p className="text-xs">{t("detailPage.shapes.searchEmpty")}</p>
141
141
  </div>
@@ -157,7 +157,7 @@ export function DetailPageBasicShapes({ store }: { store: unknown }) {
157
157
  if (!shapes.length) return null;
158
158
  return (
159
159
  <div key={category} className="mb-4 last:mb-0">
160
- <p className="mb-2 text-xs font-medium text-neutral-500">
160
+ <p className="mb-2 text-xs font-dpe-medium text-dpe-ink-500">
161
161
  {t(`detailPage.shapes.categories.${category}`)}
162
162
  </p>
163
163
  <div className="grid grid-cols-4 gap-2">
@@ -147,7 +147,7 @@ export function DetailPageBrandGifsPanel({ store }: { store: unknown }) {
147
147
  <div className="flex h-full flex-col p-4">
148
148
  <BrandPanelHeader onRefresh={() => void gifsQuery.refetch()} />
149
149
 
150
- <label className="mb-3 flex h-16 cursor-pointer flex-col items-center justify-center gap-1 rounded-md border border-dashed border-neutral-300 text-neutral-400 hover:border-neutral-400 hover:text-neutral-600">
150
+ <label className="mb-3 flex h-16 cursor-pointer flex-col items-center justify-center gap-1 rounded-dpe-md border border-dashed border-dpe-ink-300 text-dpe-ink-400 hover:border-dpe-ink-400 hover:text-dpe-ink-600">
151
151
  {uploadMutation.isPending ? (
152
152
  <Loader2 aria-hidden="true" className="animate-spin" size={18} />
153
153
  ) : (
@@ -166,19 +166,19 @@ export function DetailPageBrandGifsPanel({ store }: { store: unknown }) {
166
166
  </label>
167
167
 
168
168
  {brandsLoading || gifsQuery.isLoading ? (
169
- <div className="flex flex-1 items-center justify-center text-neutral-400">
169
+ <div className="flex flex-1 items-center justify-center text-dpe-ink-400">
170
170
  <Loader2 aria-hidden="true" className="animate-spin" size={22} />
171
171
  </div>
172
172
  ) : error || gifsQuery.error ? (
173
- <p className="text-xs font-medium text-red-600">
173
+ <p className="text-xs font-dpe-medium text-dpe-danger-600">
174
174
  {error ?? t("detailPage.brandAssets.loadFailed")}
175
175
  </p>
176
176
  ) : !activeBrand ? (
177
- <p className="text-xs text-neutral-400">
177
+ <p className="text-xs text-dpe-ink-400">
178
178
  {t("detailPage.brandAssets.noBrand")}
179
179
  </p>
180
180
  ) : sections.length === 0 ? (
181
- <div className="flex flex-1 flex-col items-center justify-center gap-2 text-center text-neutral-400">
181
+ <div className="flex flex-1 flex-col items-center justify-center gap-2 text-center text-dpe-ink-400">
182
182
  <Film aria-hidden="true" size={22} />
183
183
  <p className="text-xs">
184
184
  {t("detailPage.brandGifs.empty", {
@@ -191,7 +191,7 @@ export function DetailPageBrandGifsPanel({ store }: { store: unknown }) {
191
191
  <div className="flex flex-col gap-4 overflow-y-auto">
192
192
  {sections.map((section) => (
193
193
  <section key={section.kind}>
194
- <h3 className="mb-1.5 text-[11px] font-semibold text-neutral-500">
194
+ <h3 className="mb-1.5 text-[11px] font-dpe-semibold text-dpe-ink-500">
195
195
  {section.kind === "other"
196
196
  ? t("detailPage.brandGifs.sectionOther", {
197
197
  defaultValue: "기타",
@@ -205,7 +205,7 @@ export function DetailPageBrandGifsPanel({ store }: { store: unknown }) {
205
205
  )!.fallback,
206
206
  },
207
207
  )}
208
- <span className="ml-1 font-normal text-neutral-300">
208
+ <span className="ml-1 font-dpe-normal text-dpe-ink-300">
209
209
  {section.items.length}
210
210
  </span>
211
211
  </h3>
@@ -213,7 +213,7 @@ export function DetailPageBrandGifsPanel({ store }: { store: unknown }) {
213
213
  {section.items.map((asset) => (
214
214
  <div
215
215
  key={asset.id}
216
- className="group relative overflow-hidden rounded-md border border-neutral-200 hover:border-neutral-500"
216
+ className="group relative overflow-hidden rounded-dpe-md border border-dpe-ink-200 hover:border-dpe-ink-500"
217
217
  >
218
218
  <button
219
219
  type="button"
@@ -237,14 +237,14 @@ export function DetailPageBrandGifsPanel({ store }: { store: unknown }) {
237
237
  className="h-24 w-full object-cover transition group-hover:opacity-90"
238
238
  />
239
239
  </button>
240
- <span className="pointer-events-none absolute bottom-1.5 left-1.5 max-w-[calc(100%-12px)] truncate rounded-sm bg-black/70 px-1.5 py-0.5 text-[10px] text-white">
240
+ <span className="pointer-events-none absolute bottom-1.5 left-1.5 max-w-[calc(100%-12px)] truncate rounded-dpe-sm bg-dpe-scrim/70 px-1.5 py-0.5 text-[10px] text-dpe-on-accent">
241
241
  {String(asset.metadata?.effect ?? "") ||
242
242
  (asset.display_name ?? asset.filename)}
243
243
  </span>
244
244
  <button
245
245
  type="button"
246
246
  onClick={() => deleteMutation.mutate(asset)}
247
- className="absolute right-1 top-1 hidden h-6 w-6 items-center justify-center rounded-md bg-white/95 text-neutral-400 shadow-sm hover:text-red-600 group-hover:flex"
247
+ className="absolute right-1 top-1 hidden h-6 w-6 items-center justify-center rounded-dpe-md bg-dpe-surface/95 text-dpe-ink-400 shadow-sm hover:text-dpe-danger-600 group-hover:flex"
248
248
  aria-label={t("detailPage.brandAssets.delete")}
249
249
  >
250
250
  <Trash2 aria-hidden="true" size={13} />
@@ -33,7 +33,7 @@ export function DetailPageBrandKitPanel({ store }: { store: unknown }) {
33
33
 
34
34
  if (brandsLoading || assetsQuery.isLoading || moodboardQuery.isLoading) {
35
35
  return (
36
- <div className="flex h-full items-center justify-center text-neutral-400">
36
+ <div className="flex h-full items-center justify-center text-dpe-ink-400">
37
37
  <Loader2 className="animate-spin" size={20} />
38
38
  </div>
39
39
  );
@@ -42,7 +42,7 @@ export function DetailPageBrandKitPanel({ store }: { store: unknown }) {
42
42
  const KitPanel = slots?.BrandKitPanel;
43
43
  if (!activeBrand || !moodboardQuery.data || !KitPanel) {
44
44
  return (
45
- <div className="flex h-full flex-col items-center justify-center gap-2 p-5 text-center text-neutral-400">
45
+ <div className="flex h-full flex-col items-center justify-center gap-2 p-5 text-center text-dpe-ink-400">
46
46
  <Palette size={22} />
47
47
  <p className="text-xs">{t("detailPage.brandAssets.kitEmpty")}</p>
48
48
  </div>
@@ -51,13 +51,13 @@ export function DetailPageBrandKitPanel({ store }: { store: unknown }) {
51
51
 
52
52
  return (
53
53
  <div className="flex h-full flex-col overflow-y-auto">
54
- <div className="border-b border-neutral-200 p-3">
55
- <label className="grid gap-1 text-[11px] font-medium text-neutral-500">
54
+ <div className="border-b border-dpe-ink-200 p-3">
55
+ <label className="grid gap-1 text-[11px] font-dpe-medium text-dpe-ink-500">
56
56
  {t("detailPage.brandAssets.activeBrand")}
57
57
  <select
58
58
  value={activeBrand.id}
59
59
  onChange={(event) => setActiveBrandId(event.target.value)}
60
- className="h-8 rounded-md border border-neutral-200 bg-white px-2 text-xs text-neutral-800"
60
+ className="h-8 rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface px-2 text-xs text-dpe-ink-800"
61
61
  >
62
62
  {brands.map((brand) => (
63
63
  <option key={brand.id} value={brand.id}>
@@ -16,13 +16,16 @@ export function BrandPanelHeader({ onRefresh }: { onRefresh: () => void }) {
16
16
  const { brands, activeBrandId, setActiveBrandId } = brand.useBrandWorkspace();
17
17
 
18
18
  return (
19
- <div className="mb-3 flex items-center justify-between gap-2">
20
- <label className="grid min-w-0 flex-1 gap-1 text-[11px] font-medium text-neutral-500">
19
+ <div
20
+ data-dpe-part="panel-header"
21
+ className="mb-3 flex items-center justify-between gap-2"
22
+ >
23
+ <label className="grid min-w-0 flex-1 gap-1 text-[11px] font-dpe-medium text-dpe-ink-500">
21
24
  {t("detailPage.brandAssets.activeBrand")}
22
25
  <select
23
26
  value={activeBrandId ?? ""}
24
27
  onChange={(event) => setActiveBrandId(event.target.value)}
25
- className="h-8 min-w-0 rounded-md border border-neutral-200 bg-white px-2 text-xs text-neutral-800"
28
+ className="h-8 min-w-0 rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface px-2 text-xs text-dpe-ink-800"
26
29
  >
27
30
  {brands.map((brand) => (
28
31
  <option key={brand.id} value={brand.id}>
@@ -34,7 +37,7 @@ export function BrandPanelHeader({ onRefresh }: { onRefresh: () => void }) {
34
37
  <button
35
38
  type="button"
36
39
  onClick={onRefresh}
37
- className="mt-4 flex h-7 w-7 items-center justify-center rounded-md text-neutral-400 hover:bg-neutral-100 hover:text-neutral-600"
40
+ className="mt-4 flex h-7 w-7 items-center justify-center rounded-dpe-md text-dpe-ink-400 hover:bg-dpe-ink-100 hover:text-dpe-ink-600"
38
41
  aria-label={t("detailPage.brandAssets.refresh")}
39
42
  >
40
43
  <RefreshCw aria-hidden="true" size={14} />
@@ -74,7 +74,7 @@ export function DetailPageChartsPanel({ store }: { store: unknown }) {
74
74
 
75
75
  return (
76
76
  <div className="flex h-full flex-col overflow-y-auto p-3">
77
- <p className="mb-2 text-xs font-medium text-neutral-500">
77
+ <p className="mb-2 text-xs font-dpe-medium text-dpe-ink-500">
78
78
  {t("detailPage.chart.panelTitle")}
79
79
  </p>
80
80
  <div className="grid grid-cols-2 gap-2">
@@ -84,7 +84,7 @@ export function DetailPageChartsPanel({ store }: { store: unknown }) {
84
84
  type="button"
85
85
  onClick={() => insert(preset)}
86
86
  title={t("detailPage.chart.insertHint")}
87
- className="flex flex-col items-center gap-1.5 rounded-lg border border-neutral-200 p-2 hover:border-neutral-400"
87
+ className="flex flex-col items-center gap-1.5 rounded-dpe-lg border border-dpe-ink-200 p-2 hover:border-dpe-ink-400"
88
88
  >
89
89
  {/* eslint-disable-next-line @next/next/no-img-element */}
90
90
  <img
@@ -92,11 +92,11 @@ export function DetailPageChartsPanel({ store }: { store: unknown }) {
92
92
  alt={t(preset.labelKey)}
93
93
  className="h-20 w-full object-contain"
94
94
  />
95
- <span className="text-[11px] text-neutral-500">{t(preset.labelKey)}</span>
95
+ <span className="text-[11px] text-dpe-ink-500">{t(preset.labelKey)}</span>
96
96
  </button>
97
97
  ))}
98
98
  </div>
99
- <p className="mt-3 text-[11px] leading-relaxed text-neutral-400">
99
+ <p className="mt-3 text-[11px] leading-relaxed text-dpe-ink-400">
100
100
  {t("detailPage.chart.panelHint")}
101
101
  </p>
102
102
  </div>
@@ -72,14 +72,14 @@ export function DetailPageDecorationsPanel({ store }: { store: unknown }) {
72
72
  if (isError) {
73
73
  return (
74
74
  <div className="p-3">
75
- <p className="text-xs text-red-600">{t("detailPage.shapes.decorationsFailed")}</p>
75
+ <p className="text-xs text-dpe-danger-600">{t("detailPage.shapes.decorationsFailed")}</p>
76
76
  </div>
77
77
  );
78
78
  }
79
79
 
80
80
  if (isPending) {
81
81
  return (
82
- <div className="flex h-full items-center justify-center text-neutral-400">
82
+ <div className="flex h-full items-center justify-center text-dpe-ink-400">
83
83
  <Loader2 aria-hidden="true" className="animate-spin" size={18} />
84
84
  </div>
85
85
  );
@@ -88,7 +88,7 @@ export function DetailPageDecorationsPanel({ store }: { store: unknown }) {
88
88
  if (!groups.length) {
89
89
  return (
90
90
  <div className="p-3">
91
- <p className="text-xs text-neutral-400">{t("detailPage.shapes.decorationsEmpty")}</p>
91
+ <p className="text-xs text-dpe-ink-400">{t("detailPage.shapes.decorationsEmpty")}</p>
92
92
  </div>
93
93
  );
94
94
  }
@@ -97,7 +97,7 @@ export function DetailPageDecorationsPanel({ store }: { store: unknown }) {
97
97
  <div className="flex h-full flex-col overflow-y-auto p-3">
98
98
  {groups.map(({ category, cells }) => (
99
99
  <div key={category} className="mb-4 last:mb-0">
100
- <p className="mb-2 text-xs font-medium text-neutral-500">
100
+ <p className="mb-2 text-xs font-dpe-medium text-dpe-ink-500">
101
101
  {t(`detailPage.shapes.decorations.${category}`)}
102
102
  </p>
103
103
  <div className="grid grid-cols-4 gap-2">
@@ -106,7 +106,7 @@ export function DetailPageDecorationsPanel({ store }: { store: unknown }) {
106
106
  key={item.id}
107
107
  type="button"
108
108
  onClick={() => insert(item)}
109
- className="flex aspect-square items-center justify-center rounded-lg border border-neutral-200 p-2 hover:border-neutral-400"
109
+ className="flex aspect-square items-center justify-center rounded-dpe-lg border border-dpe-ink-200 p-2 hover:border-dpe-ink-400"
110
110
  title={t("detailPage.shapes.insertHint")}
111
111
  >
112
112
  {/* eslint-disable-next-line @next/next/no-img-element */}