@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.
- package/components/cardnews/color-input.tsx +11 -11
- 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 +6 -6
- package/components/detail-page/canvas-insert-toolbar.tsx +10 -10
- package/components/detail-page/canvas-selection-tools.tsx +5 -5
- 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-panel-header.tsx +4 -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 +14 -14
- package/components/detail-page/detail-page-elements-panel.tsx +4 -4
- package/components/detail-page/detail-page-font-picker.tsx +15 -15
- package/components/detail-page/detail-page-frame-header.tsx +2 -2
- package/components/detail-page/detail-page-history-buttons.tsx +3 -3
- 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 +22 -22
- package/components/detail-page/detail-page-my-shapes-panel.tsx +8 -8
- package/components/detail-page/detail-page-page-toolbar.tsx +2 -2
- package/components/detail-page/detail-page-pages-panel.tsx +16 -16
- 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 +57 -57
- 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 +7 -7
- 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/frame-drag-grip.tsx +1 -1
- package/components/detail-page/frame-drag-layer.tsx +2 -2
- package/components/detail-page/generation-progress-fill.tsx +7 -7
- package/components/detail-page/gif-effect-picker.tsx +9 -9
- package/components/detail-page/group-prompt-edit-panel.tsx +6 -6
- package/components/detail-page/image-crop-overlay.tsx +13 -13
- package/components/detail-page/inspector-controls.tsx +9 -9
- package/components/detail-page/leviosa-canvas-workspace.tsx +3 -3
- package/components/detail-page/panel-search-input.tsx +2 -2
- package/components/detail-page/prompt-edit-panel.tsx +6 -6
- package/components/detail-page/section-reauthor-controller.tsx +2 -2
- package/components/detail-page/selection-quick-toolbar.tsx +6 -6
- package/components/detail-page/spec-prompt-edit-panel.tsx +7 -7
- package/components/detail-page/svg-prompt-edit-panel.tsx +6 -6
- package/components/detail-page/table-inspector.tsx +24 -24
- package/components/ui/button.tsx +13 -13
- package/components/ui/command.tsx +10 -10
- package/components/ui/dialog.tsx +7 -7
- package/components/ui/label.tsx +1 -1
- package/components/ui/select.tsx +9 -9
- package/components/ui/slider.tsx +3 -3
- package/components/ui/switch.tsx +4 -4
- package/components/ui/tooltip.tsx +4 -4
- package/package.json +1 -1
- package/styles/canvas-bridge.css +10 -10
- 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-
|
|
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-
|
|
180
|
+
`[data-le-root]` 로 좁혀 둔 색·모서리 토큰이 이 팝오버에만 안 먹어서, 각진
|
|
181
181
|
인스펙터 위에 둥근 카드가 하나 뜬다. */}
|
|
182
182
|
<PopoverPrimitive.Portal>
|
|
183
183
|
<PopoverPrimitive.Content
|
|
184
184
|
data-layer-panel
|
|
185
|
-
data-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
491
|
-
: "flex h-10 items-center justify-center gap-2 rounded-
|
|
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-
|
|
503
|
-
: "flex h-10 items-center justify-center gap-2 rounded-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
686
|
-
{t("detailPage.aiGenerate.prompt")} <span className="text-
|
|
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-
|
|
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-
|
|
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-
|
|
703
|
-
<p className="text-[13px] font-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
805
|
+
"flex h-8 w-8 items-center justify-center rounded-le-lg transition-colors",
|
|
806
806
|
tool === id
|
|
807
|
-
? "bg-
|
|
808
|
-
: "text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
324
|
-
: "border-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
205
|
-
data-
|
|
204
|
+
// `[data-le-root]` 로 좁혀 둔 색·모서리 토큰이 이 메뉴에도 먹는다.
|
|
205
|
+
data-le-root=""
|
|
206
206
|
style={{ left, top, width: MENU_WIDTH }}
|
|
207
|
-
className="fixed z-[71] rounded-
|
|
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-
|
|
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-
|
|
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-
|
|
228
|
+
<Icon size={14} className="shrink-0 text-le-ink-500" />
|
|
229
229
|
{label}
|
|
230
230
|
</button>
|
|
231
231
|
</div>
|