@leviosa-ai/detail-page-editor 0.1.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 (190) hide show
  1. package/components/cardnews/color-input.tsx +380 -0
  2. package/components/detail-page/ai-generate-panel.tsx +763 -0
  3. package/components/detail-page/annotation-canvas.tsx +860 -0
  4. package/components/detail-page/annotation-dialog.tsx +397 -0
  5. package/components/detail-page/bubble-tail-overlay.tsx +209 -0
  6. package/components/detail-page/canvas-context-menu.tsx +234 -0
  7. package/components/detail-page/canvas-observer.tsx +68 -0
  8. package/components/detail-page/canvas-overlay-host.tsx +44 -0
  9. package/components/detail-page/canvas-store-facade.ts +51 -0
  10. package/components/detail-page/chart-inspector.tsx +531 -0
  11. package/components/detail-page/design-reference-picker.tsx +383 -0
  12. package/components/detail-page/detail-page-basic-shapes.tsx +180 -0
  13. package/components/detail-page/detail-page-brand-gifs-panel.tsx +261 -0
  14. package/components/detail-page/detail-page-brand-kit-panel.tsx +77 -0
  15. package/components/detail-page/detail-page-brand-panel-header.tsx +44 -0
  16. package/components/detail-page/detail-page-charts-panel.tsx +104 -0
  17. package/components/detail-page/detail-page-decorations-panel.tsx +125 -0
  18. package/components/detail-page/detail-page-download-dialog.tsx +524 -0
  19. package/components/detail-page/detail-page-editor.tsx +974 -0
  20. package/components/detail-page/detail-page-elements-panel.tsx +117 -0
  21. package/components/detail-page/detail-page-font-picker.tsx +231 -0
  22. package/components/detail-page/detail-page-history-buttons.tsx +87 -0
  23. package/components/detail-page/detail-page-host-context.tsx +465 -0
  24. package/components/detail-page/detail-page-icons-panel.tsx +251 -0
  25. package/components/detail-page/detail-page-konva-json-preview.tsx +474 -0
  26. package/components/detail-page/detail-page-layers-panel.tsx +618 -0
  27. package/components/detail-page/detail-page-my-images-panel.tsx +179 -0
  28. package/components/detail-page/detail-page-my-shapes-panel.tsx +174 -0
  29. package/components/detail-page/detail-page-page-toolbar.tsx +171 -0
  30. package/components/detail-page/detail-page-pages-panel.tsx +299 -0
  31. package/components/detail-page/detail-page-pages-timeline.tsx +79 -0
  32. package/components/detail-page/detail-page-photos-panel.tsx +257 -0
  33. package/components/detail-page/detail-page-properties-panel.tsx +2680 -0
  34. package/components/detail-page/detail-page-qr-panel.tsx +137 -0
  35. package/components/detail-page/detail-page-references-panel.tsx +166 -0
  36. package/components/detail-page/detail-page-selection.ts +44 -0
  37. package/components/detail-page/detail-page-shapes-panel.tsx +16 -0
  38. package/components/detail-page/detail-page-sidebar-sections.tsx +324 -0
  39. package/components/detail-page/detail-page-tables-panel.tsx +106 -0
  40. package/components/detail-page/detail-page-text-panel.tsx +179 -0
  41. package/components/detail-page/detail-page-thumbnail-bus.ts +42 -0
  42. package/components/detail-page/edit-quota-ui.tsx +150 -0
  43. package/components/detail-page/editor-history.ts +56 -0
  44. package/components/detail-page/editor-hotkeys.tsx +165 -0
  45. package/components/detail-page/element-recents-strip.tsx +105 -0
  46. package/components/detail-page/element-rects.ts +82 -0
  47. package/components/detail-page/fill-control.tsx +261 -0
  48. package/components/detail-page/find-replace-panel.tsx +252 -0
  49. package/components/detail-page/generation-progress-fill.tsx +57 -0
  50. package/components/detail-page/gif-animator.tsx +184 -0
  51. package/components/detail-page/gif-effect-picker.tsx +136 -0
  52. package/components/detail-page/group-drill-in.tsx +219 -0
  53. package/components/detail-page/group-prompt-edit-panel.tsx +154 -0
  54. package/components/detail-page/hover-highlight-overlay.tsx +111 -0
  55. package/components/detail-page/hovered-layer.ts +34 -0
  56. package/components/detail-page/inspector-controls.tsx +175 -0
  57. package/components/detail-page/leviosa-canvas-workspace.tsx +313 -0
  58. package/components/detail-page/nested-selection-overlay.tsx +364 -0
  59. package/components/detail-page/panel-search-input.tsx +49 -0
  60. package/components/detail-page/prompt-edit-panel.tsx +146 -0
  61. package/components/detail-page/section-height-handle.tsx +276 -0
  62. package/components/detail-page/section-reauthor-controller.tsx +210 -0
  63. package/components/detail-page/spec-prompt-edit-panel.tsx +178 -0
  64. package/components/detail-page/spec-resize-absorber.tsx +149 -0
  65. package/components/detail-page/svg-prompt-edit-panel.tsx +139 -0
  66. package/components/detail-page/table-canvas-overlay.tsx +707 -0
  67. package/components/detail-page/table-inspector.tsx +586 -0
  68. package/components/ui/button.tsx +64 -0
  69. package/components/ui/command.tsx +175 -0
  70. package/components/ui/dialog.tsx +167 -0
  71. package/components/ui/label.tsx +24 -0
  72. package/components/ui/select.tsx +190 -0
  73. package/components/ui/slider.tsx +63 -0
  74. package/components/ui/switch.tsx +35 -0
  75. package/components/ui/tooltip.tsx +57 -0
  76. package/config/detail-page-bundle-font-tags.json +39 -0
  77. package/config/detail-page-fonts.json +373 -0
  78. package/index.ts +24 -0
  79. package/lib/cardnews/font-loader.ts +306 -0
  80. package/lib/detail-page/animation-sniff.ts +101 -0
  81. package/lib/detail-page/asset-bytes-url.ts +70 -0
  82. package/lib/detail-page/basic-shapes.ts +603 -0
  83. package/lib/detail-page/canvas-menu.ts +182 -0
  84. package/lib/detail-page/chart/defaults.ts +283 -0
  85. package/lib/detail-page/chart/format.ts +66 -0
  86. package/lib/detail-page/chart/normalize.ts +149 -0
  87. package/lib/detail-page/chart/palette.ts +85 -0
  88. package/lib/detail-page/chart/paste.ts +82 -0
  89. package/lib/detail-page/chart/preview.ts +78 -0
  90. package/lib/detail-page/chart/render.ts +188 -0
  91. package/lib/detail-page/chart/renderers/bar-h.ts +103 -0
  92. package/lib/detail-page/chart/renderers/bar-v.ts +213 -0
  93. package/lib/detail-page/chart/renderers/donut.ts +186 -0
  94. package/lib/detail-page/chart/renderers/gauge.ts +96 -0
  95. package/lib/detail-page/chart/renderers/line.ts +143 -0
  96. package/lib/detail-page/chart/renderers/shared.ts +263 -0
  97. package/lib/detail-page/chart/renderers/stack.ts +121 -0
  98. package/lib/detail-page/chart/sync.ts +92 -0
  99. package/lib/detail-page/chart/types.ts +145 -0
  100. package/lib/detail-page/css-color.ts +34 -0
  101. package/lib/detail-page/data-gif-payload.ts +167 -0
  102. package/lib/detail-page/design-reference.ts +244 -0
  103. package/lib/detail-page/distribute.ts +89 -0
  104. package/lib/detail-page/editor-routes.ts +10 -0
  105. package/lib/detail-page/element-recents.ts +167 -0
  106. package/lib/detail-page/find-replace.ts +120 -0
  107. package/lib/detail-page/format-painter.ts +159 -0
  108. package/lib/detail-page/gif-frames.ts +216 -0
  109. package/lib/detail-page/gif-timeline.ts +65 -0
  110. package/lib/detail-page/group-action.ts +62 -0
  111. package/lib/detail-page/hangul-roman.ts +58 -0
  112. package/lib/detail-page/icon-curated.ts +290 -0
  113. package/lib/detail-page/icon-fuzzy.ts +250 -0
  114. package/lib/detail-page/icon-keywords.generated.ts +3766 -0
  115. package/lib/detail-page/icon-keywords.ko.ts +524 -0
  116. package/lib/detail-page/icon-search.ts +257 -0
  117. package/lib/detail-page/icons.ts +67 -0
  118. package/lib/detail-page/image-credit.ts +121 -0
  119. package/lib/detail-page/image-data-uri.ts +29 -0
  120. package/lib/detail-page/insert-image.ts +75 -0
  121. package/lib/detail-page/insert-shape.ts +49 -0
  122. package/lib/detail-page/korean-dictionary.ts +187 -0
  123. package/lib/detail-page/layer-move.ts +257 -0
  124. package/lib/detail-page/new-product-draft-store.ts +105 -0
  125. package/lib/detail-page/new-product-draft.ts +283 -0
  126. package/lib/detail-page/personal-images-refresh.ts +30 -0
  127. package/lib/detail-page/product-source.ts +587 -0
  128. package/lib/detail-page/qr-code.ts +214 -0
  129. package/lib/detail-page/reference-image.ts +103 -0
  130. package/lib/detail-page/reference-upload.ts +181 -0
  131. package/lib/detail-page/replace-page.ts +35 -0
  132. package/lib/detail-page/replace-with-gif.ts +251 -0
  133. package/lib/detail-page/section-height.ts +131 -0
  134. package/lib/detail-page/section-reauthor-bus.ts +61 -0
  135. package/lib/detail-page/shape-to-image.ts +219 -0
  136. package/lib/detail-page/spec-group/clipboard.ts +35 -0
  137. package/lib/detail-page/spec-group/sync.ts +377 -0
  138. package/lib/detail-page/spec-group/text-metrics.ts +48 -0
  139. package/lib/detail-page/stock-photos.ts +88 -0
  140. package/lib/detail-page/svg-colors.ts +74 -0
  141. package/lib/detail-page/table/defaults.ts +344 -0
  142. package/lib/detail-page/table/edit.ts +312 -0
  143. package/lib/detail-page/table/harvest.ts +103 -0
  144. package/lib/detail-page/table/normalize.ts +125 -0
  145. package/lib/detail-page/table/paste.ts +49 -0
  146. package/lib/detail-page/table/preview.ts +66 -0
  147. package/lib/detail-page/table/render.ts +388 -0
  148. package/lib/detail-page/table/sync.ts +107 -0
  149. package/lib/detail-page/table/types.ts +102 -0
  150. package/lib/detail-page/text-gif-layout.ts +245 -0
  151. package/lib/detail-page/text-gif-payload.ts +67 -0
  152. package/lib/detail-page/text-presets.ts +230 -0
  153. package/lib/detail-page/use-fake-progress.ts +67 -0
  154. package/lib/detail-page/z-order.ts +82 -0
  155. package/lib/detail-page-canvas/bubble-path.ts +190 -0
  156. package/lib/detail-page-canvas/canvas-key.ts +22 -0
  157. package/lib/detail-page-canvas/clip-rect.ts +65 -0
  158. package/lib/detail-page-canvas/custom-props-adapter.ts +219 -0
  159. package/lib/detail-page-canvas/editor-fonts.ts +161 -0
  160. package/lib/detail-page-canvas/export/ai.ts +413 -0
  161. package/lib/detail-page-canvas/export/color.ts +35 -0
  162. package/lib/detail-page-canvas/export/document-model.ts +78 -0
  163. package/lib/detail-page-canvas/export/export-files.ts +290 -0
  164. package/lib/detail-page-canvas/export/font-subset.ts +264 -0
  165. package/lib/detail-page-canvas/export/gif-export.ts +310 -0
  166. package/lib/detail-page-canvas/export/gif-plan.ts +95 -0
  167. package/lib/detail-page-canvas/export/mp4-encode.ts +158 -0
  168. package/lib/detail-page-canvas/export/mp4-support.ts +22 -0
  169. package/lib/detail-page-canvas/export/pdf/geometry.ts +309 -0
  170. package/lib/detail-page-canvas/export/pdf/inline-svg.ts +399 -0
  171. package/lib/detail-page-canvas/export/pdf/resources.ts +443 -0
  172. package/lib/detail-page-canvas/export/pdf/text-ops.ts +81 -0
  173. package/lib/detail-page-canvas/export/pdf/writer.ts +109 -0
  174. package/lib/detail-page-canvas/export/psd.ts +354 -0
  175. package/lib/detail-page-canvas/export/raster.ts +255 -0
  176. package/lib/detail-page-canvas/export/svg.ts +479 -0
  177. package/lib/detail-page-canvas/export/text-layout.ts +142 -0
  178. package/lib/detail-page-canvas/font-catalog.ts +177 -0
  179. package/lib/detail-page-canvas/font-tags.ts +47 -0
  180. package/lib/detail-page-canvas/gif-web-fonts.ts +90 -0
  181. package/lib/detail-page-canvas/konva-fallback.ts +477 -0
  182. package/lib/detail-page-canvas/text-background-plan.ts +124 -0
  183. package/lib/detail-page-canvas/text-highlight-bands.ts +127 -0
  184. package/lib/utils.ts +10 -0
  185. package/package.json +68 -0
  186. package/types/commerce.ts +171 -0
  187. package/types/detail-page-api.ts +200 -0
  188. package/types/detail-page-canvas.ts +20 -0
  189. package/types/fontkit.d.ts +42 -0
  190. package/types/gifenc.d.ts +51 -0
@@ -0,0 +1,177 @@
1
+ import catalogJson from "../../config/detail-page-fonts.json";
2
+
3
+ import type { FontTag } from "./font-tags";
4
+
5
+ export type DetailPageFontStyle = {
6
+ weight: number;
7
+ style: "normal" | "italic";
8
+ format: "woff" | "woff2";
9
+ url: string;
10
+ };
11
+
12
+ export type DetailPageFont = {
13
+ id: string;
14
+ family: string;
15
+ label: string;
16
+ labelEn: string;
17
+ /** 피커 칩이 거르는 분류. 한 폰트가 여러 개를 가진다. */
18
+ tags: FontTag[];
19
+ /** 한글 글리프가 없는 라틴 전용 폰트 — 한글에 쓰면 시스템 폰트로 폴백한다. */
20
+ latinOnly?: boolean;
21
+ previewText: string;
22
+ previewWeight: number;
23
+ licenseName: string;
24
+ licenseUrl: string;
25
+ licenseNoteKey?: string;
26
+ styles: DetailPageFontStyle[];
27
+ };
28
+
29
+ type CanvasFontStyle = {
30
+ src: string;
31
+ fontStyle?: string;
32
+ fontWeight?: string;
33
+ };
34
+
35
+ export type FontCatalogStore = {
36
+ fonts?: Array<{
37
+ fontFamily: string;
38
+ styles?: unknown;
39
+ }>;
40
+ addFont?: (font: {
41
+ fontFamily: string;
42
+ styles: CanvasFontStyle[];
43
+ }) => void;
44
+ };
45
+
46
+ export const DETAIL_PAGE_FONTS = catalogJson as DetailPageFont[];
47
+
48
+ const byFamily = new Map(
49
+ DETAIL_PAGE_FONTS.map((font) => [font.family, font] as const),
50
+ );
51
+ const loadPromises = new Map<string, Promise<DetailPageFontStyle>>();
52
+
53
+ export function getDetailPageFont(family: string): DetailPageFont | undefined {
54
+ return byFamily.get(family);
55
+ }
56
+
57
+ export function normalizeFontWeight(weight: unknown): number {
58
+ if (weight === "bold") return 700;
59
+ if (weight === "normal" || weight == null) return 400;
60
+ const parsed = Number(weight);
61
+ return Number.isFinite(parsed) ? parsed : 400;
62
+ }
63
+
64
+ export function getDetailPageFontWeights(font: DetailPageFont): number[] {
65
+ return [...new Set(font.styles.map((style) => style.weight))].sort(
66
+ (a, b) => a - b,
67
+ );
68
+ }
69
+
70
+ export function closestDetailPageFontWeight(
71
+ font: DetailPageFont,
72
+ requested: unknown,
73
+ ): number {
74
+ const target = normalizeFontWeight(requested);
75
+ return getDetailPageFontWeights(font).reduce((best, weight) =>
76
+ Math.abs(weight - target) < Math.abs(best - target) ? weight : best,
77
+ );
78
+ }
79
+
80
+ export function resolveDetailPageFontStyle(
81
+ font: DetailPageFont,
82
+ weight: unknown,
83
+ style: string,
84
+ ): DetailPageFontStyle {
85
+ const requestedWeight = normalizeFontWeight(weight);
86
+ const sameStyle = font.styles.filter((candidate) => candidate.style === style);
87
+ const candidates = sameStyle.length ? sameStyle : font.styles;
88
+ return candidates.reduce((best, candidate) =>
89
+ Math.abs(candidate.weight - requestedWeight) <
90
+ Math.abs(best.weight - requestedWeight)
91
+ ? candidate
92
+ : best,
93
+ );
94
+ }
95
+
96
+ function asCanvasStyle(style: DetailPageFontStyle): CanvasFontStyle {
97
+ return {
98
+ src: `url("${style.url}") format("${style.format}")`,
99
+ fontStyle: style.style,
100
+ fontWeight: String(style.weight),
101
+ };
102
+ }
103
+
104
+ function registerStoreFont(
105
+ store: FontCatalogStore | undefined,
106
+ font: DetailPageFont,
107
+ loadedStyle: DetailPageFontStyle,
108
+ ) {
109
+ if (!store?.addFont) return;
110
+ const existing = store.fonts?.find(
111
+ (candidate) => candidate.fontFamily === font.family,
112
+ );
113
+ const previous = Array.isArray(existing?.styles)
114
+ ? (existing.styles as CanvasFontStyle[])
115
+ : [];
116
+ const next = asCanvasStyle(loadedStyle);
117
+ const styles = previous.filter(
118
+ (style) =>
119
+ !(
120
+ style.fontStyle === next.fontStyle &&
121
+ String(style.fontWeight) === next.fontWeight
122
+ ),
123
+ );
124
+ styles.push(next);
125
+ store.addFont({ fontFamily: font.family, styles });
126
+ }
127
+
128
+ /**
129
+ * Loads only the selected family/style/weight. The picker itself uses static
130
+ * WebP assets, so opening or searching the list never downloads font bytes.
131
+ */
132
+ export async function loadDetailPageFont({
133
+ family,
134
+ weight,
135
+ style = "normal",
136
+ sample = "",
137
+ store,
138
+ }: {
139
+ family: string;
140
+ weight: unknown;
141
+ style?: string;
142
+ sample?: string;
143
+ store?: FontCatalogStore;
144
+ }): Promise<DetailPageFontStyle | null> {
145
+ const font = getDetailPageFont(family);
146
+ if (!font || typeof document === "undefined" || typeof FontFace === "undefined") {
147
+ return null;
148
+ }
149
+ const source = resolveDetailPageFontStyle(font, weight, style);
150
+ const key = `${font.family}\n${source.style}\n${source.weight}`;
151
+ let promise = loadPromises.get(key);
152
+ if (!promise) {
153
+ promise = new FontFace(
154
+ font.family,
155
+ `url("${source.url}") format("${source.format}")`,
156
+ {
157
+ style: source.style,
158
+ weight: String(source.weight),
159
+ display: "swap",
160
+ },
161
+ )
162
+ .load()
163
+ .then(async (face) => {
164
+ document.fonts.add(face);
165
+ await document.fonts.load(
166
+ `${source.style} ${source.weight} 24px "${font.family}"`,
167
+ sample || font.previewText,
168
+ );
169
+ return source;
170
+ });
171
+ loadPromises.set(key, promise);
172
+ promise.catch(() => loadPromises.delete(key));
173
+ }
174
+ const loaded = await promise;
175
+ registerStoreFont(store, font, loaded);
176
+ return loaded;
177
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The vocabulary the font picker filters by — 눈누의 태그 방식을 우리 어휘로 추린 것.
3
+ *
4
+ * Two sources, two homes. Catalog fonts carry their tags inline in
5
+ * `src/config/detail-page-fonts.json` (we own that file, so the tags sit next to
6
+ * the label and the license they describe). Bundle fonts come from the konva
7
+ * package, which we cannot edit, so their tags live in a sidecar keyed by font id.
8
+ * A test asserts both sides are complete, so a package bump that adds a font fails
9
+ * loudly instead of quietly shipping an untagged one.
10
+ *
11
+ * Order matters: the picker renders chips in this order.
12
+ */
13
+
14
+ import bundleTagsJson from "../../config/detail-page-bundle-font-tags.json";
15
+
16
+ export const FONT_TAGS = [
17
+ "heading",
18
+ "body",
19
+ "gothic",
20
+ "myeongjo",
21
+ "batang",
22
+ "handwriting",
23
+ "pixel",
24
+ "rounded",
25
+ "angular",
26
+ "outline",
27
+ "italic",
28
+ ] as const;
29
+
30
+ export type FontTag = (typeof FONT_TAGS)[number];
31
+
32
+ const KNOWN = new Set<string>(FONT_TAGS);
33
+
34
+ export function isFontTag(value: unknown): value is FontTag {
35
+ return typeof value === "string" && KNOWN.has(value);
36
+ }
37
+
38
+ /** Drops anything the vocabulary does not know — a stale tag must not crash a list. */
39
+ export function toFontTags(value: unknown): FontTag[] {
40
+ return Array.isArray(value) ? value.filter(isFontTag) : [];
41
+ }
42
+
43
+ const bundleTags = bundleTagsJson as Record<string, string[]>;
44
+
45
+ export function bundleFontTags(id: string): FontTag[] {
46
+ return toFontTags(bundleTags[id]);
47
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * 텍스트 GIF 요청에 실어 보낼 **웹폰트 URL**을 편집기 폰트 카탈로그에서 뽑는다.
3
+ *
4
+ * 서버는 헤드리스 Chromium으로 GIF를 굽는데, 그 컨테이너엔 우리 폰트가 하나도 없다.
5
+ * family 이름만 보내면 시스템 폴백(비트맵 픽셀 폰트)으로 그려져서 "폰트가 유지되지
6
+ * 않는다"는 증상이 난다. 그래서 실제 폰트 파일 주소를 함께 보낸다.
7
+ *
8
+ * 소스가 둘이라 주소 모양도 둘이다(서버는 확장자로 구분한다):
9
+ * catalog — jsdelivr 의 woff2 한 개(굵기별 파일)
10
+ * bundle — 우리 오리진의 스타일시트 한 개(한글 unicode-range 조각 ~90개를 묶은 것)
11
+ *
12
+ * 서버가 호스트 허용 목록으로 한 번 더 거르므로, 여기서 못 만들거나 로컬 오리진이면
13
+ * 그냥 비워 보낸다 — 그 경우 서버의 폴백 폰트로 그려진다.
14
+ */
15
+
16
+ import { LEVIOSA_KONVA_VERSION } from "@leviosa-ai/konva";
17
+
18
+ import {
19
+ closestDetailPageFontWeight,
20
+ getDetailPageFont,
21
+ resolveDetailPageFontStyle,
22
+ } from "./font-catalog";
23
+ import { closestEditorFontWeight, getEditorFont } from "./editor-fonts";
24
+
25
+ export type GifWebFont = {
26
+ family: string;
27
+ url: string;
28
+ weight: number;
29
+ };
30
+
31
+ /** 번들 폰트 스타일시트 경로가 쓰는 슬러그(font-loader와 같은 규칙). */
32
+ function slugifyFamily(family: string): string {
33
+ return family
34
+ .toLowerCase()
35
+ .trim()
36
+ .replace(/[^a-z0-9]+/g, "-")
37
+ .replace(/^-+|-+$/g, "");
38
+ }
39
+
40
+ function currentOrigin(): string {
41
+ if (typeof window === "undefined") return "";
42
+ return window.location.origin;
43
+ }
44
+
45
+ /**
46
+ * (family, weight) 쌍들 → 서버에 보낼 폰트 목록. 같은 조합은 한 번만 담고, 카탈로그에
47
+ * 없는 family(문서에만 있는 레거시 이름)는 건너뛴다.
48
+ */
49
+ export function resolveGifWebFonts(
50
+ faces: Array<{ family: string; weight?: unknown }>,
51
+ origin: string = currentOrigin(),
52
+ ): GifWebFont[] {
53
+ const out: GifWebFont[] = [];
54
+ const seen = new Set<string>();
55
+
56
+ for (const face of faces) {
57
+ const family = (face.family ?? "").trim();
58
+ if (!family) continue;
59
+ const editorFont = getEditorFont(family);
60
+ if (!editorFont) continue;
61
+
62
+ if (editorFont.source === "catalog") {
63
+ const catalogFont = getDetailPageFont(family);
64
+ if (!catalogFont) continue;
65
+ const weight = closestDetailPageFontWeight(catalogFont, face.weight);
66
+ const key = `${family}\n${weight}`;
67
+ if (seen.has(key)) continue;
68
+ seen.add(key);
69
+ const style = resolveDetailPageFontStyle(catalogFont, weight, "normal");
70
+ if (style?.url) out.push({ family, url: style.url, weight });
71
+ continue;
72
+ }
73
+
74
+ // 번들 폰트는 우리 오리진에서 서빙된다 — 서버가 받아갈 수 있는 절대 주소로만 보낸다.
75
+ if (!/^https:\/\//i.test(origin)) continue;
76
+ const slug = slugifyFamily(family);
77
+ if (!slug) continue;
78
+ const weight = closestEditorFontWeight(editorFont, face.weight);
79
+ const key = `${family}\n${weight}`;
80
+ if (seen.has(key)) continue;
81
+ seen.add(key);
82
+ out.push({
83
+ family,
84
+ weight,
85
+ url: `${origin}/render-fonts/family-css/${slug}-${weight}.css?v=${LEVIOSA_KONVA_VERSION}`,
86
+ });
87
+ }
88
+
89
+ return out.slice(0, 8);
90
+ }