@leviosa-ai/detail-page-editor 0.2.2 → 0.3.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.
Files changed (59) 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/package.json +4 -1
  58. package/styles/canvas-bridge.css +35 -0
  59. package/styles/tokens.css +103 -0
@@ -61,8 +61,8 @@ export function ElementRecentsStrip({ store }: { store: unknown }) {
61
61
  if (!items.length) return null;
62
62
 
63
63
  return (
64
- <div className="shrink-0 border-b border-neutral-200 px-2 py-2">
65
- <p className="mb-1.5 px-0.5 text-[11px] font-medium text-neutral-500">
64
+ <div className="shrink-0 border-b border-dpe-ink-200 px-2 py-2">
65
+ <p className="mb-1.5 px-0.5 text-[11px] font-dpe-medium text-dpe-ink-500">
66
66
  {t("detailPage.recents.title")}
67
67
  </p>
68
68
  <div className="flex gap-1.5 overflow-x-auto pb-0.5">
@@ -73,7 +73,7 @@ export function ElementRecentsStrip({ store }: { store: unknown }) {
73
73
  onClick={() => insert(entry)}
74
74
  title={entry.label ?? entry.key}
75
75
  aria-label={entry.label ?? entry.key}
76
- className="flex h-11 w-11 items-center justify-center rounded-lg border border-neutral-200 bg-white p-1.5 hover:border-neutral-400"
76
+ className="flex h-11 w-11 items-center justify-center rounded-dpe-lg border border-dpe-ink-200 bg-dpe-surface p-1.5 hover:border-dpe-ink-400"
77
77
  >
78
78
  {/* eslint-disable-next-line @next/next/no-img-element */}
79
79
  <img
@@ -89,10 +89,10 @@ export function ElementRecentsStrip({ store }: { store: unknown }) {
89
89
  isPinned ? "detailPage.recents.unpin" : "detailPage.recents.pin",
90
90
  )}
91
91
  aria-pressed={isPinned}
92
- className={`absolute -right-1 -top-1 rounded-full border border-neutral-200 bg-white p-0.5 transition-opacity ${
92
+ className={`absolute -right-1 -top-1 rounded-full border border-dpe-ink-200 bg-dpe-surface p-0.5 transition-opacity ${
93
93
  isPinned
94
- ? "text-amber-500 opacity-100"
95
- : "text-neutral-300 opacity-0 group-hover:opacity-100"
94
+ ? "text-dpe-warn-500 opacity-100"
95
+ : "text-dpe-ink-300 opacity-0 group-hover:opacity-100"
96
96
  }`}
97
97
  >
98
98
  <Star aria-hidden="true" size={10} fill={isPinned ? "currentColor" : "none"} />
@@ -166,8 +166,8 @@ export function FillControl({
166
166
  onClick={() => onChange(solid)}
167
167
  className={
168
168
  !isGradient
169
- ? "h-8 rounded-md bg-neutral-900 text-xs font-semibold text-white"
170
- : "h-8 rounded-md border border-neutral-200 text-xs font-medium text-neutral-600 hover:bg-neutral-50"
169
+ ? "h-8 rounded-dpe-md bg-dpe-ink-900 text-xs font-dpe-semibold text-dpe-on-accent"
170
+ : "h-8 rounded-dpe-md border border-dpe-ink-200 text-xs font-dpe-medium text-dpe-ink-600 hover:bg-dpe-ink-50"
171
171
  }
172
172
  >
173
173
  {t("detailPage.properties.fillSolid")}
@@ -177,8 +177,8 @@ export function FillControl({
177
177
  onClick={() => emit(g)}
178
178
  className={
179
179
  isGradient
180
- ? "h-8 rounded-md bg-neutral-900 text-xs font-semibold text-white"
181
- : "h-8 rounded-md border border-neutral-200 text-xs font-medium text-neutral-600 hover:bg-neutral-50"
180
+ ? "h-8 rounded-dpe-md bg-dpe-ink-900 text-xs font-dpe-semibold text-dpe-on-accent"
181
+ : "h-8 rounded-dpe-md border border-dpe-ink-200 text-xs font-dpe-medium text-dpe-ink-600 hover:bg-dpe-ink-50"
182
182
  }
183
183
  >
184
184
  {t("detailPage.properties.fillGradient")}
@@ -191,7 +191,7 @@ export function FillControl({
191
191
  <div className="flex flex-col gap-2">
192
192
  {/* 미리보기 바 */}
193
193
  <div
194
- className="h-6 w-full rounded-md border border-neutral-200"
194
+ className="h-6 w-full rounded-dpe-md border border-dpe-ink-200"
195
195
  style={{ background: value }}
196
196
  aria-hidden="true"
197
197
  />
@@ -212,16 +212,16 @@ export function FillControl({
212
212
  value={Math.round(clampPos(s.pos))}
213
213
  onChange={(e) => setStop(i, { pos: Number(e.target.value) })}
214
214
  aria-label={t("detailPage.properties.gradientStopPosition")}
215
- className="w-14 rounded-md border border-neutral-200 px-1.5 py-1 text-xs tabular-nums focus:border-neutral-400 focus:outline-none"
215
+ className="w-14 rounded-dpe-md border border-dpe-ink-200 px-1.5 py-1 text-xs tabular-nums focus:border-dpe-ink-400 focus:outline-none"
216
216
  />
217
- <span className="text-[11px] text-neutral-400">%</span>
217
+ <span className="text-[11px] text-dpe-ink-400">%</span>
218
218
  </div>
219
219
  <button
220
220
  type="button"
221
221
  onClick={() => removeStop(i)}
222
222
  disabled={g.stops.length <= 2}
223
223
  aria-label={t("detailPage.properties.gradientRemoveStop")}
224
- className="rounded p-1 text-neutral-400 hover:text-neutral-700 disabled:cursor-not-allowed disabled:opacity-30"
224
+ className="rounded p-1 text-dpe-ink-400 hover:text-dpe-ink-700 disabled:cursor-not-allowed disabled:opacity-30"
225
225
  >
226
226
  <X size={13} />
227
227
  </button>
@@ -230,7 +230,7 @@ export function FillControl({
230
230
  <button
231
231
  type="button"
232
232
  onClick={addStop}
233
- className="flex items-center justify-center gap-1 rounded-md border border-dashed border-neutral-300 py-1.5 text-[11px] font-medium text-neutral-500 hover:border-neutral-400 hover:bg-neutral-50"
233
+ className="flex items-center justify-center gap-1 rounded-dpe-md border border-dashed border-dpe-ink-300 py-1.5 text-[11px] font-dpe-medium text-dpe-ink-500 hover:border-dpe-ink-400 hover:bg-dpe-ink-50"
234
234
  >
235
235
  <Plus size={12} />
236
236
  {t("detailPage.properties.gradientAddStop")}
@@ -239,7 +239,7 @@ export function FillControl({
239
239
 
240
240
  {/* 각도 */}
241
241
  <label className="flex items-center gap-3">
242
- <span className="w-8 shrink-0 text-[11px] text-neutral-500">
242
+ <span className="w-8 shrink-0 text-[11px] text-dpe-ink-500">
243
243
  {t("detailPage.properties.gradientAngle")}
244
244
  </span>
245
245
  <input
@@ -248,9 +248,9 @@ export function FillControl({
248
248
  max={360}
249
249
  value={g.angle}
250
250
  onChange={(e) => emit({ ...g, angle: Number(e.target.value) })}
251
- className="min-w-0 flex-1 accent-neutral-900"
251
+ className="min-w-0 flex-1 accent-dpe-ink-900"
252
252
  />
253
- <span className="w-10 text-right text-xs tabular-nums text-neutral-700">
253
+ <span className="w-10 text-right text-xs tabular-nums text-dpe-ink-700">
254
254
  {Math.round(g.angle)}°
255
255
  </span>
256
256
  </label>
@@ -119,21 +119,21 @@ export const FindReplacePanel = observer(function FindReplacePanel({
119
119
  if (!open) return null;
120
120
 
121
121
  const field =
122
- "h-8 w-full rounded-md border border-neutral-200 px-2 text-[13px] text-neutral-800 outline-none focus:border-neutral-400";
122
+ "h-8 w-full rounded-dpe-md border border-dpe-ink-200 px-2 text-[13px] text-dpe-ink-800 outline-none focus:border-dpe-ink-400";
123
123
  const button =
124
- "flex h-8 items-center justify-center rounded-md border border-neutral-200 px-2.5 text-[12px] text-neutral-700 transition-colors hover:bg-neutral-50 disabled:cursor-not-allowed disabled:opacity-40";
124
+ "flex h-8 items-center justify-center rounded-dpe-md border border-dpe-ink-200 px-2.5 text-[12px] text-dpe-ink-700 transition-colors hover:bg-dpe-ink-50 disabled:cursor-not-allowed disabled:opacity-40";
125
125
 
126
126
  return (
127
127
  <div
128
128
  data-dp-find-replace
129
- className="absolute right-4 top-4 z-40 w-[292px] rounded-lg border border-neutral-200 bg-white p-3 shadow-lg"
129
+ className="absolute right-4 top-4 z-40 w-[292px] rounded-dpe-lg border border-dpe-ink-200 bg-dpe-surface p-3 shadow-lg"
130
130
  onKeyDown={(e) => {
131
131
  if (e.key === "Escape") setOpen(false);
132
132
  if (e.key === "Enter") step(e.shiftKey ? -1 : 1);
133
133
  }}
134
134
  >
135
135
  <div className="mb-2 flex items-center justify-between">
136
- <span className="text-[12px] font-medium text-neutral-500">
136
+ <span className="text-[12px] font-dpe-medium text-dpe-ink-500">
137
137
  {t("detailPage.findReplace.title")}
138
138
  </span>
139
139
  <button
@@ -141,7 +141,7 @@ export const FindReplacePanel = observer(function FindReplacePanel({
141
141
  aria-label={t("detailPage.findReplace.close")}
142
142
  data-dp-find-close
143
143
  onClick={() => setOpen(false)}
144
- className="rounded p-0.5 text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700"
144
+ className="rounded p-0.5 text-dpe-ink-400 hover:bg-dpe-ink-100 hover:text-dpe-ink-700"
145
145
  >
146
146
  <X size={14} />
147
147
  </button>
@@ -167,7 +167,7 @@ export const FindReplacePanel = observer(function FindReplacePanel({
167
167
  aria-pressed={caseSensitive}
168
168
  data-dp-find-case
169
169
  onClick={() => setCaseSensitive((v) => !v)}
170
- className={`${button} shrink-0 ${caseSensitive ? "bg-neutral-900 text-white hover:bg-neutral-900" : ""}`}
170
+ className={`${button} shrink-0 ${caseSensitive ? "bg-dpe-ink-900 text-dpe-on-accent hover:bg-dpe-ink-900" : ""}`}
171
171
  >
172
172
  <CaseSensitive size={15} />
173
173
  </button>
@@ -189,7 +189,7 @@ export const FindReplacePanel = observer(function FindReplacePanel({
189
189
  data-dp-find-current={matches.length ? at + 1 : 0}
190
190
  data-dp-find-blocks={matches.length}
191
191
  data-dp-find-total={total}
192
- className="text-[12px] tabular-nums text-neutral-500"
192
+ className="text-[12px] tabular-nums text-dpe-ink-500"
193
193
  >
194
194
  {matches.length
195
195
  ? t("detailPage.findReplace.count", {
@@ -244,7 +244,7 @@ export const FindReplacePanel = observer(function FindReplacePanel({
244
244
  </button>
245
245
  </div>
246
246
 
247
- <p className="mt-2 text-[11px] leading-relaxed text-neutral-400">
247
+ <p className="mt-2 text-[11px] leading-relaxed text-dpe-ink-400">
248
248
  {t("detailPage.findReplace.chartHint")}
249
249
  </p>
250
250
  </div>
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  /**
4
- * 어두운(bg-neutral-900) 생성 버튼 위에 얹는 가짜 프로그레스 오버레이.
4
+ * 어두운(bg-dpe-ink-900) 생성 버튼 위에 얹는 가짜 프로그레스 오버레이.
5
5
  *
6
6
  * 진행 신호를 두 겹으로 준다: (1) 버튼 전체를 은은하게 채우는 반투명 면,
7
7
  * (2) 하단의 또렷한 3px 바. 둘 다 왼→오로 자라며, 라벨(퍼센트 포함)은 버튼이
@@ -15,15 +15,15 @@ export function GenerationProgressFill({ progress }: { progress: number }) {
15
15
  <>
16
16
  <span
17
17
  aria-hidden="true"
18
- className="pointer-events-none absolute inset-y-0 left-0 bg-white/10"
18
+ className="pointer-events-none absolute inset-y-0 left-0 bg-dpe-surface/10"
19
19
  style={{ width: `${pct}%`, transition: "width 200ms ease-out" }}
20
20
  />
21
21
  <span
22
22
  aria-hidden="true"
23
- className="pointer-events-none absolute inset-x-0 bottom-0 h-[3px] bg-white/25"
23
+ className="pointer-events-none absolute inset-x-0 bottom-0 h-[3px] bg-dpe-surface/25"
24
24
  >
25
25
  <span
26
- className="block h-full bg-white"
26
+ className="block h-full bg-dpe-surface"
27
27
  style={{ width: `${pct}%`, transition: "width 200ms ease-out" }}
28
28
  />
29
29
  </span>
@@ -62,12 +62,12 @@ export function GifEffectPicker({
62
62
  disabled={disabled}
63
63
  aria-label={label}
64
64
  aria-expanded={open}
65
- className="flex min-w-0 items-center justify-between gap-2 rounded-md border border-neutral-200 bg-white px-2 py-1.5 text-left text-sm text-neutral-900 outline-none hover:bg-neutral-50 focus:border-neutral-400 disabled:cursor-not-allowed disabled:opacity-50"
65
+ className="flex min-w-0 items-center justify-between gap-2 rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface px-2 py-1.5 text-left text-sm text-dpe-ink-900 outline-none hover:bg-dpe-ink-50 focus:border-dpe-ink-400 disabled:cursor-not-allowed disabled:opacity-50"
66
66
  >
67
67
  <span className="min-w-0 truncate">{selected?.label ?? value}</span>
68
68
  <ChevronDown
69
69
  aria-hidden="true"
70
- className="shrink-0 text-neutral-400"
70
+ className="shrink-0 text-dpe-ink-400"
71
71
  size={14}
72
72
  />
73
73
  </button>
@@ -78,13 +78,13 @@ export function GifEffectPicker({
78
78
  align="start"
79
79
  sideOffset={8}
80
80
  collisionPadding={12}
81
- className="z-[120] w-[300px] overflow-hidden rounded-lg border border-neutral-200 bg-white shadow-[0_10px_32px_rgba(0,0,0,0.14)]"
81
+ className="z-[120] w-[300px] overflow-hidden rounded-dpe-lg border border-dpe-ink-200 bg-dpe-surface shadow-[0_10px_32px_rgba(0,0,0,0.14)]"
82
82
  >
83
83
  <div className="max-h-[420px] overflow-y-auto p-1">
84
84
  {groups.map((group) => (
85
85
  <div key={group.name || "default"}>
86
86
  {group.name ? (
87
- <p className="px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-[0.06em] text-neutral-400">
87
+ <p className="px-2 pb-1 pt-2 text-[10px] font-dpe-semibold uppercase tracking-[0.06em] text-dpe-ink-400">
88
88
  {group.name}
89
89
  </p>
90
90
  ) : null}
@@ -97,9 +97,9 @@ export function GifEffectPicker({
97
97
  onChange(option.id);
98
98
  setOpen(false);
99
99
  }}
100
- className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left hover:bg-neutral-50"
100
+ className="flex w-full items-center gap-2 rounded-dpe-md px-2 py-1.5 text-left hover:bg-dpe-ink-50"
101
101
  >
102
- <span className="flex h-12 w-[92px] shrink-0 items-center justify-center overflow-hidden rounded border border-neutral-100 bg-neutral-50">
102
+ <span className="flex h-12 w-[92px] shrink-0 items-center justify-center overflow-hidden rounded border border-dpe-ink-100 bg-dpe-ink-50">
103
103
  {/* 미리보기는 움직여야 의미가 있다 — 최적화(정지 프레임 변환)를 끈다. */}
104
104
  <Image
105
105
  src={option.previewSrc}
@@ -111,11 +111,11 @@ export function GifEffectPicker({
111
111
  />
112
112
  </span>
113
113
  <span className="min-w-0 flex-1">
114
- <span className="block truncate text-sm text-neutral-900">
114
+ <span className="block truncate text-sm text-dpe-ink-900">
115
115
  {option.label}
116
116
  </span>
117
117
  {option.hint ? (
118
- <span className="mt-0.5 block text-[11px] leading-4 text-neutral-400">
118
+ <span className="mt-0.5 block text-[11px] leading-4 text-dpe-ink-400">
119
119
  {option.hint}
120
120
  </span>
121
121
  ) : null}
@@ -128,7 +128,7 @@ export function GifEffectPicker({
128
128
  </div>
129
129
  ))}
130
130
  </div>
131
- <PopoverPrimitive.Arrow className="fill-white" />
131
+ <PopoverPrimitive.Arrow className="fill-dpe-on-accent" />
132
132
  </PopoverPrimitive.Content>
133
133
  </PopoverPrimitive.Portal>
134
134
  </PopoverPrimitive.Root>
@@ -106,10 +106,10 @@ export function GroupPromptEditPanel({
106
106
  const badgeLimit = hasText ? textLimit : svgLimit;
107
107
 
108
108
  return (
109
- <div className="flex flex-col overflow-hidden rounded-xl border border-border bg-card">
109
+ <div className="flex flex-col overflow-hidden rounded-dpe-xl border border-border bg-card">
110
110
  <div className="flex items-center gap-1.5 border-b border-border px-3 py-2">
111
111
  <Sparkles size={13} className="text-primary" />
112
- <span className="text-xs font-medium text-foreground">프롬프트로 편집</span>
112
+ <span className="text-xs font-dpe-medium text-foreground">프롬프트로 편집</span>
113
113
  {unlimited || badgeLimit > 0 ? (
114
114
  <EditUsageBadge used={badgeUsed} limit={badgeLimit} unlimited={unlimited} />
115
115
  ) : null}
@@ -138,7 +138,7 @@ export function GroupPromptEditPanel({
138
138
  type="button"
139
139
  onClick={send}
140
140
  disabled={busy || !input.trim()}
141
- className="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-primary text-primary-foreground disabled:opacity-40"
141
+ className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-primary text-primary-foreground disabled:opacity-40"
142
142
  aria-label="프롬프트로 수정"
143
143
  >
144
144
  {busy ? (
@@ -18,8 +18,8 @@ export function Section({
18
18
  children: React.ReactNode;
19
19
  }) {
20
20
  return (
21
- <section className="border-t border-neutral-200 px-4 py-3 first:border-t-0">
22
- <h4 className="mb-2 text-[11px] font-semibold uppercase tracking-[0.06em] text-neutral-400">
21
+ <section className="border-t border-dpe-ink-200 px-4 py-3 first:border-t-0">
22
+ <h4 className="mb-2 text-[11px] font-dpe-semibold uppercase tracking-[0.06em] text-dpe-ink-400">
23
23
  {title}
24
24
  </h4>
25
25
  {children}
@@ -105,7 +105,7 @@ export function NumberField({
105
105
  };
106
106
 
107
107
  return (
108
- <div className="flex items-center rounded-md border border-neutral-200 bg-white pr-2 focus-within:border-neutral-400">
108
+ <div className="flex items-center rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface pr-2 focus-within:border-dpe-ink-400">
109
109
  {label ? (
110
110
  <span
111
111
  aria-hidden="true"
@@ -113,7 +113,7 @@ export function NumberField({
113
113
  onPointerMove={onScrubMove}
114
114
  onPointerUp={onScrubUp}
115
115
  onPointerCancel={onScrubUp}
116
- className="cursor-ew-resize select-none touch-none py-1.5 pl-2 pr-1.5 text-xs font-medium text-neutral-400 hover:text-neutral-600"
116
+ className="cursor-ew-resize select-none touch-none py-1.5 pl-2 pr-1.5 text-xs font-dpe-medium text-dpe-ink-400 hover:text-dpe-ink-600"
117
117
  title={`${label} — 좌우로 드래그해 값 조절 (Shift ×10)`}
118
118
  >
119
119
  {label}
@@ -139,9 +139,9 @@ export function NumberField({
139
139
  stepBy(-1);
140
140
  }
141
141
  }}
142
- className="w-full min-w-0 bg-transparent py-1.5 text-sm tabular-nums text-neutral-900 outline-none"
142
+ className="w-full min-w-0 bg-transparent py-1.5 text-sm tabular-nums text-dpe-ink-900 outline-none"
143
143
  />
144
- {suffix ? <span className="ml-1 text-xs text-neutral-400">{suffix}</span> : null}
144
+ {suffix ? <span className="ml-1 text-xs text-dpe-ink-400">{suffix}</span> : null}
145
145
  </div>
146
146
  );
147
147
  }
@@ -163,10 +163,10 @@ export function ToggleButton({
163
163
  title={title}
164
164
  onClick={onClick}
165
165
  className={[
166
- "flex h-8 flex-1 items-center justify-center rounded-md border transition-colors",
166
+ "flex h-8 flex-1 items-center justify-center rounded-dpe-md border transition-colors",
167
167
  active
168
- ? "border-neutral-800 bg-neutral-900 text-white"
169
- : "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50",
168
+ ? "border-dpe-ink-800 bg-dpe-ink-900 text-dpe-on-accent"
169
+ : "border-dpe-ink-200 bg-dpe-surface text-dpe-ink-600 hover:bg-dpe-ink-50",
170
170
  ].join(" ")}
171
171
  >
172
172
  {children}
@@ -34,7 +34,7 @@ export function PanelSearchInput({
34
34
  <Search
35
35
  aria-hidden="true"
36
36
  size={14}
37
- className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-neutral-400"
37
+ className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-dpe-ink-400"
38
38
  />
39
39
  <input
40
40
  type="search"
@@ -42,7 +42,7 @@ export function PanelSearchInput({
42
42
  onChange={(event) => onChange(event.target.value)}
43
43
  placeholder={placeholder}
44
44
  aria-label={label}
45
- className="w-full appearance-none rounded-lg border border-neutral-200 py-2 pl-8 pr-2.5 text-sm leading-5 placeholder:text-neutral-400 focus:border-neutral-400 focus:outline-none [&::-webkit-search-cancel-button]:appearance-none"
45
+ className="w-full appearance-none rounded-dpe-lg border border-dpe-ink-200 py-2 pl-8 pr-2.5 text-sm leading-5 placeholder:text-dpe-ink-400 focus:border-dpe-ink-400 focus:outline-none [&::-webkit-search-cancel-button]:appearance-none"
46
46
  />
47
47
  </div>
48
48
  );
@@ -97,10 +97,10 @@ export function PromptEditPanel({
97
97
  }, [api, toast, input, busy, blocked, generatedId, slotRole, currentText, maxLength, renderKind, onApplied, onUsage, t]);
98
98
 
99
99
  return (
100
- <div className="relative flex flex-col overflow-hidden rounded-xl border border-border bg-card">
100
+ <div className="relative flex flex-col overflow-hidden rounded-dpe-xl border border-border bg-card">
101
101
  <div className="flex items-center gap-1.5 border-b border-border px-3 py-2">
102
102
  <Sparkles size={13} className="text-primary" />
103
- <span className="text-xs font-medium text-foreground">{t("detailPage.promptEdit.header")}</span>
103
+ <span className="text-xs font-dpe-medium text-foreground">{t("detailPage.promptEdit.header")}</span>
104
104
  {unlimited || editLimit > 0 ? (
105
105
  <EditUsageBadge used={editsUsed} limit={editLimit} unlimited={unlimited} />
106
106
  ) : null}
@@ -129,7 +129,7 @@ export function PromptEditPanel({
129
129
  type="button"
130
130
  onClick={send}
131
131
  disabled={busy || !input.trim()}
132
- className="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-primary text-primary-foreground disabled:opacity-40"
132
+ className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-primary text-primary-foreground disabled:opacity-40"
133
133
  aria-label={t("detailPage.promptEdit.send")}
134
134
  >
135
135
  {busy ? (
@@ -190,12 +190,12 @@ export function SectionReauthorController({
190
190
  onClose={close}
191
191
  />
192
192
  {notice ? (
193
- <div className="fixed bottom-4 left-1/2 z-[110] -translate-x-1/2 rounded-lg border border-amber-200 bg-amber-50 px-4 py-2 text-xs text-amber-800 shadow-lg">
193
+ <div className="fixed bottom-4 left-1/2 z-[110] -translate-x-1/2 rounded-dpe-lg border border-dpe-warn-200 bg-dpe-warn-50 px-4 py-2 text-xs text-dpe-warn-800 shadow-lg">
194
194
  <span>{notice}</span>
195
195
  <button
196
196
  type="button"
197
197
  onClick={() => setNotice(null)}
198
- className="ml-3 font-semibold underline"
198
+ className="ml-3 font-dpe-semibold underline"
199
199
  >
200
200
  {t("detailPage.annotate.close", { defaultValue: "닫기" })}
201
201
  </button>
@@ -122,10 +122,10 @@ export function SpecPromptEditPanel({
122
122
  ]);
123
123
 
124
124
  return (
125
- <div className="relative flex flex-col overflow-hidden rounded-xl border border-border bg-card">
125
+ <div className="relative flex flex-col overflow-hidden rounded-dpe-xl border border-border bg-card">
126
126
  <div className="flex items-center gap-1.5 border-b border-border px-3 py-2">
127
127
  <Sparkles size={13} className="text-primary" />
128
- <span className="text-xs font-medium text-foreground">
128
+ <span className="text-xs font-dpe-medium text-foreground">
129
129
  {t("detailPage.promptEdit.header")}
130
130
  </span>
131
131
  {unlimited || editLimit > 0 ? (
@@ -158,7 +158,7 @@ export function SpecPromptEditPanel({
158
158
  type="button"
159
159
  onClick={send}
160
160
  disabled={busy || !input.trim()}
161
- className="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-primary text-primary-foreground disabled:opacity-40"
161
+ className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-primary text-primary-foreground disabled:opacity-40"
162
162
  aria-label={t("detailPage.promptEdit.send")}
163
163
  >
164
164
  {busy ? (
@@ -90,10 +90,10 @@ export function SvgPromptEditPanel({
90
90
  }, [api, toast, input, busy, blocked, generatedId, slotRole, currentSvg, onApplied, onUsage, t]);
91
91
 
92
92
  return (
93
- <div className="relative flex flex-col overflow-hidden rounded-xl border border-border bg-card">
93
+ <div className="relative flex flex-col overflow-hidden rounded-dpe-xl border border-border bg-card">
94
94
  <div className="flex items-center gap-1.5 border-b border-border px-3 py-2">
95
95
  <Sparkles size={13} className="text-primary" />
96
- <span className="text-xs font-medium text-foreground">{t("detailPage.promptEdit.header")}</span>
96
+ <span className="text-xs font-dpe-medium text-foreground">{t("detailPage.promptEdit.header")}</span>
97
97
  {unlimited || editLimit > 0 ? (
98
98
  <EditUsageBadge used={editsUsed} limit={editLimit} unlimited={unlimited} />
99
99
  ) : null}
@@ -122,7 +122,7 @@ export function SvgPromptEditPanel({
122
122
  type="button"
123
123
  onClick={send}
124
124
  disabled={busy || !input.trim()}
125
- className="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-primary text-primary-foreground disabled:opacity-40"
125
+ className="grid h-8 w-8 shrink-0 place-items-center rounded-dpe-lg bg-primary text-primary-foreground disabled:opacity-40"
126
126
  aria-label={t("detailPage.promptEdit.send")}
127
127
  >
128
128
  {busy ? (
@@ -102,7 +102,7 @@ function Cell({
102
102
  (e.target as HTMLInputElement).blur();
103
103
  }
104
104
  }}
105
- 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"
105
+ 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"
106
106
  />
107
107
  );
108
108
  }
@@ -212,10 +212,10 @@ export const TableInspector = observer(function TableInspector({
212
212
  type="button"
213
213
  aria-pressed={kind === spec.kind}
214
214
  onClick={() => apply({ kind })}
215
- className={`h-8 rounded-md border text-xs transition-colors ${
215
+ className={`h-8 rounded-dpe-md border text-xs transition-colors ${
216
216
  kind === spec.kind
217
- ? "border-neutral-300 bg-neutral-100 font-semibold text-neutral-900"
218
- : "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
217
+ ? "border-dpe-ink-300 bg-dpe-ink-100 font-dpe-semibold text-dpe-ink-900"
218
+ : "border-dpe-ink-200 bg-dpe-surface text-dpe-ink-600 hover:bg-dpe-ink-50"
219
219
  }`}
220
220
  >
221
221
  {t(`detailPage.table.kinds.${kind}`)}
@@ -223,7 +223,7 @@ export const TableInspector = observer(function TableInspector({
223
223
  ))}
224
224
  </div>
225
225
  {spec.kind === "keyvalue" && spec.data.columns.length > 2 ? (
226
- <p className="mt-2 text-[11px] text-amber-600">
226
+ <p className="mt-2 text-[11px] text-dpe-warn-600">
227
227
  {t("detailPage.table.hiddenColumns", {
228
228
  count: spec.data.columns.length - 2,
229
229
  })}
@@ -263,7 +263,7 @@ export const TableInspector = observer(function TableInspector({
263
263
  },
264
264
  })
265
265
  }
266
- className="shrink-0 rounded-md p-1.5 text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700 disabled:opacity-30"
266
+ className="shrink-0 rounded-dpe-md p-1.5 text-dpe-ink-400 hover:bg-dpe-ink-100 hover:text-dpe-ink-700 disabled:opacity-30"
267
267
  >
268
268
  <Trash2 className="h-3.5 w-3.5" />
269
269
  </button>
@@ -292,7 +292,7 @@ export const TableInspector = observer(function TableInspector({
292
292
  title={t("detailPage.table.removeRow")}
293
293
  disabled={rowCount <= 1}
294
294
  onClick={() => apply({ data: withRowRemoved(spec.data, row) })}
295
- className="shrink-0 rounded-md p-1.5 text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700 disabled:opacity-30"
295
+ className="shrink-0 rounded-dpe-md p-1.5 text-dpe-ink-400 hover:bg-dpe-ink-100 hover:text-dpe-ink-700 disabled:opacity-30"
296
296
  >
297
297
  <Trash2 className="h-3.5 w-3.5" />
298
298
  </button>
@@ -305,7 +305,7 @@ export const TableInspector = observer(function TableInspector({
305
305
  type="button"
306
306
  disabled={rowCount >= MAX_ROWS}
307
307
  onClick={() => apply({ data: withRowAdded(spec.data) })}
308
- className="flex flex-1 items-center justify-center gap-1 rounded-md border border-neutral-200 py-1.5 text-xs text-neutral-600 hover:bg-neutral-50 disabled:opacity-40"
308
+ className="flex flex-1 items-center justify-center gap-1 rounded-dpe-md border border-dpe-ink-200 py-1.5 text-xs text-dpe-ink-600 hover:bg-dpe-ink-50 disabled:opacity-40"
309
309
  >
310
310
  <Rows3 className="h-3.5 w-3.5" />
311
311
  {t("detailPage.table.addRow")}
@@ -315,14 +315,14 @@ export const TableInspector = observer(function TableInspector({
315
315
  disabled={!isGrid || columnCount >= MAX_COLUMNS}
316
316
  onClick={() => apply({ data: withColumnAdded(spec.data) })}
317
317
  title={isGrid ? undefined : t("detailPage.table.addColumnGridOnly")}
318
- className="flex flex-1 items-center justify-center gap-1 rounded-md border border-neutral-200 py-1.5 text-xs text-neutral-600 hover:bg-neutral-50 disabled:opacity-40"
318
+ className="flex flex-1 items-center justify-center gap-1 rounded-dpe-md border border-dpe-ink-200 py-1.5 text-xs text-dpe-ink-600 hover:bg-dpe-ink-50 disabled:opacity-40"
319
319
  >
320
320
  <Columns3 className="h-3.5 w-3.5" />
321
321
  {t("detailPage.table.addColumn")}
322
322
  </button>
323
323
  </div>
324
324
 
325
- <p className="mt-2 text-[11px] text-neutral-400">
325
+ <p className="mt-2 text-[11px] text-dpe-ink-400">
326
326
  {notice ?? `${t("detailPage.table.pasteHint")} ${t("detailPage.table.canvasHint")}`}
327
327
  </p>
328
328
  </div>
@@ -355,12 +355,12 @@ export const TableInspector = observer(function TableInspector({
355
355
  <button
356
356
  type="button"
357
357
  onClick={() => apply(autoColumnWidths(spec))}
358
- className="flex items-center gap-1 rounded-md border border-neutral-200 px-2 py-1.5 text-xs text-neutral-600 hover:bg-neutral-50"
358
+ className="flex items-center gap-1 rounded-dpe-md border border-dpe-ink-200 px-2 py-1.5 text-xs text-dpe-ink-600 hover:bg-dpe-ink-50"
359
359
  >
360
360
  <RotateCcw className="h-3.5 w-3.5" />
361
361
  {t("detailPage.table.autoWidths")}
362
362
  </button>
363
- <span className="text-[11px] text-neutral-400">
363
+ <span className="text-[11px] text-dpe-ink-400">
364
364
  {t("detailPage.table.autoWidthsHint")}
365
365
  </span>
366
366
  </div>
@@ -389,7 +389,7 @@ export const TableInspector = observer(function TableInspector({
389
389
  )}
390
390
 
391
391
  <div className="flex items-center gap-1.5">
392
- <span className="w-16 shrink-0 text-[11px] text-neutral-500">
392
+ <span className="w-16 shrink-0 text-[11px] text-dpe-ink-500">
393
393
  {t("detailPage.table.align")}
394
394
  </span>
395
395
  <div className="flex flex-1 gap-1">
@@ -405,10 +405,10 @@ export const TableInspector = observer(function TableInspector({
405
405
  next[columnCount - 1] = align;
406
406
  setOptions({ align: next });
407
407
  }}
408
- className={`h-7 flex-1 rounded-md border text-[11px] ${
408
+ className={`h-7 flex-1 rounded-dpe-md border text-[11px] ${
409
409
  (spec.options.align[columnCount - 1] ?? "left") === align
410
- ? "border-neutral-300 bg-neutral-100 font-semibold text-neutral-900"
411
- : "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
410
+ ? "border-dpe-ink-300 bg-dpe-ink-100 font-dpe-semibold text-dpe-ink-900"
411
+ : "border-dpe-ink-200 bg-dpe-surface text-dpe-ink-600 hover:bg-dpe-ink-50"
412
412
  }`}
413
413
  >
414
414
  {t(`detailPage.table.aligns.${align}`)}
@@ -416,7 +416,7 @@ export const TableInspector = observer(function TableInspector({
416
416
  ))}
417
417
  </div>
418
418
  </div>
419
- <p className="text-[11px] text-neutral-400">
419
+ <p className="text-[11px] text-dpe-ink-400">
420
420
  {t("detailPage.table.alignHint")}
421
421
  </p>
422
422
  </div>
@@ -490,7 +490,7 @@ export const TableInspector = observer(function TableInspector({
490
490
  ) : null}
491
491
  </div>
492
492
  <div className="flex items-center gap-2">
493
- <span className="w-20 shrink-0 text-[11px] text-neutral-500">
493
+ <span className="w-20 shrink-0 text-[11px] text-dpe-ink-500">
494
494
  {t("detailPage.table.firstColor")}
495
495
  </span>
496
496
  <ColorInput
@@ -499,7 +499,7 @@ export const TableInspector = observer(function TableInspector({
499
499
  />
500
500
  </div>
501
501
  <div className="flex items-center gap-2">
502
- <span className="w-20 shrink-0 text-[11px] text-neutral-500">
502
+ <span className="w-20 shrink-0 text-[11px] text-dpe-ink-500">
503
503
  {t("detailPage.table.textColor")}
504
504
  </span>
505
505
  <ColorInput
@@ -530,7 +530,7 @@ export const TableInspector = observer(function TableInspector({
530
530
  <Section title={t("detailPage.table.type")}>
531
531
  <div className="flex flex-col gap-2">
532
532
  <div className="flex items-center gap-2">
533
- <span className="w-20 shrink-0 text-[11px] text-neutral-500">
533
+ <span className="w-20 shrink-0 text-[11px] text-dpe-ink-500">
534
534
  {t("detailPage.table.fontSize")}
535
535
  </span>
536
536
  <NumberField
@@ -542,7 +542,7 @@ export const TableInspector = observer(function TableInspector({
542
542
  />
543
543
  </div>
544
544
  <div className="flex items-center gap-2">
545
- <span className="w-20 shrink-0 text-[11px] text-neutral-500">
545
+ <span className="w-20 shrink-0 text-[11px] text-dpe-ink-500">
546
546
  {t("detailPage.table.padX")}
547
547
  </span>
548
548
  <NumberField
@@ -554,7 +554,7 @@ export const TableInspector = observer(function TableInspector({
554
554
  />
555
555
  </div>
556
556
  <div className="flex items-center gap-2">
557
- <span className="w-20 shrink-0 text-[11px] text-neutral-500">
557
+ <span className="w-20 shrink-0 text-[11px] text-dpe-ink-500">
558
558
  {t("detailPage.table.padY")}
559
559
  </span>
560
560
  <NumberField
@@ -572,12 +572,12 @@ export const TableInspector = observer(function TableInspector({
572
572
  <button
573
573
  type="button"
574
574
  onClick={() => detachTable(el)}
575
- className="flex w-full items-center justify-center gap-1.5 rounded-md border border-neutral-200 py-1.5 text-xs text-neutral-600 hover:bg-neutral-50"
575
+ className="flex w-full items-center justify-center gap-1.5 rounded-dpe-md border border-dpe-ink-200 py-1.5 text-xs text-dpe-ink-600 hover:bg-dpe-ink-50"
576
576
  >
577
577
  <Unlink className="h-3.5 w-3.5" />
578
578
  {t("detailPage.table.detachAction")}
579
579
  </button>
580
- <p className="mt-1.5 text-[11px] text-neutral-400">
580
+ <p className="mt-1.5 text-[11px] text-dpe-ink-400">
581
581
  {t("detailPage.table.detachHint")}
582
582
  </p>
583
583
  </Section>