@leviosa-ai/detail-page-editor 0.14.2 → 0.15.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 (65) hide show
  1. package/components/cardnews/color-input.tsx +11 -11
  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 +6 -6
  6. package/components/detail-page/canvas-insert-toolbar.tsx +10 -10
  7. package/components/detail-page/canvas-selection-tools.tsx +5 -5
  8. package/components/detail-page/chart-inspector.tsx +23 -23
  9. package/components/detail-page/design-reference-picker.tsx +22 -22
  10. package/components/detail-page/detail-page-basic-shapes.tsx +4 -4
  11. package/components/detail-page/detail-page-brand-gifs-panel.tsx +10 -10
  12. package/components/detail-page/detail-page-brand-panel-header.tsx +4 -4
  13. package/components/detail-page/detail-page-charts-panel.tsx +4 -4
  14. package/components/detail-page/detail-page-decorations-panel.tsx +5 -5
  15. package/components/detail-page/detail-page-download-dialog.tsx +19 -19
  16. package/components/detail-page/detail-page-editor.tsx +14 -14
  17. package/components/detail-page/detail-page-elements-panel.tsx +4 -4
  18. package/components/detail-page/detail-page-font-picker.tsx +15 -15
  19. package/components/detail-page/detail-page-frame-header.tsx +2 -2
  20. package/components/detail-page/detail-page-history-buttons.tsx +3 -3
  21. package/components/detail-page/detail-page-icons-panel.tsx +13 -13
  22. package/components/detail-page/detail-page-konva-json-preview.tsx +9 -9
  23. package/components/detail-page/detail-page-layers-panel.tsx +14 -14
  24. package/components/detail-page/detail-page-my-images-panel.tsx +22 -22
  25. package/components/detail-page/detail-page-my-shapes-panel.tsx +8 -8
  26. package/components/detail-page/detail-page-page-toolbar.tsx +2 -2
  27. package/components/detail-page/detail-page-pages-panel.tsx +16 -16
  28. package/components/detail-page/detail-page-pages-timeline.tsx +4 -4
  29. package/components/detail-page/detail-page-photos-panel.tsx +13 -13
  30. package/components/detail-page/detail-page-properties-panel.tsx +57 -57
  31. package/components/detail-page/detail-page-qr-panel.tsx +13 -13
  32. package/components/detail-page/detail-page-references-panel.tsx +9 -9
  33. package/components/detail-page/detail-page-sidebar-sections.tsx +1 -1
  34. package/components/detail-page/detail-page-tables-panel.tsx +4 -4
  35. package/components/detail-page/detail-page-text-panel.tsx +4 -4
  36. package/components/detail-page/edit-quota-ui.tsx +7 -7
  37. package/components/detail-page/element-recents-strip.tsx +6 -6
  38. package/components/detail-page/fill-control.tsx +12 -12
  39. package/components/detail-page/find-replace-panel.tsx +8 -8
  40. package/components/detail-page/frame-drag-grip.tsx +1 -1
  41. package/components/detail-page/frame-drag-layer.tsx +2 -2
  42. package/components/detail-page/generation-progress-fill.tsx +7 -7
  43. package/components/detail-page/gif-effect-picker.tsx +9 -9
  44. package/components/detail-page/group-prompt-edit-panel.tsx +6 -6
  45. package/components/detail-page/image-crop-overlay.tsx +13 -13
  46. package/components/detail-page/inspector-controls.tsx +9 -9
  47. package/components/detail-page/leviosa-canvas-workspace.tsx +3 -3
  48. package/components/detail-page/panel-search-input.tsx +2 -2
  49. package/components/detail-page/prompt-edit-panel.tsx +6 -6
  50. package/components/detail-page/section-reauthor-controller.tsx +2 -2
  51. package/components/detail-page/selection-quick-toolbar.tsx +6 -6
  52. package/components/detail-page/spec-prompt-edit-panel.tsx +7 -7
  53. package/components/detail-page/svg-prompt-edit-panel.tsx +6 -6
  54. package/components/detail-page/table-inspector.tsx +24 -24
  55. package/components/ui/button.tsx +13 -13
  56. package/components/ui/command.tsx +10 -10
  57. package/components/ui/dialog.tsx +7 -7
  58. package/components/ui/label.tsx +1 -1
  59. package/components/ui/select.tsx +9 -9
  60. package/components/ui/slider.tsx +3 -3
  61. package/components/ui/switch.tsx +4 -4
  62. package/components/ui/tooltip.tsx +4 -4
  63. package/package.json +1 -1
  64. package/styles/canvas-bridge.css +10 -10
  65. package/styles/tokens.css +70 -61
@@ -161,7 +161,7 @@ export function ColorInput({
161
161
  <PopoverPrimitive.Trigger asChild>
162
162
  <button
163
163
  type="button"
164
- className={`${swatchPx} rounded-dpe-md border border-dpe-ink-200 cursor-pointer shrink-0 transition-colors hover:border-dpe-ink-400`}
164
+ className={`${swatchPx} rounded-le-md border border-le-ink-200 cursor-pointer shrink-0 transition-colors hover:border-le-ink-400`}
165
165
  style={{
166
166
  backgroundColor: isTransparentValue ? undefined : safeColor,
167
167
  backgroundImage: isTransparentValue ? CHECKER_BG : undefined,
@@ -177,17 +177,17 @@ export function ColorInput({
177
177
  어긋난 자리에 떴다.
178
178
 
179
179
  포털은 body 로 나가므로 편집기 뿌리 딱지를 다시 달아 준다 — 안 달면 소비자 앱이
180
- `[data-dpe-root]` 로 좁혀 둔 색·모서리 토큰이 이 팝오버에만 안 먹어서, 각진
180
+ `[data-le-root]` 로 좁혀 둔 색·모서리 토큰이 이 팝오버에만 안 먹어서, 각진
181
181
  인스펙터 위에 둥근 카드가 하나 뜬다. */}
182
182
  <PopoverPrimitive.Portal>
183
183
  <PopoverPrimitive.Content
184
184
  data-layer-panel
185
- data-dpe-root=""
185
+ data-le-root=""
186
186
  side="bottom"
187
187
  align="start"
188
188
  sideOffset={6}
189
189
  collisionPadding={8}
190
- className="z-[200] w-56 bg-dpe-surface border border-dpe-ink-200 rounded-dpe-md shadow-[0_12px_40px_rgba(0,0,0,0.16)] p-3 space-y-2.5"
190
+ className="z-[200] w-56 bg-le-surface border border-le-ink-200 rounded-le-md shadow-[0_12px_40px_rgba(0,0,0,0.16)] p-3 space-y-2.5"
191
191
  >
192
192
  {/* Transparent + Preset palette */}
193
193
  <div className="grid grid-cols-6 gap-1.5">
@@ -195,7 +195,7 @@ export function ColorInput({
195
195
  <button
196
196
  type="button"
197
197
  onClick={() => selectColor("transparent")}
198
- className={`w-7 h-7 rounded-dpe-md border transition-colors relative overflow-hidden ${
198
+ className={`w-7 h-7 rounded-le-md border transition-colors relative overflow-hidden ${
199
199
  isTransparentValue ? "border-[var(--color-primary)] ring-1 ring-[var(--color-primary)]" : "border-[var(--color-border)]"
200
200
  }`}
201
201
  style={{ backgroundImage: CHECKER_BG, backgroundSize: "8px 8px" }}
@@ -203,7 +203,7 @@ export function ColorInput({
203
203
  >
204
204
  {/* Diagonal line to indicate "none/transparent" */}
205
205
  <span className="absolute inset-0 flex items-center justify-center">
206
- <span className="block w-[130%] h-px bg-dpe-danger-500 rotate-45" />
206
+ <span className="block w-[130%] h-px bg-le-danger-500 rotate-45" />
207
207
  </span>
208
208
  </button>
209
209
  )}
@@ -212,7 +212,7 @@ export function ColorInput({
212
212
  key={color}
213
213
  type="button"
214
214
  onClick={() => selectColor(color)}
215
- className={`w-7 h-7 rounded-dpe-md border transition-colors ${
215
+ className={`w-7 h-7 rounded-le-md border transition-colors ${
216
216
  !isTransparentValue && safeColor.toUpperCase() === color ? "border-[var(--color-primary)] ring-1 ring-[var(--color-primary)]" : "border-[var(--color-border)]"
217
217
  }`}
218
218
  style={{ backgroundColor: color }}
@@ -231,13 +231,13 @@ export function ColorInput({
231
231
  key={color}
232
232
  type="button"
233
233
  onClick={() => selectColor(color)}
234
- className={`size-5 rounded-dpe-md border transition-colors ${
234
+ className={`size-5 rounded-le-md border transition-colors ${
235
235
  !isTransparentValue && safeColor.toUpperCase() === color ? "border-[var(--color-primary)] ring-1 ring-[var(--color-primary)]" : "border-[var(--color-border)]"
236
236
  }`}
237
237
  style={{ backgroundColor: color }}
238
238
  />
239
239
  ) : (
240
- <span key={idx} className="size-5 rounded-dpe-md border border-transparent" />
240
+ <span key={idx} className="size-5 rounded-le-md border border-transparent" />
241
241
  );
242
242
  })}
243
243
  </div>
@@ -249,7 +249,7 @@ export function ColorInput({
249
249
  <button
250
250
  type="button"
251
251
  onClick={openEyeDropper}
252
- className="flex shrink-0 items-center gap-1 rounded-dpe-md px-1.5 py-1.5 text-[11px] text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface)] hover:text-[var(--color-text-primary)]"
252
+ className="flex shrink-0 items-center gap-1 rounded-le-md px-1.5 py-1.5 text-[11px] text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface)] hover:text-[var(--color-text-primary)]"
253
253
  title={t("cardnews.colorInput.eyedropper")}
254
254
  >
255
255
  <Pipette className="w-3.5 h-3.5" />
@@ -262,7 +262,7 @@ export function ColorInput({
262
262
  onChange={(e) => setHexInput(e.target.value)}
263
263
  onKeyDown={(e) => e.key === "Enter" && handleHexSubmit()}
264
264
  placeholder="#000000"
265
- className="min-w-0 flex-1 rounded-dpe-md border border-dpe-ink-200 bg-dpe-surface px-2 py-1.5 text-xs font-mono text-dpe-ink-900 focus:outline-none focus:border-dpe-ink-400"
265
+ className="min-w-0 flex-1 rounded-le-md border border-le-ink-200 bg-le-surface px-2 py-1.5 text-xs font-mono text-le-ink-900 focus:outline-none focus:border-le-ink-400"
266
266
  />
267
267
  </div>
268
268
  </PopoverPrimitive.Content>
@@ -487,8 +487,8 @@ export function AiGeneratePanel({
487
487
  onClick={() => setMode("image")}
488
488
  className={
489
489
  mode === "image"
490
- ? "flex h-10 items-center justify-center gap-2 rounded-dpe-md bg-dpe-ink-900 text-sm font-dpe-semibold text-dpe-on-accent"
491
- : "flex h-10 items-center justify-center gap-2 rounded-dpe-md border border-dpe-ink-200 text-sm font-dpe-medium text-dpe-ink-600 hover:bg-dpe-ink-50"
490
+ ? "flex h-10 items-center justify-center gap-2 rounded-le-md bg-le-ink-900 text-sm font-le-semibold text-le-on-accent"
491
+ : "flex h-10 items-center justify-center gap-2 rounded-le-md border border-le-ink-200 text-sm font-le-medium text-le-ink-600 hover:bg-le-ink-50"
492
492
  }
493
493
  >
494
494
  <Sparkles aria-hidden="true" size={15} />
@@ -499,8 +499,8 @@ export function AiGeneratePanel({
499
499
  onClick={() => setMode("gif")}
500
500
  className={
501
501
  isGif
502
- ? "flex h-10 items-center justify-center gap-2 rounded-dpe-md bg-dpe-ink-900 text-sm font-dpe-semibold text-dpe-on-accent"
503
- : "flex h-10 items-center justify-center gap-2 rounded-dpe-md border border-dpe-ink-200 text-sm font-dpe-medium text-dpe-ink-600 hover:bg-dpe-ink-50"
502
+ ? "flex h-10 items-center justify-center gap-2 rounded-le-md bg-le-ink-900 text-sm font-le-semibold text-le-on-accent"
503
+ : "flex h-10 items-center justify-center gap-2 rounded-le-md border border-le-ink-200 text-sm font-le-medium text-le-ink-600 hover:bg-le-ink-50"
504
504
  }
505
505
  >
506
506
  <Sparkles aria-hidden="true" size={15} />
@@ -511,12 +511,12 @@ export function AiGeneratePanel({
511
511
 
512
512
  {/* GIF 모드: 배경 투명 토글(기본 ON). 상세페이지 움짤은 대체로 배경 투명. */}
513
513
  {isGif ? (
514
- <label className="flex cursor-pointer items-center justify-between rounded-dpe-md border border-dpe-ink-200 px-3 py-2.5">
514
+ <label className="flex cursor-pointer items-center justify-between rounded-le-md border border-le-ink-200 px-3 py-2.5">
515
515
  <span className="flex flex-col">
516
- <span className="text-sm font-dpe-medium text-dpe-ink-800">
516
+ <span className="text-sm font-le-medium text-le-ink-800">
517
517
  {t("detailPage.aiGenerate.transparentBg")}
518
518
  </span>
519
- <span className="text-[11px] leading-4 text-dpe-ink-500">
519
+ <span className="text-[11px] leading-4 text-le-ink-500">
520
520
  {t("detailPage.aiGenerate.transparentBgHint")}
521
521
  </span>
522
522
  </span>
@@ -524,7 +524,7 @@ export function AiGeneratePanel({
524
524
  type="checkbox"
525
525
  checked={transparent}
526
526
  onChange={(e) => setTransparent(e.target.checked)}
527
- className="h-4 w-4 accent-dpe-ink-900"
527
+ className="h-4 w-4 accent-le-ink-900"
528
528
  />
529
529
  </label>
530
530
  ) : null}
@@ -532,23 +532,23 @@ export function AiGeneratePanel({
532
532
  {/* 이미지 모델(티어) 선택 — GIF 모드는 단일 모델이라 숨긴다. */}
533
533
  <div hidden={isGif}>
534
534
  <div className="mb-2 flex items-center gap-1">
535
- <p className="text-xs font-dpe-medium text-dpe-ink-500">{t("detailPage.aiGenerate.imageModel")}</p>
535
+ <p className="text-xs font-le-medium text-le-ink-500">{t("detailPage.aiGenerate.imageModel")}</p>
536
536
  <Tooltip>
537
537
  <TooltipTrigger asChild>
538
538
  <button
539
539
  type="button"
540
- className="flex h-4 w-4 items-center justify-center rounded-full text-dpe-ink-400 hover:text-dpe-ink-600"
540
+ className="flex h-4 w-4 items-center justify-center rounded-full text-le-ink-400 hover:text-le-ink-600"
541
541
  aria-label={t("detailPage.aiGenerate.modelQualityHelp")}
542
542
  >
543
543
  <Info aria-hidden="true" size={13} />
544
544
  </button>
545
545
  </TooltipTrigger>
546
546
  <TooltipContent side="left" className="max-w-[240px] text-left">
547
- <p className="mb-1 font-dpe-semibold">{t("detailPage.aiGenerate.modelTooltipTitle")}</p>
547
+ <p className="mb-1 font-le-semibold">{t("detailPage.aiGenerate.modelTooltipTitle")}</p>
548
548
  <ul className="space-y-1">
549
549
  {tierOptions.map((tk) => (
550
550
  <li key={tk}>
551
- <span className="font-dpe-medium">{IMAGE_TIER_META[tk].label}</span>
551
+ <span className="font-le-medium">{IMAGE_TIER_META[tk].label}</span>
552
552
  <span className="opacity-80"> · {IMAGE_TIER_META[tk].quality}</span>
553
553
  </li>
554
554
  ))}
@@ -567,20 +567,20 @@ export function AiGeneratePanel({
567
567
  return (
568
568
  <SelectItem key={tk} value={tk} className="items-start py-2">
569
569
  <div className="flex flex-col gap-0.5 pr-2">
570
- <span className="flex items-center gap-1.5 text-sm font-dpe-medium text-dpe-ink-900">
570
+ <span className="flex items-center gap-1.5 text-sm font-le-medium text-le-ink-900">
571
571
  {meta.label}
572
572
  {meta.badge ? (
573
- <span className="rounded-full bg-dpe-ink-900 px-1.5 py-px text-[10px] font-dpe-semibold text-dpe-on-accent">
573
+ <span className="rounded-full bg-le-ink-900 px-1.5 py-px text-[10px] font-le-semibold text-le-on-accent">
574
574
  {meta.badge}
575
575
  </span>
576
576
  ) : null}
577
577
  {typeof cost === "number" && cost > 0 ? (
578
- <span className="text-[11px] font-dpe-normal text-dpe-ink-500">
578
+ <span className="text-[11px] font-le-normal text-le-ink-500">
579
579
  · {cost}cr
580
580
  </span>
581
581
  ) : null}
582
582
  </span>
583
- <span className="text-[11px] leading-4 text-dpe-ink-500">
583
+ <span className="text-[11px] leading-4 text-le-ink-500">
584
584
  {meta.quality}
585
585
  </span>
586
586
  </div>
@@ -589,7 +589,7 @@ export function AiGeneratePanel({
589
589
  })}
590
590
  </SelectContent>
591
591
  </Select>
592
- <p className="mt-1.5 text-[11px] leading-4 text-dpe-ink-500">
592
+ <p className="mt-1.5 text-[11px] leading-4 text-le-ink-500">
593
593
  {IMAGE_TIER_META[activeTier].description}
594
594
  </p>
595
595
  </div>
@@ -601,10 +601,10 @@ export function AiGeneratePanel({
601
601
  - 좌측 자유 생성: GIF는 참조 필수(image→video), 이미지는 선택 업로드. */}
602
602
  {hasImplicitReference && implicitReferenceSrc ? (
603
603
  <div>
604
- <p className="mb-2 text-xs font-dpe-medium text-dpe-ink-500">
604
+ <p className="mb-2 text-xs font-le-medium text-le-ink-500">
605
605
  {t("detailPage.aiGenerate.currentImageLabel")}
606
606
  </p>
607
- <div className="relative overflow-hidden rounded-dpe-lg border border-dpe-ink-200 bg-dpe-ink-50">
607
+ <div className="relative overflow-hidden rounded-le-lg border border-le-ink-200 bg-le-ink-50">
608
608
  {/* crossOrigin: S3 이미지를 Origin 없이 그리면 ACAO 없는 응답이 캐시돼
609
609
  캔버스 crossOrigin 로드를 오염시킨다. 미리보기도 Origin을 실어 로드한다. */}
610
610
  {/* eslint-disable-next-line @next/next/no-img-element */}
@@ -615,7 +615,7 @@ export function AiGeneratePanel({
615
615
  className="h-36 w-full object-contain"
616
616
  />
617
617
  </div>
618
- <p className="mt-1.5 text-[11px] leading-4 text-dpe-ink-500">
618
+ <p className="mt-1.5 text-[11px] leading-4 text-le-ink-500">
619
619
  {isGif
620
620
  ? t("detailPage.aiGenerate.currentImageGifHint")
621
621
  : t("detailPage.aiGenerate.currentImageHint")}
@@ -627,7 +627,7 @@ export function AiGeneratePanel({
627
627
  type="button"
628
628
  onClick={() => void openAnnotate()}
629
629
  disabled={annotatePreparing || loading}
630
- className="mt-2 inline-flex h-9 w-full items-center justify-center gap-1.5 rounded-dpe-md border border-dpe-ink-200 text-xs font-dpe-medium text-dpe-ink-700 hover:bg-dpe-ink-50 disabled:cursor-not-allowed disabled:opacity-40"
630
+ className="mt-2 inline-flex h-9 w-full items-center justify-center gap-1.5 rounded-le-md border border-le-ink-200 text-xs font-le-medium text-le-ink-700 hover:bg-le-ink-50 disabled:cursor-not-allowed disabled:opacity-40"
631
631
  >
632
632
  {annotatePreparing ? (
633
633
  <Loader2 aria-hidden="true" className="animate-spin" size={14} />
@@ -640,29 +640,29 @@ export function AiGeneratePanel({
640
640
  </div>
641
641
  ) : (
642
642
  <div hidden={isGif && hasImplicitReference}>
643
- <p className="mb-2 text-xs font-dpe-medium text-dpe-ink-500">
643
+ <p className="mb-2 text-xs font-le-medium text-le-ink-500">
644
644
  {t("detailPage.aiGenerate.referenceLabel")}{" "}
645
645
  {isGif ? (
646
- <span className="text-dpe-danger-500">*</span>
646
+ <span className="text-le-danger-500">*</span>
647
647
  ) : (
648
648
  t("detailPage.aiGenerate.referenceOptional")
649
649
  )}
650
650
  </p>
651
651
  {refUrl ? (
652
- <div className="relative overflow-hidden rounded-dpe-lg border border-dpe-ink-200">
652
+ <div className="relative overflow-hidden rounded-le-lg border border-le-ink-200">
653
653
  {/* eslint-disable-next-line @next/next/no-img-element */}
654
654
  <img src={refUrl} crossOrigin="anonymous" alt={t("detailPage.aiGenerate.referenceAlt")} className="h-36 w-full object-cover" />
655
655
  <button
656
656
  type="button"
657
657
  onClick={() => setRefUrl(null)}
658
- className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-full bg-dpe-scrim/60 text-dpe-on-accent hover:bg-dpe-scrim/80"
658
+ className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-full bg-le-scrim/60 text-le-on-accent hover:bg-le-scrim/80"
659
659
  aria-label={t("detailPage.aiGenerate.removeReference")}
660
660
  >
661
661
  <X aria-hidden="true" size={15} />
662
662
  </button>
663
663
  </div>
664
664
  ) : (
665
- <label className="flex h-36 cursor-pointer flex-col items-center justify-center gap-2 rounded-dpe-lg border border-dashed border-dpe-ink-300 text-dpe-ink-400 hover:border-dpe-ink-400 hover:text-dpe-ink-500">
665
+ <label className="flex h-36 cursor-pointer flex-col items-center justify-center gap-2 rounded-le-lg border border-dashed border-le-ink-300 text-le-ink-400 hover:border-le-ink-400 hover:text-le-ink-500">
666
666
  {refUploading ? (
667
667
  <Loader2 aria-hidden="true" className="animate-spin" size={22} />
668
668
  ) : (
@@ -682,35 +682,35 @@ export function AiGeneratePanel({
682
682
 
683
683
  {/* 프롬프트 */}
684
684
  <div className="flex min-h-0 flex-1 flex-col">
685
- <label className="mb-2 text-xs font-dpe-medium text-dpe-ink-500">
686
- {t("detailPage.aiGenerate.prompt")} <span className="text-dpe-danger-500">*</span>
685
+ <label className="mb-2 text-xs font-le-medium text-le-ink-500">
686
+ {t("detailPage.aiGenerate.prompt")} <span className="text-le-danger-500">*</span>
687
687
  </label>
688
688
  <textarea
689
689
  value={prompt}
690
690
  onChange={(e) => setPrompt(e.target.value)}
691
691
  placeholder={t("detailPage.aiGenerate.promptPlaceholder")}
692
- className="min-h-[160px] flex-1 resize-none rounded-dpe-lg border border-dpe-ink-200 p-3 text-sm text-dpe-ink-800 outline-none placeholder:text-dpe-ink-400 focus:border-dpe-ink-400"
692
+ className="min-h-[160px] flex-1 resize-none rounded-le-lg border border-le-ink-200 p-3 text-sm text-le-ink-800 outline-none placeholder:text-le-ink-400 focus:border-le-ink-400"
693
693
  />
694
694
  </div>
695
695
 
696
696
  {error ? (
697
- <p className="text-xs font-dpe-medium text-dpe-danger-600">{error}</p>
697
+ <p className="text-xs font-le-medium text-le-danger-600">{error}</p>
698
698
  ) : null}
699
699
 
700
700
  {/* 1.5× 안전 마진 미달(또는 생성 중 크레딧 부족) → 차단 안내 + 추가구매 CTA */}
701
701
  {activeCost > 0 && (blocked || insufficient) ? (
702
- <div className="rounded-dpe-lg border border-dpe-warn-200 bg-dpe-warn-50 px-3 py-2.5 text-center">
703
- <p className="text-[13px] font-dpe-medium text-dpe-warn-800">
702
+ <div className="rounded-le-lg border border-le-warn-200 bg-le-warn-50 px-3 py-2.5 text-center">
703
+ <p className="text-[13px] font-le-medium text-le-warn-800">
704
704
  {t("detailPage.aiGenerate.insufficientCreditsTitle")}
705
705
  </p>
706
- <p className="mt-0.5 text-[11px] leading-4 text-dpe-warn-700">
706
+ <p className="mt-0.5 text-[11px] leading-4 text-le-warn-700">
707
707
  {t("detailPage.aiGenerate.insufficientCreditsHint", { required, balance: creditBalance })}
708
708
  </p>
709
709
  <button
710
710
  type="button"
711
711
  onClick={onBuyCredits}
712
712
  disabled={!onBuyCredits}
713
- className="mt-2 inline-flex items-center gap-1.5 rounded-dpe-md bg-dpe-ink-900 px-3 py-1.5 text-xs font-dpe-medium text-dpe-on-accent disabled:opacity-40"
713
+ className="mt-2 inline-flex items-center gap-1.5 rounded-le-md bg-le-ink-900 px-3 py-1.5 text-xs font-le-medium text-le-on-accent disabled:opacity-40"
714
714
  >
715
715
  <Sparkles aria-hidden="true" size={13} />
716
716
  {t("detailPage.aiGenerate.buyCredits")}
@@ -719,7 +719,7 @@ export function AiGeneratePanel({
719
719
  ) : null}
720
720
 
721
721
  {gifMissingRef ? (
722
- <p className="text-xs font-dpe-medium text-dpe-ink-500">
722
+ <p className="text-xs font-le-medium text-le-ink-500">
723
723
  {t("detailPage.aiGenerate.gifNeedsReference")}
724
724
  </p>
725
725
  ) : null}
@@ -729,7 +729,7 @@ export function AiGeneratePanel({
729
729
  onClick={handleGenerate}
730
730
  disabled={loading || !prompt.trim() || blocked || gifMissingRef}
731
731
  className={
732
- "relative flex h-11 items-center justify-center gap-2 overflow-hidden rounded-dpe-lg bg-dpe-ink-900 text-sm font-dpe-semibold text-dpe-on-accent " +
732
+ "relative flex h-11 items-center justify-center gap-2 overflow-hidden rounded-le-lg bg-le-ink-900 text-sm font-le-semibold text-le-on-accent " +
733
733
  (loading
734
734
  ? "cursor-progress"
735
735
  : "disabled:cursor-not-allowed disabled:opacity-40")
@@ -755,7 +755,7 @@ export function AiGeneratePanel({
755
755
  ? t("detailPage.aiGenerate.replaceImage")
756
756
  : t("detailPage.aiGenerate.generate")}
757
757
  {activeCost > 0 && !loading ? (
758
- <span className="text-xs font-dpe-normal opacity-80">· {activeCost}cr</span>
758
+ <span className="text-xs font-le-normal opacity-80">· {activeCost}cr</span>
759
759
  ) : null}
760
760
  </span>
761
761
  </button>
@@ -677,7 +677,7 @@ export const AnnotationCanvas = forwardRef<
677
677
  return (
678
678
  <div
679
679
  ref={wrapRef}
680
- className="relative h-full w-full overflow-hidden bg-dpe-ink-100"
680
+ className="relative h-full w-full overflow-hidden bg-le-ink-100"
681
681
  >
682
682
  {/* eslint-disable-next-line @next/next/no-img-element */}
683
683
  <img
@@ -792,7 +792,7 @@ export const AnnotationCanvas = forwardRef<
792
792
  ) : null}
793
793
 
794
794
  <div className="pointer-events-none absolute inset-x-0 bottom-3 flex justify-center">
795
- <div className="pointer-events-auto flex items-center gap-0.5 rounded-dpe-xl border border-dpe-ink-200 bg-dpe-surface/95 px-1.5 py-1 shadow-lg backdrop-blur-sm">
795
+ <div className="pointer-events-auto flex items-center gap-0.5 rounded-le-xl border border-le-ink-200 bg-le-surface/95 px-1.5 py-1 shadow-lg backdrop-blur-sm">
796
796
  {TOOLS.map(([id, label, icon]) => (
797
797
  <button
798
798
  key={id}
@@ -802,16 +802,16 @@ export const AnnotationCanvas = forwardRef<
802
802
  aria-pressed={tool === id}
803
803
  onClick={() => setTool(id)}
804
804
  className={[
805
- "flex h-8 w-8 items-center justify-center rounded-dpe-lg transition-colors",
805
+ "flex h-8 w-8 items-center justify-center rounded-le-lg transition-colors",
806
806
  tool === id
807
- ? "bg-dpe-ink-900 text-dpe-on-accent"
808
- : "text-dpe-ink-500 hover:bg-dpe-ink-100 hover:text-dpe-ink-900",
807
+ ? "bg-le-ink-900 text-le-on-accent"
808
+ : "text-le-ink-500 hover:bg-le-ink-100 hover:text-le-ink-900",
809
809
  ].join(" ")}
810
810
  >
811
811
  {icon}
812
812
  </button>
813
813
  ))}
814
- <span className="mx-1 h-5 w-px bg-dpe-ink-200" />
814
+ <span className="mx-1 h-5 w-px bg-le-ink-200" />
815
815
  {COLORS.map((c) => (
816
816
  <button
817
817
  key={c}
@@ -831,14 +831,14 @@ export const AnnotationCanvas = forwardRef<
831
831
  />
832
832
  </button>
833
833
  ))}
834
- <span className="mx-1 h-5 w-px bg-dpe-ink-200" />
834
+ <span className="mx-1 h-5 w-px bg-le-ink-200" />
835
835
  <button
836
836
  type="button"
837
837
  title={labels?.undo ?? "실행 취소"}
838
838
  aria-label={labels?.undo ?? "실행 취소"}
839
839
  disabled={!history.past.length}
840
840
  onClick={undo}
841
- className="flex h-8 w-8 items-center justify-center rounded-dpe-lg text-dpe-ink-500 transition-colors hover:bg-dpe-ink-100 hover:text-dpe-ink-900 disabled:cursor-not-allowed disabled:opacity-30"
841
+ className="flex h-8 w-8 items-center justify-center rounded-le-lg text-le-ink-500 transition-colors hover:bg-le-ink-100 hover:text-le-ink-900 disabled:cursor-not-allowed disabled:opacity-30"
842
842
  >
843
843
  <Undo2 size={16} />
844
844
  </button>
@@ -848,7 +848,7 @@ export const AnnotationCanvas = forwardRef<
848
848
  aria-label={labels?.redo ?? "다시 실행"}
849
849
  disabled={!history.future.length}
850
850
  onClick={redo}
851
- className="flex h-8 w-8 items-center justify-center rounded-dpe-lg text-dpe-ink-500 transition-colors hover:bg-dpe-ink-100 hover:text-dpe-ink-900 disabled:cursor-not-allowed disabled:opacity-30"
851
+ className="flex h-8 w-8 items-center justify-center rounded-le-lg text-le-ink-500 transition-colors hover:bg-le-ink-100 hover:text-le-ink-900 disabled:cursor-not-allowed disabled:opacity-30"
852
852
  >
853
853
  <Redo2 size={16} />
854
854
  </button>
@@ -210,18 +210,18 @@ export function AnnotationDialog({
210
210
  const canSubmit = !busy && (hasMarks || instruction.trim().length > 0);
211
211
 
212
212
  return (
213
- <div className="fixed inset-0 z-[100] flex items-center justify-center bg-dpe-ink-900/60 p-4">
213
+ <div className="fixed inset-0 z-[100] flex items-center justify-center bg-le-ink-900/60 p-4">
214
214
  <div
215
215
  role="dialog"
216
216
  aria-modal="true"
217
217
  aria-label={title}
218
- className="flex h-full max-h-[92vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl bg-dpe-surface shadow-2xl"
218
+ className="flex h-full max-h-[92vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl bg-le-surface shadow-2xl"
219
219
  >
220
- <div className="flex items-start justify-between gap-4 border-b border-dpe-ink-200 px-5 py-3">
220
+ <div className="flex items-start justify-between gap-4 border-b border-le-ink-200 px-5 py-3">
221
221
  <div className="min-w-0">
222
- <h2 className="text-sm font-dpe-semibold text-dpe-ink-900">{title}</h2>
222
+ <h2 className="text-sm font-le-semibold text-le-ink-900">{title}</h2>
223
223
  {description ? (
224
- <p className="mt-0.5 text-xs text-dpe-ink-500">{description}</p>
224
+ <p className="mt-0.5 text-xs text-le-ink-500">{description}</p>
225
225
  ) : null}
226
226
  </div>
227
227
  <button
@@ -229,7 +229,7 @@ export function AnnotationDialog({
229
229
  aria-label={t("detailPage.annotate.close", { defaultValue: "닫기" })}
230
230
  onClick={onClose}
231
231
  disabled={busy}
232
- className="flex h-8 w-8 shrink-0 items-center justify-center rounded-dpe-lg text-dpe-ink-400 transition-colors hover:bg-dpe-ink-100 hover:text-dpe-ink-900 disabled:opacity-40"
232
+ className="flex h-8 w-8 shrink-0 items-center justify-center rounded-le-lg text-le-ink-400 transition-colors hover:bg-le-ink-100 hover:text-le-ink-900 disabled:opacity-40"
233
233
  >
234
234
  <X size={16} />
235
235
  </button>
@@ -255,7 +255,7 @@ export function AnnotationDialog({
255
255
  />
256
256
  </div>
257
257
 
258
- <div className="border-t border-dpe-ink-200 px-5 py-3">
258
+ <div className="border-t border-le-ink-200 px-5 py-3">
259
259
  <textarea
260
260
  value={instruction}
261
261
  onChange={(e) => setInstruction(e.target.value)}
@@ -268,16 +268,16 @@ export function AnnotationDialog({
268
268
  "표시한 곳을 어떻게 바꿀지 적어주세요. 그림 위에 손글씨로 적어도 돼요.",
269
269
  })
270
270
  }
271
- className="w-full resize-none rounded-dpe-lg border border-dpe-ink-200 px-3 py-2 text-sm text-dpe-ink-900 outline-none placeholder:text-dpe-ink-400 focus:border-dpe-ink-400 disabled:bg-dpe-ink-50"
271
+ className="w-full resize-none rounded-le-lg border border-le-ink-200 px-3 py-2 text-sm text-le-ink-900 outline-none placeholder:text-le-ink-400 focus:border-le-ink-400 disabled:bg-le-ink-50"
272
272
  />
273
273
  {maxReferences > 0 ? (
274
274
  <div className="mt-2 flex flex-wrap items-start gap-2">
275
275
  {references.map((ref, index) => (
276
276
  <div
277
277
  key={ref.id}
278
- className="flex items-start gap-2 rounded-dpe-lg border border-dpe-ink-200 p-1.5"
278
+ className="flex items-start gap-2 rounded-le-lg border border-le-ink-200 p-1.5"
279
279
  >
280
- <div className="relative h-14 w-14 shrink-0 overflow-hidden rounded-dpe-md bg-dpe-ink-100">
280
+ <div className="relative h-14 w-14 shrink-0 overflow-hidden rounded-le-md bg-le-ink-100">
281
281
  {/* 붙인 사진은 data URI라 next/image 로 최적화할 것이 없다. */}
282
282
  {/* eslint-disable-next-line @next/next/no-img-element */}
283
283
  <img
@@ -289,7 +289,7 @@ export function AnnotationDialog({
289
289
  />
290
290
  {/* 번호는 프롬프트에 쓰는 이름이다 — 화면에 없으면 "2번 이미지"라고
291
291
  적을 근거가 없다. */}
292
- <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">
292
+ <span className="absolute left-0.5 top-0.5 rounded bg-le-ink-900/75 px-1 text-[10px] font-le-semibold leading-4 text-le-on-accent">
293
293
  {referenceOrdinal(index)}
294
294
  </span>
295
295
  <button
@@ -301,7 +301,7 @@ export function AnnotationDialog({
301
301
  aria-label={t("detailPage.reauthor.referenceRemove", {
302
302
  defaultValue: "참고 사진 빼기",
303
303
  })}
304
- 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"
304
+ className="absolute right-0.5 top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-le-ink-900/70 text-le-on-accent disabled:opacity-40"
305
305
  >
306
306
  <X size={10} />
307
307
  </button>
@@ -320,8 +320,8 @@ export function AnnotationDialog({
320
320
  onClick={() => toggleAspect(ref.id, aspect.key)}
321
321
  className={`rounded-full border px-2 py-0.5 text-[11px] transition-colors disabled:opacity-40 ${
322
322
  on
323
- ? "border-dpe-ink-900 bg-dpe-ink-900 text-dpe-on-accent"
324
- : "border-dpe-ink-200 text-dpe-ink-500 hover:border-dpe-ink-400 hover:text-dpe-ink-700"
323
+ ? "border-le-ink-900 bg-le-ink-900 text-le-on-accent"
324
+ : "border-le-ink-200 text-le-ink-500 hover:border-le-ink-400 hover:text-le-ink-700"
325
325
  }`}
326
326
  >
327
327
  {aspect.label}
@@ -336,7 +336,7 @@ export function AnnotationDialog({
336
336
  type="button"
337
337
  disabled={busy}
338
338
  onClick={() => fileRef.current?.click()}
339
- className="flex h-[4.75rem] items-center gap-1.5 rounded-dpe-lg border border-dashed border-dpe-ink-300 px-3 text-xs text-dpe-ink-500 transition-colors hover:border-dpe-ink-400 hover:text-dpe-ink-700 disabled:opacity-40"
339
+ className="flex h-[4.75rem] items-center gap-1.5 rounded-le-lg border border-dashed border-le-ink-300 px-3 text-xs text-le-ink-500 transition-colors hover:border-le-ink-400 hover:text-le-ink-700 disabled:opacity-40"
340
340
  >
341
341
  <ImagePlus size={14} />
342
342
  {t("detailPage.reauthor.referenceAdd", {
@@ -353,7 +353,7 @@ export function AnnotationDialog({
353
353
  onChange={(e) => void attach(e.target.files)}
354
354
  />
355
355
  {references.length ? (
356
- <p className="w-full text-[11px] text-dpe-ink-400">
356
+ <p className="w-full text-[11px] text-le-ink-400">
357
357
  {t("detailPage.reauthor.referenceHint", {
358
358
  defaultValue:
359
359
  "축을 고르면 그 장에서는 고른 것만 따라가요(안 고르면 배치와 구성만). " +
@@ -365,12 +365,12 @@ export function AnnotationDialog({
365
365
  </div>
366
366
  ) : null}
367
367
  {error || flattenError || attachError ? (
368
- <p className="mt-2 text-xs text-dpe-danger-600">
368
+ <p className="mt-2 text-xs text-le-danger-600">
369
369
  {error ?? flattenError ?? attachError}
370
370
  </p>
371
371
  ) : null}
372
372
  <div className="mt-3 flex items-center justify-between gap-3">
373
- <p className="text-xs text-dpe-ink-400">
373
+ <p className="text-xs text-le-ink-400">
374
374
  {hasMarks
375
375
  ? t("detailPage.annotate.marked", {
376
376
  defaultValue: "표시한 자리를 기준으로 고쳐요.",
@@ -201,10 +201,10 @@ export const CanvasContextMenu = observer(function CanvasContextMenu({
201
201
  role="menu"
202
202
  data-dp-canvas-menu
203
203
  // 포털이 body 로 나가 편집기 뿌리 밖에 놓인다 — 딱지를 다시 달아야 소비자 앱이
204
- // `[data-dpe-root]` 로 좁혀 둔 색·모서리 토큰이 이 메뉴에도 먹는다.
205
- data-dpe-root=""
204
+ // `[data-le-root]` 로 좁혀 둔 색·모서리 토큰이 이 메뉴에도 먹는다.
205
+ data-le-root=""
206
206
  style={{ left, top, width: MENU_WIDTH }}
207
- className="fixed z-[71] rounded-dpe-lg border border-dpe-ink-200 bg-dpe-surface py-1 shadow-lg"
207
+ className="fixed z-[71] rounded-le-lg border border-le-ink-200 bg-le-surface py-1 shadow-lg"
208
208
  >
209
209
  {items.map((item) => {
210
210
  const Icon = CANVAS_MENU_ICONS[item.action];
@@ -212,7 +212,7 @@ export const CanvasContextMenu = observer(function CanvasContextMenu({
212
212
  return (
213
213
  <div key={item.action}>
214
214
  {item.separated ? (
215
- <div className="my-1 border-t border-dpe-ink-100" />
215
+ <div className="my-1 border-t border-le-ink-100" />
216
216
  ) : null}
217
217
  <button
218
218
  type="button"
@@ -223,9 +223,9 @@ export const CanvasContextMenu = observer(function CanvasContextMenu({
223
223
  runCanvasMenuAction(s, item.action);
224
224
  close();
225
225
  }}
226
- className="flex h-[30px] w-full items-center gap-2.5 px-3 text-left text-[13px] text-dpe-ink-700 transition-colors hover:bg-dpe-ink-100 disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent"
226
+ className="flex h-[30px] w-full items-center gap-2.5 px-3 text-left text-[13px] text-le-ink-700 transition-colors hover:bg-le-ink-100 disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent"
227
227
  >
228
- <Icon size={14} className="shrink-0 text-dpe-ink-500" />
228
+ <Icon size={14} className="shrink-0 text-le-ink-500" />
229
229
  {label}
230
230
  </button>
231
231
  </div>