@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.
- package/components/cardnews/color-input.tsx +9 -9
- package/components/detail-page/ai-generate-panel.tsx +37 -37
- package/components/detail-page/annotation-canvas.tsx +9 -9
- package/components/detail-page/annotation-dialog.tsx +18 -18
- package/components/detail-page/canvas-context-menu.tsx +4 -4
- package/components/detail-page/chart-inspector.tsx +23 -23
- package/components/detail-page/design-reference-picker.tsx +22 -22
- package/components/detail-page/detail-page-basic-shapes.tsx +4 -4
- package/components/detail-page/detail-page-brand-gifs-panel.tsx +10 -10
- package/components/detail-page/detail-page-brand-kit-panel.tsx +5 -5
- package/components/detail-page/detail-page-brand-panel-header.tsx +7 -4
- package/components/detail-page/detail-page-charts-panel.tsx +4 -4
- package/components/detail-page/detail-page-decorations-panel.tsx +5 -5
- package/components/detail-page/detail-page-download-dialog.tsx +19 -19
- package/components/detail-page/detail-page-editor.tsx +131 -76
- package/components/detail-page/detail-page-elements-panel.tsx +4 -4
- package/components/detail-page/detail-page-font-picker.tsx +14 -14
- package/components/detail-page/detail-page-history-buttons.tsx +3 -3
- package/components/detail-page/detail-page-host-context.tsx +63 -0
- package/components/detail-page/detail-page-icons-panel.tsx +13 -13
- package/components/detail-page/detail-page-konva-json-preview.tsx +9 -9
- package/components/detail-page/detail-page-layers-panel.tsx +14 -14
- package/components/detail-page/detail-page-my-images-panel.tsx +29 -14
- package/components/detail-page/detail-page-my-shapes-panel.tsx +8 -8
- package/components/detail-page/detail-page-page-toolbar.tsx +10 -10
- package/components/detail-page/detail-page-pages-panel.tsx +13 -13
- package/components/detail-page/detail-page-pages-timeline.tsx +4 -4
- package/components/detail-page/detail-page-photos-panel.tsx +13 -13
- package/components/detail-page/detail-page-properties-panel.tsx +55 -55
- package/components/detail-page/detail-page-qr-panel.tsx +13 -13
- package/components/detail-page/detail-page-references-panel.tsx +9 -9
- package/components/detail-page/detail-page-sidebar-sections.tsx +1 -1
- package/components/detail-page/detail-page-tables-panel.tsx +4 -4
- package/components/detail-page/detail-page-text-panel.tsx +4 -4
- package/components/detail-page/edit-quota-ui.tsx +3 -3
- package/components/detail-page/element-recents-strip.tsx +6 -6
- package/components/detail-page/fill-control.tsx +12 -12
- package/components/detail-page/find-replace-panel.tsx +8 -8
- package/components/detail-page/generation-progress-fill.tsx +4 -4
- package/components/detail-page/gif-effect-picker.tsx +9 -9
- package/components/detail-page/group-prompt-edit-panel.tsx +3 -3
- package/components/detail-page/inspector-controls.tsx +9 -9
- package/components/detail-page/panel-search-input.tsx +2 -2
- package/components/detail-page/prompt-edit-panel.tsx +3 -3
- package/components/detail-page/section-reauthor-controller.tsx +2 -2
- package/components/detail-page/spec-prompt-edit-panel.tsx +3 -3
- package/components/detail-page/svg-prompt-edit-panel.tsx +3 -3
- package/components/detail-page/table-inspector.tsx +24 -24
- package/components/ui/button.tsx +6 -6
- package/components/ui/command.tsx +5 -5
- package/components/ui/dialog.tsx +3 -3
- package/components/ui/label.tsx +1 -1
- package/components/ui/select.tsx +3 -3
- package/components/ui/slider.tsx +1 -1
- package/components/ui/tooltip.tsx +1 -1
- package/lib/detail-page/use-progressive-reveal.ts +82 -0
- package/lib/detail-page-canvas/konva-fallback.ts +10 -4
- package/package.json +4 -1
- package/styles/canvas-bridge.css +35 -0
- package/styles/tokens.css +103 -0
|
@@ -278,7 +278,7 @@ export function ColorInput({
|
|
|
278
278
|
<button
|
|
279
279
|
type="button"
|
|
280
280
|
onClick={() => open ? setOpen(false) : openPopover()}
|
|
281
|
-
className={`${swatchPx} rounded-lg border-2 border-[var(--color-border)] cursor-pointer shrink-0 transition-all hover:border-[var(--color-primary)] hover:shadow-md hover:scale-105 active:scale-95`}
|
|
281
|
+
className={`${swatchPx} rounded-dpe-lg border-2 border-[var(--color-border)] cursor-pointer shrink-0 transition-all hover:border-[var(--color-primary)] hover:shadow-md hover:scale-105 active:scale-95`}
|
|
282
282
|
style={{
|
|
283
283
|
backgroundColor: isTransparentValue ? undefined : safeColor,
|
|
284
284
|
backgroundImage: isTransparentValue ? CHECKER_BG : undefined,
|
|
@@ -293,7 +293,7 @@ export function ColorInput({
|
|
|
293
293
|
<div
|
|
294
294
|
ref={popoverRef}
|
|
295
295
|
data-layer-panel
|
|
296
|
-
className={`fixed z-[200] w-56 bg-[var(--color-background)] border border-[var(--color-border)] rounded-xl shadow-lg p-3 space-y-2.5 transition-opacity duration-75 ${measured ? "opacity-100" : "opacity-0 pointer-events-none"}`}
|
|
296
|
+
className={`fixed z-[200] w-56 bg-[var(--color-background)] border border-[var(--color-border)] rounded-dpe-xl shadow-lg p-3 space-y-2.5 transition-opacity duration-75 ${measured ? "opacity-100" : "opacity-0 pointer-events-none"}`}
|
|
297
297
|
style={{ top: popoverPos.top, left: popoverPos.left }}
|
|
298
298
|
>
|
|
299
299
|
{/* Transparent + Preset palette */}
|
|
@@ -302,7 +302,7 @@ export function ColorInput({
|
|
|
302
302
|
<button
|
|
303
303
|
type="button"
|
|
304
304
|
onClick={() => selectColor("transparent")}
|
|
305
|
-
className={`w-7 h-7 rounded-md border transition-transform hover:scale-110 active:scale-95 relative overflow-hidden ${
|
|
305
|
+
className={`w-7 h-7 rounded-dpe-md border transition-transform hover:scale-110 active:scale-95 relative overflow-hidden ${
|
|
306
306
|
isTransparentValue ? "border-[var(--color-primary)] ring-1 ring-[var(--color-primary)]" : "border-[var(--color-border)]"
|
|
307
307
|
}`}
|
|
308
308
|
style={{ backgroundImage: CHECKER_BG, backgroundSize: "8px 8px" }}
|
|
@@ -310,7 +310,7 @@ export function ColorInput({
|
|
|
310
310
|
>
|
|
311
311
|
{/* Diagonal line to indicate "none/transparent" */}
|
|
312
312
|
<span className="absolute inset-0 flex items-center justify-center">
|
|
313
|
-
<span className="block w-[130%] h-px bg-
|
|
313
|
+
<span className="block w-[130%] h-px bg-dpe-danger-500 rotate-45" />
|
|
314
314
|
</span>
|
|
315
315
|
</button>
|
|
316
316
|
)}
|
|
@@ -319,7 +319,7 @@ export function ColorInput({
|
|
|
319
319
|
key={color}
|
|
320
320
|
type="button"
|
|
321
321
|
onClick={() => selectColor(color)}
|
|
322
|
-
className={`w-7 h-7 rounded-md border transition-transform hover:scale-110 active:scale-95 ${
|
|
322
|
+
className={`w-7 h-7 rounded-dpe-md border transition-transform hover:scale-110 active:scale-95 ${
|
|
323
323
|
!isTransparentValue && safeColor.toUpperCase() === color ? "border-[var(--color-primary)] ring-1 ring-[var(--color-primary)]" : "border-[var(--color-border)]"
|
|
324
324
|
}`}
|
|
325
325
|
style={{ backgroundColor: color }}
|
|
@@ -338,13 +338,13 @@ export function ColorInput({
|
|
|
338
338
|
key={color}
|
|
339
339
|
type="button"
|
|
340
340
|
onClick={() => selectColor(color)}
|
|
341
|
-
className={`size-5 rounded-md border transition-transform hover:scale-110 active:scale-95 ${
|
|
341
|
+
className={`size-5 rounded-dpe-md border transition-transform hover:scale-110 active:scale-95 ${
|
|
342
342
|
!isTransparentValue && safeColor.toUpperCase() === color ? "border-[var(--color-primary)] ring-1 ring-[var(--color-primary)]" : "border-[var(--color-border)]"
|
|
343
343
|
}`}
|
|
344
344
|
style={{ backgroundColor: color }}
|
|
345
345
|
/>
|
|
346
346
|
) : (
|
|
347
|
-
<span key={idx} className="size-5 rounded-md border border-transparent" />
|
|
347
|
+
<span key={idx} className="size-5 rounded-dpe-md border border-transparent" />
|
|
348
348
|
);
|
|
349
349
|
})}
|
|
350
350
|
</div>
|
|
@@ -356,7 +356,7 @@ export function ColorInput({
|
|
|
356
356
|
<button
|
|
357
357
|
type="button"
|
|
358
358
|
onClick={openEyeDropper}
|
|
359
|
-
className="flex shrink-0 items-center gap-1 rounded-lg 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)]"
|
|
359
|
+
className="flex shrink-0 items-center gap-1 rounded-dpe-lg 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)]"
|
|
360
360
|
title={t("cardnews.colorInput.eyedropper")}
|
|
361
361
|
>
|
|
362
362
|
<Pipette className="w-3.5 h-3.5" />
|
|
@@ -369,7 +369,7 @@ export function ColorInput({
|
|
|
369
369
|
onChange={(e) => setHexInput(e.target.value)}
|
|
370
370
|
onKeyDown={(e) => e.key === "Enter" && handleHexSubmit()}
|
|
371
371
|
placeholder="#000000"
|
|
372
|
-
className="min-w-0 flex-1 rounded-lg border border-[var(--color-border)] px-2 py-1.5 text-xs font-mono focus:outline-none focus:border-[var(--color-primary)]"
|
|
372
|
+
className="min-w-0 flex-1 rounded-dpe-lg border border-[var(--color-border)] px-2 py-1.5 text-xs font-mono focus:outline-none focus:border-[var(--color-primary)]"
|
|
373
373
|
/>
|
|
374
374
|
</div>
|
|
375
375
|
</div>,
|
|
@@ -470,8 +470,8 @@ export function AiGeneratePanel({
|
|
|
470
470
|
onClick={() => setMode("image")}
|
|
471
471
|
className={
|
|
472
472
|
mode === "image"
|
|
473
|
-
? "flex h-10 items-center justify-center gap-2 rounded-md bg-
|
|
474
|
-
: "flex h-10 items-center justify-center gap-2 rounded-md border border-
|
|
473
|
+
? "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"
|
|
474
|
+
: "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"
|
|
475
475
|
}
|
|
476
476
|
>
|
|
477
477
|
<Sparkles aria-hidden="true" size={15} />
|
|
@@ -482,8 +482,8 @@ export function AiGeneratePanel({
|
|
|
482
482
|
onClick={() => setMode("gif")}
|
|
483
483
|
className={
|
|
484
484
|
isGif
|
|
485
|
-
? "flex h-10 items-center justify-center gap-2 rounded-md bg-
|
|
486
|
-
: "flex h-10 items-center justify-center gap-2 rounded-md border border-
|
|
485
|
+
? "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"
|
|
486
|
+
: "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"
|
|
487
487
|
}
|
|
488
488
|
>
|
|
489
489
|
<Sparkles aria-hidden="true" size={15} />
|
|
@@ -494,12 +494,12 @@ export function AiGeneratePanel({
|
|
|
494
494
|
|
|
495
495
|
{/* GIF 모드: 배경 투명 토글(기본 ON). 상세페이지 움짤은 대체로 배경 투명. */}
|
|
496
496
|
{isGif ? (
|
|
497
|
-
<label className="flex cursor-pointer items-center justify-between rounded-md border border-
|
|
497
|
+
<label className="flex cursor-pointer items-center justify-between rounded-dpe-md border border-dpe-ink-200 px-3 py-2.5">
|
|
498
498
|
<span className="flex flex-col">
|
|
499
|
-
<span className="text-sm font-medium text-
|
|
499
|
+
<span className="text-sm font-dpe-medium text-dpe-ink-800">
|
|
500
500
|
{t("detailPage.aiGenerate.transparentBg")}
|
|
501
501
|
</span>
|
|
502
|
-
<span className="text-[11px] leading-4 text-
|
|
502
|
+
<span className="text-[11px] leading-4 text-dpe-ink-500">
|
|
503
503
|
{t("detailPage.aiGenerate.transparentBgHint")}
|
|
504
504
|
</span>
|
|
505
505
|
</span>
|
|
@@ -507,7 +507,7 @@ export function AiGeneratePanel({
|
|
|
507
507
|
type="checkbox"
|
|
508
508
|
checked={transparent}
|
|
509
509
|
onChange={(e) => setTransparent(e.target.checked)}
|
|
510
|
-
className="h-4 w-4 accent-
|
|
510
|
+
className="h-4 w-4 accent-dpe-ink-900"
|
|
511
511
|
/>
|
|
512
512
|
</label>
|
|
513
513
|
) : null}
|
|
@@ -515,23 +515,23 @@ export function AiGeneratePanel({
|
|
|
515
515
|
{/* 이미지 모델(티어) 선택 — GIF 모드는 단일 모델이라 숨긴다. */}
|
|
516
516
|
<div hidden={isGif}>
|
|
517
517
|
<div className="mb-2 flex items-center gap-1">
|
|
518
|
-
<p className="text-xs font-medium text-
|
|
518
|
+
<p className="text-xs font-dpe-medium text-dpe-ink-500">{t("detailPage.aiGenerate.imageModel")}</p>
|
|
519
519
|
<Tooltip>
|
|
520
520
|
<TooltipTrigger asChild>
|
|
521
521
|
<button
|
|
522
522
|
type="button"
|
|
523
|
-
className="flex h-4 w-4 items-center justify-center rounded-full text-
|
|
523
|
+
className="flex h-4 w-4 items-center justify-center rounded-full text-dpe-ink-400 hover:text-dpe-ink-600"
|
|
524
524
|
aria-label={t("detailPage.aiGenerate.modelQualityHelp")}
|
|
525
525
|
>
|
|
526
526
|
<Info aria-hidden="true" size={13} />
|
|
527
527
|
</button>
|
|
528
528
|
</TooltipTrigger>
|
|
529
529
|
<TooltipContent side="left" className="max-w-[240px] text-left">
|
|
530
|
-
<p className="mb-1 font-semibold">{t("detailPage.aiGenerate.modelTooltipTitle")}</p>
|
|
530
|
+
<p className="mb-1 font-dpe-semibold">{t("detailPage.aiGenerate.modelTooltipTitle")}</p>
|
|
531
531
|
<ul className="space-y-1">
|
|
532
532
|
{IMAGE_TIERS.map((tk) => (
|
|
533
533
|
<li key={tk}>
|
|
534
|
-
<span className="font-medium">{IMAGE_TIER_META[tk].label}</span>
|
|
534
|
+
<span className="font-dpe-medium">{IMAGE_TIER_META[tk].label}</span>
|
|
535
535
|
<span className="opacity-80"> · {IMAGE_TIER_META[tk].quality}</span>
|
|
536
536
|
</li>
|
|
537
537
|
))}
|
|
@@ -550,20 +550,20 @@ export function AiGeneratePanel({
|
|
|
550
550
|
return (
|
|
551
551
|
<SelectItem key={tk} value={tk} className="items-start py-2">
|
|
552
552
|
<div className="flex flex-col gap-0.5 pr-2">
|
|
553
|
-
<span className="flex items-center gap-1.5 text-sm font-medium text-
|
|
553
|
+
<span className="flex items-center gap-1.5 text-sm font-dpe-medium text-dpe-ink-900">
|
|
554
554
|
{meta.label}
|
|
555
555
|
{meta.badge ? (
|
|
556
|
-
<span className="rounded-full bg-
|
|
556
|
+
<span className="rounded-full bg-dpe-ink-900 px-1.5 py-px text-[10px] font-dpe-semibold text-dpe-on-accent">
|
|
557
557
|
{meta.badge}
|
|
558
558
|
</span>
|
|
559
559
|
) : null}
|
|
560
560
|
{typeof cost === "number" && cost > 0 ? (
|
|
561
|
-
<span className="text-[11px] font-normal text-
|
|
561
|
+
<span className="text-[11px] font-dpe-normal text-dpe-ink-500">
|
|
562
562
|
· {cost}cr
|
|
563
563
|
</span>
|
|
564
564
|
) : null}
|
|
565
565
|
</span>
|
|
566
|
-
<span className="text-[11px] leading-4 text-
|
|
566
|
+
<span className="text-[11px] leading-4 text-dpe-ink-500">
|
|
567
567
|
{meta.quality}
|
|
568
568
|
</span>
|
|
569
569
|
</div>
|
|
@@ -572,7 +572,7 @@ export function AiGeneratePanel({
|
|
|
572
572
|
})}
|
|
573
573
|
</SelectContent>
|
|
574
574
|
</Select>
|
|
575
|
-
<p className="mt-1.5 text-[11px] leading-4 text-
|
|
575
|
+
<p className="mt-1.5 text-[11px] leading-4 text-dpe-ink-500">
|
|
576
576
|
{IMAGE_TIER_META[tier].description}
|
|
577
577
|
</p>
|
|
578
578
|
</div>
|
|
@@ -584,10 +584,10 @@ export function AiGeneratePanel({
|
|
|
584
584
|
- 좌측 자유 생성: GIF는 참조 필수(image→video), 이미지는 선택 업로드. */}
|
|
585
585
|
{hasImplicitReference && implicitReferenceSrc ? (
|
|
586
586
|
<div>
|
|
587
|
-
<p className="mb-2 text-xs font-medium text-
|
|
587
|
+
<p className="mb-2 text-xs font-dpe-medium text-dpe-ink-500">
|
|
588
588
|
{t("detailPage.aiGenerate.currentImageLabel")}
|
|
589
589
|
</p>
|
|
590
|
-
<div className="relative overflow-hidden rounded-lg border border-
|
|
590
|
+
<div className="relative overflow-hidden rounded-dpe-lg border border-dpe-ink-200 bg-dpe-ink-50">
|
|
591
591
|
{/* crossOrigin: S3 이미지를 Origin 없이 그리면 ACAO 없는 응답이 캐시돼
|
|
592
592
|
캔버스 crossOrigin 로드를 오염시킨다. 미리보기도 Origin을 실어 로드한다. */}
|
|
593
593
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
@@ -598,7 +598,7 @@ export function AiGeneratePanel({
|
|
|
598
598
|
className="h-36 w-full object-contain"
|
|
599
599
|
/>
|
|
600
600
|
</div>
|
|
601
|
-
<p className="mt-1.5 text-[11px] leading-4 text-
|
|
601
|
+
<p className="mt-1.5 text-[11px] leading-4 text-dpe-ink-500">
|
|
602
602
|
{isGif
|
|
603
603
|
? t("detailPage.aiGenerate.currentImageGifHint")
|
|
604
604
|
: t("detailPage.aiGenerate.currentImageHint")}
|
|
@@ -610,7 +610,7 @@ export function AiGeneratePanel({
|
|
|
610
610
|
type="button"
|
|
611
611
|
onClick={() => void openAnnotate()}
|
|
612
612
|
disabled={annotatePreparing || loading}
|
|
613
|
-
className="mt-2 inline-flex h-9 w-full items-center justify-center gap-1.5 rounded-md border border-
|
|
613
|
+
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"
|
|
614
614
|
>
|
|
615
615
|
{annotatePreparing ? (
|
|
616
616
|
<Loader2 aria-hidden="true" className="animate-spin" size={14} />
|
|
@@ -623,29 +623,29 @@ export function AiGeneratePanel({
|
|
|
623
623
|
</div>
|
|
624
624
|
) : (
|
|
625
625
|
<div hidden={isGif && hasImplicitReference}>
|
|
626
|
-
<p className="mb-2 text-xs font-medium text-
|
|
626
|
+
<p className="mb-2 text-xs font-dpe-medium text-dpe-ink-500">
|
|
627
627
|
{t("detailPage.aiGenerate.referenceLabel")}{" "}
|
|
628
628
|
{isGif ? (
|
|
629
|
-
<span className="text-
|
|
629
|
+
<span className="text-dpe-danger-500">*</span>
|
|
630
630
|
) : (
|
|
631
631
|
t("detailPage.aiGenerate.referenceOptional")
|
|
632
632
|
)}
|
|
633
633
|
</p>
|
|
634
634
|
{refUrl ? (
|
|
635
|
-
<div className="relative overflow-hidden rounded-lg border border-
|
|
635
|
+
<div className="relative overflow-hidden rounded-dpe-lg border border-dpe-ink-200">
|
|
636
636
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
637
637
|
<img src={refUrl} crossOrigin="anonymous" alt={t("detailPage.aiGenerate.referenceAlt")} className="h-36 w-full object-cover" />
|
|
638
638
|
<button
|
|
639
639
|
type="button"
|
|
640
640
|
onClick={() => setRefUrl(null)}
|
|
641
|
-
className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-full bg-
|
|
641
|
+
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"
|
|
642
642
|
aria-label={t("detailPage.aiGenerate.removeReference")}
|
|
643
643
|
>
|
|
644
644
|
<X aria-hidden="true" size={15} />
|
|
645
645
|
</button>
|
|
646
646
|
</div>
|
|
647
647
|
) : (
|
|
648
|
-
<label className="flex h-36 cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-
|
|
648
|
+
<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">
|
|
649
649
|
{refUploading ? (
|
|
650
650
|
<Loader2 aria-hidden="true" className="animate-spin" size={22} />
|
|
651
651
|
) : (
|
|
@@ -665,35 +665,35 @@ export function AiGeneratePanel({
|
|
|
665
665
|
|
|
666
666
|
{/* 프롬프트 */}
|
|
667
667
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
668
|
-
<label className="mb-2 text-xs font-medium text-
|
|
669
|
-
{t("detailPage.aiGenerate.prompt")} <span className="text-
|
|
668
|
+
<label className="mb-2 text-xs font-dpe-medium text-dpe-ink-500">
|
|
669
|
+
{t("detailPage.aiGenerate.prompt")} <span className="text-dpe-danger-500">*</span>
|
|
670
670
|
</label>
|
|
671
671
|
<textarea
|
|
672
672
|
value={prompt}
|
|
673
673
|
onChange={(e) => setPrompt(e.target.value)}
|
|
674
674
|
placeholder={t("detailPage.aiGenerate.promptPlaceholder")}
|
|
675
|
-
className="min-h-[160px] flex-1 resize-none rounded-lg border border-
|
|
675
|
+
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"
|
|
676
676
|
/>
|
|
677
677
|
</div>
|
|
678
678
|
|
|
679
679
|
{error ? (
|
|
680
|
-
<p className="text-xs font-medium text-
|
|
680
|
+
<p className="text-xs font-dpe-medium text-dpe-danger-600">{error}</p>
|
|
681
681
|
) : null}
|
|
682
682
|
|
|
683
683
|
{/* 1.5× 안전 마진 미달(또는 생성 중 크레딧 부족) → 차단 안내 + 추가구매 CTA */}
|
|
684
684
|
{activeCost > 0 && (blocked || insufficient) ? (
|
|
685
|
-
<div className="rounded-lg border border-
|
|
686
|
-
<p className="text-[13px] font-medium text-
|
|
685
|
+
<div className="rounded-dpe-lg border border-dpe-warn-200 bg-dpe-warn-50 px-3 py-2.5 text-center">
|
|
686
|
+
<p className="text-[13px] font-dpe-medium text-dpe-warn-800">
|
|
687
687
|
{t("detailPage.aiGenerate.insufficientCreditsTitle")}
|
|
688
688
|
</p>
|
|
689
|
-
<p className="mt-0.5 text-[11px] leading-4 text-
|
|
689
|
+
<p className="mt-0.5 text-[11px] leading-4 text-dpe-warn-700">
|
|
690
690
|
{t("detailPage.aiGenerate.insufficientCreditsHint", { required, balance: creditBalance })}
|
|
691
691
|
</p>
|
|
692
692
|
<button
|
|
693
693
|
type="button"
|
|
694
694
|
onClick={onBuyCredits}
|
|
695
695
|
disabled={!onBuyCredits}
|
|
696
|
-
className="mt-2 inline-flex items-center gap-1.5 rounded-md bg-
|
|
696
|
+
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"
|
|
697
697
|
>
|
|
698
698
|
<Sparkles aria-hidden="true" size={13} />
|
|
699
699
|
{t("detailPage.aiGenerate.buyCredits")}
|
|
@@ -702,7 +702,7 @@ export function AiGeneratePanel({
|
|
|
702
702
|
) : null}
|
|
703
703
|
|
|
704
704
|
{gifMissingRef ? (
|
|
705
|
-
<p className="text-xs font-medium text-
|
|
705
|
+
<p className="text-xs font-dpe-medium text-dpe-ink-500">
|
|
706
706
|
{t("detailPage.aiGenerate.gifNeedsReference")}
|
|
707
707
|
</p>
|
|
708
708
|
) : null}
|
|
@@ -712,7 +712,7 @@ export function AiGeneratePanel({
|
|
|
712
712
|
onClick={handleGenerate}
|
|
713
713
|
disabled={loading || !prompt.trim() || blocked || gifMissingRef}
|
|
714
714
|
className={
|
|
715
|
-
"relative flex h-11 items-center justify-center gap-2 overflow-hidden rounded-lg bg-
|
|
715
|
+
"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 " +
|
|
716
716
|
(loading
|
|
717
717
|
? "cursor-progress"
|
|
718
718
|
: "disabled:cursor-not-allowed disabled:opacity-40")
|
|
@@ -738,7 +738,7 @@ export function AiGeneratePanel({
|
|
|
738
738
|
? t("detailPage.aiGenerate.replaceImage")
|
|
739
739
|
: t("detailPage.aiGenerate.generate")}
|
|
740
740
|
{activeCost > 0 && !loading ? (
|
|
741
|
-
<span className="text-xs font-normal opacity-80">· {activeCost}cr</span>
|
|
741
|
+
<span className="text-xs font-dpe-normal opacity-80">· {activeCost}cr</span>
|
|
742
742
|
) : null}
|
|
743
743
|
</span>
|
|
744
744
|
</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-
|
|
680
|
+
className="relative h-full w-full overflow-hidden bg-dpe-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-xl border border-
|
|
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">
|
|
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-lg transition-colors",
|
|
805
|
+
"flex h-8 w-8 items-center justify-center rounded-dpe-lg transition-colors",
|
|
806
806
|
tool === id
|
|
807
|
-
? "bg-
|
|
808
|
-
: "text-
|
|
807
|
+
? "bg-dpe-ink-900 text-dpe-on-accent"
|
|
808
|
+
: "text-dpe-ink-500 hover:bg-dpe-ink-100 hover:text-dpe-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-
|
|
814
|
+
<span className="mx-1 h-5 w-px bg-dpe-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-
|
|
834
|
+
<span className="mx-1 h-5 w-px bg-dpe-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-lg text-
|
|
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"
|
|
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-lg text-
|
|
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"
|
|
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-
|
|
213
|
+
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-dpe-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-
|
|
218
|
+
className="flex h-full max-h-[92vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl bg-dpe-surface shadow-2xl"
|
|
219
219
|
>
|
|
220
|
-
<div className="flex items-start justify-between gap-4 border-b border-
|
|
220
|
+
<div className="flex items-start justify-between gap-4 border-b border-dpe-ink-200 px-5 py-3">
|
|
221
221
|
<div className="min-w-0">
|
|
222
|
-
<h2 className="text-sm font-semibold text-
|
|
222
|
+
<h2 className="text-sm font-dpe-semibold text-dpe-ink-900">{title}</h2>
|
|
223
223
|
{description ? (
|
|
224
|
-
<p className="mt-0.5 text-xs text-
|
|
224
|
+
<p className="mt-0.5 text-xs text-dpe-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-lg text-
|
|
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"
|
|
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-
|
|
258
|
+
<div className="border-t border-dpe-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-lg border border-
|
|
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"
|
|
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-lg border border-
|
|
278
|
+
className="flex items-start gap-2 rounded-dpe-lg border border-dpe-ink-200 p-1.5"
|
|
279
279
|
>
|
|
280
|
-
<div className="relative h-14 w-14 shrink-0 overflow-hidden rounded-md bg-
|
|
280
|
+
<div className="relative h-14 w-14 shrink-0 overflow-hidden rounded-dpe-md bg-dpe-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-
|
|
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">
|
|
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-
|
|
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"
|
|
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-
|
|
324
|
-
: "border-
|
|
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"
|
|
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-lg border border-dashed border-
|
|
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"
|
|
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-
|
|
356
|
+
<p className="w-full text-[11px] text-dpe-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-
|
|
368
|
+
<p className="mt-2 text-xs text-dpe-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-
|
|
373
|
+
<p className="text-xs text-dpe-ink-400">
|
|
374
374
|
{hasMarks
|
|
375
375
|
? t("detailPage.annotate.marked", {
|
|
376
376
|
defaultValue: "표시한 자리를 기준으로 고쳐요.",
|
|
@@ -200,7 +200,7 @@ export const CanvasContextMenu = observer(function CanvasContextMenu({
|
|
|
200
200
|
role="menu"
|
|
201
201
|
data-dp-canvas-menu
|
|
202
202
|
style={{ left, top, width: MENU_WIDTH }}
|
|
203
|
-
className="fixed z-[71] rounded-lg border border-
|
|
203
|
+
className="fixed z-[71] rounded-dpe-lg border border-dpe-ink-200 bg-dpe-surface py-1 shadow-lg"
|
|
204
204
|
>
|
|
205
205
|
{items.map((item) => {
|
|
206
206
|
const Icon = ICONS[item.action];
|
|
@@ -208,7 +208,7 @@ export const CanvasContextMenu = observer(function CanvasContextMenu({
|
|
|
208
208
|
return (
|
|
209
209
|
<div key={item.action}>
|
|
210
210
|
{item.separated ? (
|
|
211
|
-
<div className="my-1 border-t border-
|
|
211
|
+
<div className="my-1 border-t border-dpe-ink-100" />
|
|
212
212
|
) : null}
|
|
213
213
|
<button
|
|
214
214
|
type="button"
|
|
@@ -219,9 +219,9 @@ export const CanvasContextMenu = observer(function CanvasContextMenu({
|
|
|
219
219
|
runCanvasMenuAction(s, item.action);
|
|
220
220
|
close();
|
|
221
221
|
}}
|
|
222
|
-
className="flex h-[30px] w-full items-center gap-2.5 px-3 text-left text-[13px] text-
|
|
222
|
+
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"
|
|
223
223
|
>
|
|
224
|
-
<Icon size={14} className="shrink-0 text-
|
|
224
|
+
<Icon size={14} className="shrink-0 text-dpe-ink-500" />
|
|
225
225
|
{label}
|
|
226
226
|
</button>
|
|
227
227
|
</div>
|