aegon-lv97 1.0.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 (78) hide show
  1. package/gen-ai/build-report/chartStreamRefs.ts +227 -0
  2. package/gen-ai/build-report/coherence-check/index.vue +559 -0
  3. package/gen-ai/build-report/components/ReportChapterWorkspace.vue +4571 -0
  4. package/gen-ai/build-report/components/ReportConfigPanel.vue +727 -0
  5. package/gen-ai/build-report/components/ReportGenerateSettingsPanel.vue +2361 -0
  6. package/gen-ai/build-report/components/ReportGeneratingPanel.vue +1765 -0
  7. package/gen-ai/build-report/components/ReportPromptPanel.vue +625 -0
  8. package/gen-ai/build-report/components/ReportSourceFileSummary.vue +665 -0
  9. package/gen-ai/build-report/components/ReportSourcesPanel.vue +1327 -0
  10. package/gen-ai/build-report/components/ReportTemplateDetail.vue +191 -0
  11. package/gen-ai/build-report/components/ReportTemplateEditorFormPanel.vue +667 -0
  12. package/gen-ai/build-report/components/ReportTemplatePicker.vue +302 -0
  13. package/gen-ai/build-report/components/ReportWorkflowSidebar.vue +148 -0
  14. package/gen-ai/build-report/components/paramsSetting.vue +886 -0
  15. package/gen-ai/build-report/components/reportEdit.vue +2283 -0
  16. package/gen-ai/build-report/index.vue +856 -0
  17. package/gen-ai/build-report/report-domain.ts +1133 -0
  18. package/gen-ai/build-report/report-template-data.ts +295 -0
  19. package/gen-ai/build-report/useReportWorkflow.ts +1386 -0
  20. package/gen-ai/common/assets/NotoSans-SC.woff2 +0 -0
  21. package/gen-ai/common/assets/NotoSans-TC.woff2 +0 -0
  22. package/gen-ai/common/assets/NotoSans.woff2 +0 -0
  23. package/gen-ai/common/assets/fonts.css +278 -0
  24. package/gen-ai/common/assets/pagination.css +163 -0
  25. package/gen-ai/common/directives/btnLoading.ts +455 -0
  26. package/gen-ai/common/directives/debounce.ts +98 -0
  27. package/gen-ai/common/directives/index.ts +30 -0
  28. package/gen-ai/common/en.md +417 -0
  29. package/gen-ai/common/index.ts +36 -0
  30. package/gen-ai/common/types/component-expose.ts +9 -0
  31. package/gen-ai/common/utils/api-res-status.ts +7 -0
  32. package/gen-ai/common/utils/navigation.ts +59 -0
  33. package/gen-ai/common/utils/remote-request.ts +45 -0
  34. package/gen-ai/common/utils/scroll.ts +577 -0
  35. package/gen-ai/components/AIExport/docx-export.ts +950 -0
  36. package/gen-ai/components/AIExport/docxExport.ts +2566 -0
  37. package/gen-ai/components/AIExport/index.ts +52 -0
  38. package/gen-ai/components/AIExport/parseMarkdownHtml.ts +653 -0
  39. package/gen-ai/components/CapsuleScrollbar.vue +149 -0
  40. package/gen-ai/components/ChapterTitleScroll.vue +468 -0
  41. package/gen-ai/components/EntryHeroNavCards.vue +379 -0
  42. package/gen-ai/components/GenAiPagination.vue +52 -0
  43. package/gen-ai/components/ModelCard.vue +1114 -0
  44. package/gen-ai/components/Preview/FilePreviewPage.vue +949 -0
  45. package/gen-ai/components/Preview/PreviewCapsuleScroll.vue +205 -0
  46. package/gen-ai/components/Preview/PreviewOutlineTree.vue +148 -0
  47. package/gen-ai/components/Preview/index.ts +11 -0
  48. package/gen-ai/components/Preview/preview.ts +724 -0
  49. package/gen-ai/components/PromptField.vue +505 -0
  50. package/gen-ai/components/RecentReportsTable.vue +974 -0
  51. package/gen-ai/components/ReportCollapsibleSection.vue +172 -0
  52. package/gen-ai/components/ReportHistoryList.vue +637 -0
  53. package/gen-ai/components/ReportSectionConfirmPopover.vue +251 -0
  54. package/gen-ai/components/editor/ChapterTiptapEditor.vue +3718 -0
  55. package/gen-ai/components/editor/StructureBlockView.vue +441 -0
  56. package/gen-ai/components/editor/TiptapToolbar.vue +1036 -0
  57. package/gen-ai/components/editor/TypewriterDocEditor.vue +422 -0
  58. package/gen-ai/components/editor/_StructureOrgNode.vue +326 -0
  59. package/gen-ai/components/editor/index.ts +28 -0
  60. package/gen-ai/components/editor/parseMarkdownHtml.ts +754 -0
  61. package/gen-ai/components/editor/reviewCitationTip.ts +163 -0
  62. package/gen-ai/gen-entry/api/index.ts +32 -0
  63. package/gen-ai/gen-entry/index.vue +287 -0
  64. package/gen-ai/gen-entry/types/index.ts +4 -0
  65. package/gen-ai/i.mark +2 -0
  66. package/gen-ai/management-center/index.vue +1815 -0
  67. package/gen-ai/management-center/mockData.ts +248 -0
  68. package/gen-ai/management-center/types/index.ts +37 -0
  69. package/gen-ai/model-manager/TemplateEditorFormPanel.vue +661 -0
  70. package/gen-ai/model-manager/api/index.ts +32 -0
  71. package/gen-ai/model-manager/api/mockApi.ts +33 -0
  72. package/gen-ai/model-manager/index.vue +1583 -0
  73. package/gen-ai/model-manager/mockData.ts +177 -0
  74. package/gen-ai/model-manager/template-detail-shared.ts +154 -0
  75. package/gen-ai/model-manager/template-editor.vue +345 -0
  76. package/gen-ai/model-manager/types/index.ts +40 -0
  77. package/gen-ai/model-manager/utils.ts +25 -0
  78. package/package.json +12 -0
@@ -0,0 +1,149 @@
1
+ <template>
2
+ <div
3
+ v-if="canScroll"
4
+ :class="[rootClass, { [`${rootClass}--hidden`]: !shown }]"
5
+ :style="rootStyle"
6
+ aria-hidden="true"
7
+ >
8
+ <div :class="thumbClass" :style="thumbStyle" @mousedown.stop="onThumbMouseDown" />
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { ref, toRef, toValue, watch } from 'vue'
14
+ import { useCapsuleScrollbar } from '../common/utils/scroll'
15
+
16
+ const props = withDefaults(
17
+ defineProps<{
18
+ /** 滾動容器;模板傳 ref 時會自動解包為 HTMLElement */
19
+ scrollEl?: unknown
20
+ anchorEl?: unknown
21
+ active?: boolean
22
+ gutterPx?: number
23
+ thumbWidthPx?: number
24
+ minThumbPx?: number
25
+ visibleRatio?: number
26
+ alwaysVisible?: boolean
27
+ autoHideAfterMs?: number
28
+ rightOffsetPx?: number
29
+ classPrefix?: string
30
+ }>(),
31
+ {
32
+ scrollEl: undefined,
33
+ anchorEl: undefined,
34
+ active: false,
35
+ alwaysVisible: false,
36
+ autoHideAfterMs: 3000,
37
+ visibleRatio: 1,
38
+ rightOffsetPx: 1,
39
+ }
40
+ )
41
+
42
+ const scrollElRef = ref(null as HTMLElement | null)
43
+ const anchorElRef = ref(null as HTMLElement | null)
44
+ const activeRef = toRef(props, 'active')
45
+
46
+ watch(
47
+ () => toValue(props.scrollEl),
48
+ (el) => {
49
+ scrollElRef.value = (el as HTMLElement | null) ?? null
50
+ },
51
+ { immediate: true }
52
+ )
53
+
54
+ watch(
55
+ () => toValue(props.anchorEl),
56
+ (el) => {
57
+ anchorElRef.value = (el as HTMLElement | null) ?? null
58
+ },
59
+ { immediate: true }
60
+ )
61
+
62
+ const {
63
+ canScroll,
64
+ shown,
65
+ rootStyle,
66
+ thumbStyle,
67
+ rootClass,
68
+ thumbClass,
69
+ onThumbMouseDown,
70
+ refresh,
71
+ } = useCapsuleScrollbar({
72
+ scrollEl: scrollElRef,
73
+ anchorEl: anchorElRef,
74
+ active: activeRef,
75
+ gutterPx: props.gutterPx,
76
+ thumbWidthPx: props.thumbWidthPx,
77
+ minThumbPx: props.minThumbPx,
78
+ visibleRatio: props.visibleRatio,
79
+ alwaysVisible: props.alwaysVisible,
80
+ autoHideAfterMs: props.autoHideAfterMs,
81
+ rightOffsetPx: props.rightOffsetPx,
82
+ classPrefix: props.classPrefix,
83
+ })
84
+
85
+ defineExpose({ refresh })
86
+ </script>
87
+
88
+ <style>
89
+ .capsule-scroll-container {
90
+ scrollbar-width: none !important;
91
+ -ms-overflow-style: none !important;
92
+ overflow-y: auto !important;
93
+ box-sizing: border-box !important;
94
+ padding-right: calc(var(--capsule-scroll-padding-right, 0px) + 20px) !important;
95
+ margin-right: -20px !important;
96
+ width: calc(100% + 20px) !important;
97
+ }
98
+
99
+ .capsule-scroll-container::-webkit-scrollbar,
100
+ .capsule-scroll-container::-webkit-scrollbar-thumb,
101
+ .capsule-scroll-container::-webkit-scrollbar-track,
102
+ .capsule-scroll-container::-webkit-scrollbar-corner {
103
+ -webkit-appearance: none !important;
104
+ appearance: none !important;
105
+ width: 0 !important;
106
+ height: 0 !important;
107
+ display: none !important;
108
+ opacity: 0 !important;
109
+ visibility: hidden !important;
110
+ background: transparent !important;
111
+ }
112
+
113
+ .capsule-scrollbar {
114
+ position: fixed;
115
+ z-index: 12;
116
+ box-sizing: border-box;
117
+ padding: 0;
118
+ pointer-events: none;
119
+ transition: opacity 0.25s ease;
120
+ }
121
+
122
+ .capsule-scrollbar--hidden {
123
+ opacity: 0;
124
+ pointer-events: none !important;
125
+ }
126
+
127
+ .capsule-scrollbar__thumb {
128
+ height: 100%;
129
+ background: #999;
130
+ border-radius: 9999px;
131
+ cursor: grab;
132
+ pointer-events: auto;
133
+ box-shadow: none;
134
+ filter: none;
135
+ outline: none;
136
+ border: none;
137
+ backface-visibility: hidden;
138
+ -webkit-backface-visibility: hidden;
139
+ }
140
+
141
+ .capsule-scrollbar__thumb:hover {
142
+ background: #777;
143
+ }
144
+
145
+ .capsule-scrollbar__thumb:active {
146
+ cursor: grabbing;
147
+ background: #666;
148
+ }
149
+ </style>
@@ -0,0 +1,468 @@
1
+ <template>
2
+ <component
3
+ :is="rootTag"
4
+ class="chapter-title-scroll"
5
+ :class="{
6
+ 'is-truncatable': isTruncatable,
7
+ 'is-animating': isAnimating,
8
+ 'is-dragging': isDragging,
9
+ 'is-active': active,
10
+ 'is-fit-width': fitWidth,
11
+ 'is-char-truncated': !showFull && isTruncatable && !fitWidth,
12
+ }"
13
+ @click="handleClick"
14
+ @wheel="handleWheel"
15
+ >
16
+ <div ref="viewportRef" class="chapter-title-scroll__viewport" @pointerdown="handlePointerDown">
17
+ <span
18
+ ref="textRef"
19
+ class="chapter-title-scroll__text"
20
+ :style="{ transform: `translateX(${-offset}px)` }"
21
+ >
22
+ <template v-if="!showFull && isTruncatable && !fitWidth">{{ truncatedPrefix }}</template>
23
+ <template v-else>{{ title }}</template>
24
+ </span>
25
+ <!-- fitWidth:省略號疊在容器右側 -->
26
+ <span
27
+ v-if="!showFull && isTruncatable && fitWidth"
28
+ class="chapter-title-scroll__ellipsis"
29
+ aria-hidden="true"
30
+ >...</span
31
+ >
32
+ </div>
33
+ <!-- 字數截斷:省略號在 overflow 外,保證 ... 完整顯示 -->
34
+ <span
35
+ v-if="!showFull && isTruncatable && !fitWidth"
36
+ class="chapter-title-scroll__ellipsis"
37
+ aria-hidden="true"
38
+ >...</span
39
+ >
40
+ </component>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
45
+
46
+ const TITLE_MAX_DEFAULT = 19
47
+ /** 自動滾動速度(px/s),較慢 */
48
+ const SCROLL_SPEED_PX_PER_SEC = 22
49
+ const END_PAUSE_MS = 280
50
+ /** 全文已能完全顯示時,點擊展開後的停留時間(避免瞬間收回造成閃動/失效感) */
51
+ const FULL_REVEAL_HOLD_MS = 1400
52
+ const DRAG_CLICK_THRESHOLD_PX = 4
53
+
54
+ const props = defineProps({
55
+ title: { type: String, required: true },
56
+ rootTag: { type: String, default: 'li' },
57
+ active: { type: Boolean, default: false },
58
+ /** 按容器寬度截斷,空間不足時再顯示 ... */
59
+ fitWidth: { type: Boolean, default: false },
60
+ /** 按字元數截斷(fitWidth 為 false 時生效),超過後顯示 ... */
61
+ maxChars: { type: Number, default: TITLE_MAX_DEFAULT },
62
+ /** 禁用按住文字橫向拖動,便於整行排序拖曳 */
63
+ disableScrollDrag: { type: Boolean, default: false },
64
+ })
65
+
66
+ const viewportRef = ref(null as HTMLElement | null)
67
+ const textRef = ref(null as HTMLElement | null)
68
+ const offset = ref(0)
69
+ const showFull = ref(false)
70
+ const isAnimating = ref(false)
71
+ const isDragging = ref(false)
72
+ const isOverflowing = ref(false)
73
+
74
+ let animFrameId = 0
75
+ let endPauseTimer = 0
76
+ let dragStartX = 0
77
+ let dragStartOffset = 0
78
+ let pointerMoved = false
79
+ /** pointerup 已處理點擊時,抑制隨後的 click,避免展開後立刻被收回 */
80
+ let suppressClick = false
81
+ let activePointerMove: ((event: PointerEvent) => void) | null = null
82
+ let activePointerUp: ((event: PointerEvent) => void) | null = null
83
+ let resizeObserver: ResizeObserver | null = null
84
+
85
+ const titleMax = computed(() => Math.max(1, props.maxChars))
86
+
87
+ const isTruncatable = computed(() =>
88
+ props.fitWidth ? isOverflowing.value : props.title.length > titleMax.value
89
+ )
90
+ const truncatedPrefix = computed(() =>
91
+ isTruncatable.value ? props.title.slice(0, titleMax.value) : props.title
92
+ )
93
+
94
+ function getMaxOffset() {
95
+ const viewport = viewportRef.value
96
+ const text = textRef.value
97
+ if (!viewport || !text) return 0
98
+ return Math.max(0, text.scrollWidth - viewport.clientWidth)
99
+ }
100
+
101
+ function clampOffset(value: number) {
102
+ return Math.min(getMaxOffset(), Math.max(0, value))
103
+ }
104
+
105
+ function cancelAnimation() {
106
+ if (animFrameId) {
107
+ cancelAnimationFrame(animFrameId)
108
+ animFrameId = 0
109
+ }
110
+ if (endPauseTimer) {
111
+ clearTimeout(endPauseTimer)
112
+ endPauseTimer = 0
113
+ }
114
+ isAnimating.value = false
115
+ }
116
+
117
+ function removePointerListeners() {
118
+ if (activePointerMove) {
119
+ window.removeEventListener('pointermove', activePointerMove)
120
+ activePointerMove = null
121
+ }
122
+ if (activePointerUp) {
123
+ window.removeEventListener('pointerup', activePointerUp)
124
+ window.removeEventListener('pointercancel', activePointerUp)
125
+ activePointerUp = null
126
+ }
127
+ }
128
+
129
+ function resetToInitial() {
130
+ cancelAnimation()
131
+ removePointerListeners()
132
+ isDragging.value = false
133
+ offset.value = 0
134
+ showFull.value = false
135
+ }
136
+
137
+ async function ensureFullMode() {
138
+ if (!showFull.value) {
139
+ showFull.value = true
140
+ offset.value = 0
141
+ await nextTick()
142
+ }
143
+ }
144
+
145
+ function waitForLayout() {
146
+ return new Promise<void>((resolve) => {
147
+ requestAnimationFrame(() => {
148
+ requestAnimationFrame(() => resolve())
149
+ })
150
+ })
151
+ }
152
+
153
+ async function startAutoScroll() {
154
+ if (!isTruncatable.value || isAnimating.value) return
155
+
156
+ cancelAnimation()
157
+ isAnimating.value = true
158
+ showFull.value = true
159
+ offset.value = 0
160
+ await nextTick()
161
+ await waitForLayout()
162
+
163
+ // 等待佈局期間若已被取消/收回,則中止
164
+ if (!isAnimating.value || !showFull.value) return
165
+
166
+ const maxOffset = getMaxOffset()
167
+
168
+ // 容器寬度已能裝下全文:無需滾動,停留後再收回(避免「點一下閃一下」)
169
+ if (maxOffset <= 0) {
170
+ endPauseTimer = window.setTimeout(() => {
171
+ resetToInitial()
172
+ }, FULL_REVEAL_HOLD_MS)
173
+ return
174
+ }
175
+
176
+ const startTime = performance.now()
177
+
178
+ const tick = (now: number) => {
179
+ const elapsedSec = (now - startTime) / 1000
180
+ const nextOffset = Math.min(maxOffset, elapsedSec * SCROLL_SPEED_PX_PER_SEC)
181
+ offset.value = nextOffset
182
+
183
+ if (nextOffset >= maxOffset) {
184
+ endPauseTimer = window.setTimeout(() => {
185
+ resetToInitial()
186
+ }, END_PAUSE_MS)
187
+ return
188
+ }
189
+
190
+ animFrameId = requestAnimationFrame(tick)
191
+ }
192
+
193
+ animFrameId = requestAnimationFrame(tick)
194
+ }
195
+
196
+ function activateByUserTap() {
197
+ if (!isTruncatable.value) return
198
+ // 展開/滾動中再次點擊:提前收回
199
+ if (isAnimating.value || showFull.value) {
200
+ resetToInitial()
201
+ return
202
+ }
203
+ void startAutoScroll()
204
+ }
205
+
206
+ function handleClick() {
207
+ // pointerup 已處理時吞掉後續 click,不可提前清 suppressClick(否則會立刻再觸發收回造成閃動)
208
+ if (suppressClick) return
209
+ if (pointerMoved) {
210
+ pointerMoved = false
211
+ return
212
+ }
213
+ activateByUserTap()
214
+ }
215
+
216
+ function handleWheel(event: WheelEvent) {
217
+ if (!isTruncatable.value) return
218
+
219
+ // 豎向滾輪交給父級列表滾動;僅橫向意圖(deltaX 為主或按住 Shift)時拖動標題
220
+ const isHorizontalIntent = event.shiftKey || Math.abs(event.deltaX) > Math.abs(event.deltaY)
221
+ if (!isHorizontalIntent) return
222
+
223
+ event.preventDefault()
224
+ cancelAnimation()
225
+
226
+ void ensureFullMode().then(() => {
227
+ const delta = (event.deltaX !== 0 ? event.deltaX : event.deltaY) * 0.6
228
+ offset.value = clampOffset(offset.value + delta)
229
+ })
230
+ }
231
+
232
+ function handlePointerDown(event: PointerEvent) {
233
+ if (props.disableScrollDrag || !isTruncatable.value || event.button !== 0) return
234
+
235
+ // 延遲進入全文/拖曳:純點擊時不先展開,避免與激活邏輯互相打架閃動
236
+ pointerMoved = false
237
+ suppressClick = false
238
+ dragStartX = event.clientX
239
+ dragStartOffset = offset.value
240
+
241
+ activePointerMove = (moveEvent: PointerEvent) => {
242
+ const delta = dragStartX - moveEvent.clientX
243
+ if (!pointerMoved && Math.abs(delta) <= DRAG_CLICK_THRESHOLD_PX) return
244
+
245
+ if (!pointerMoved) {
246
+ pointerMoved = true
247
+ moveEvent.preventDefault()
248
+ cancelAnimation()
249
+ if (!showFull.value) {
250
+ showFull.value = true
251
+ offset.value = 0
252
+ }
253
+ isDragging.value = true
254
+ dragStartX = moveEvent.clientX
255
+ dragStartOffset = offset.value
256
+ return
257
+ }
258
+
259
+ if (!isDragging.value) return
260
+ offset.value = clampOffset(dragStartOffset + delta)
261
+ }
262
+
263
+ activePointerUp = () => {
264
+ const wasDrag = pointerMoved
265
+ isDragging.value = false
266
+ removePointerListeners()
267
+
268
+ if (wasDrag) {
269
+ // 拖曳結束:吞掉可能殘留的 click
270
+ suppressClick = true
271
+ window.setTimeout(() => {
272
+ pointerMoved = false
273
+ suppressClick = false
274
+ }, 120)
275
+ return
276
+ }
277
+
278
+ // 純點擊:在 pointerup 激活;延遲釋放 suppress,避免隨後的 click 再觸發一次(閃動/立刻收回)
279
+ suppressClick = true
280
+ activateByUserTap()
281
+ window.setTimeout(() => {
282
+ suppressClick = false
283
+ }, 120)
284
+ }
285
+
286
+ window.addEventListener('pointermove', activePointerMove)
287
+ window.addEventListener('pointerup', activePointerUp)
288
+ window.addEventListener('pointercancel', activePointerUp)
289
+ }
290
+
291
+ onBeforeUnmount(() => {
292
+ resizeObserver?.disconnect()
293
+ resetToInitial()
294
+ })
295
+
296
+ async function measureOverflow() {
297
+ if (!props.fitWidth) {
298
+ isOverflowing.value = false
299
+ return
300
+ }
301
+
302
+ await nextTick()
303
+ const viewport = viewportRef.value
304
+ const text = textRef.value
305
+ if (!viewport || !text) {
306
+ isOverflowing.value = false
307
+ return
308
+ }
309
+
310
+ isOverflowing.value = text.scrollWidth > viewport.clientWidth + 1
311
+ }
312
+
313
+ function setupResizeObserver() {
314
+ resizeObserver?.disconnect()
315
+ if (!props.fitWidth || !viewportRef.value) return
316
+
317
+ resizeObserver = new ResizeObserver(() => {
318
+ void measureOverflow()
319
+ })
320
+ resizeObserver.observe(viewportRef.value)
321
+ }
322
+
323
+ onMounted(() => {
324
+ void measureOverflow()
325
+ setupResizeObserver()
326
+ })
327
+
328
+ watch(
329
+ () => [props.title, props.fitWidth] as const,
330
+ () => {
331
+ void measureOverflow()
332
+ setupResizeObserver()
333
+ }
334
+ )
335
+ </script>
336
+
337
+ <style scoped>
338
+ .chapter-title-scroll {
339
+ --cts-lh-chapter: clamp(18px, calc(22 * 100vw / 1920), 22px);
340
+ --cts-fs-chapter: 14px;
341
+ display: inline-flex;
342
+ align-items: center;
343
+ width: 100%;
344
+ min-width: 0;
345
+ max-width: 100%;
346
+ height: var(--cts-lh-chapter);
347
+ line-height: var(--cts-lh-chapter);
348
+ list-style: none;
349
+ color: #606266;
350
+ cursor: default;
351
+ user-select: none;
352
+ }
353
+
354
+ .chapter-title-scroll.is-truncatable {
355
+ cursor: grab;
356
+ }
357
+
358
+ .chapter-title-scroll.is-dragging {
359
+ cursor: grabbing;
360
+ }
361
+
362
+ .chapter-title-scroll__viewport {
363
+ position: relative;
364
+ display: block;
365
+ flex: 1 1 auto;
366
+ min-width: 0;
367
+ max-width: 100%;
368
+ overflow: hidden;
369
+ height: var(--cts-lh-chapter);
370
+ line-height: var(--cts-lh-chapter);
371
+ font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
372
+ font-size: var(--cts-fs-chapter);
373
+ font-weight: 500;
374
+ scrollbar-width: none;
375
+ -ms-overflow-style: none;
376
+ touch-action: none;
377
+ }
378
+
379
+ /* 字數截斷靜態展示:前綴按內容寬,不裁切最後一字;... 跟在後面 */
380
+ .chapter-title-scroll.is-char-truncated {
381
+ width: max-content;
382
+ max-width: 100%;
383
+ }
384
+
385
+ .chapter-title-scroll.is-char-truncated .chapter-title-scroll__viewport {
386
+ flex: 0 0 auto;
387
+ min-width: auto;
388
+ max-width: none;
389
+ width: max-content;
390
+ overflow: visible;
391
+ }
392
+
393
+ .chapter-title-scroll.is-fit-width {
394
+ display: block;
395
+ }
396
+
397
+ .chapter-title-scroll.is-fit-width .chapter-title-scroll__viewport {
398
+ display: block;
399
+ width: 100%;
400
+ }
401
+
402
+ .chapter-title-scroll__viewport::-webkit-scrollbar {
403
+ display: none;
404
+ width: 0;
405
+ height: 0;
406
+ }
407
+
408
+ .chapter-title-scroll__text {
409
+ display: inline-block;
410
+ white-space: nowrap;
411
+ will-change: transform;
412
+ font-family: inherit;
413
+ font-size: inherit;
414
+ font-weight: inherit;
415
+ line-height: inherit;
416
+ color: #606266;
417
+ }
418
+
419
+ .chapter-title-scroll.is-dragging .chapter-title-scroll__text {
420
+ transition: none;
421
+ }
422
+
423
+ .chapter-title-scroll.is-active,
424
+ .chapter-title-scroll.is-active .chapter-title-scroll__text {
425
+ color: #c53355;
426
+ font-weight: 500;
427
+ }
428
+
429
+ .chapter-title-scroll.is-active .chapter-title-scroll__ellipsis {
430
+ color: #c53355;
431
+ }
432
+
433
+ .chapter-title-scroll.is-fit-width.is-active .chapter-title-scroll__ellipsis {
434
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, #fff 45%);
435
+ }
436
+
437
+ /* 默認:緊跟文字右側,加粗紅色;不參與 overflow 裁切 */
438
+ .chapter-title-scroll__ellipsis {
439
+ position: static;
440
+ flex: 0 0 auto;
441
+ flex-shrink: 0;
442
+ display: inline-flex;
443
+ align-items: center;
444
+ height: 1em;
445
+ margin: 0 0 0 2px;
446
+ padding: 0;
447
+ background: none;
448
+ color: #c53355;
449
+ font-family: inherit;
450
+ font-size: inherit;
451
+ font-weight: 700;
452
+ line-height: 1;
453
+ letter-spacing: 0.02em;
454
+ white-space: nowrap;
455
+ pointer-events: none;
456
+ }
457
+
458
+ /* fitWidth 滾動截斷:省略號固定在容器右側做遮罩 */
459
+ .chapter-title-scroll.is-fit-width .chapter-title-scroll__ellipsis {
460
+ position: absolute;
461
+ right: 0;
462
+ top: 0;
463
+ height: 100%;
464
+ padding-left: 8px;
465
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, #fff 45%);
466
+ line-height: var(--cts-lh-chapter);
467
+ }
468
+ </style>