entari-plugin-hyw 3.5.0rc1__py3-none-any.whl → 3.5.0rc2__py3-none-any.whl

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.

Potentially problematic release.


This version of entari-plugin-hyw might be problematic. Click here for more details.

Files changed (32) hide show
  1. entari_plugin_hyw/__init__.py +77 -82
  2. entari_plugin_hyw/assets/card-dist/index.html +360 -99
  3. entari_plugin_hyw/card-ui/src/App.vue +246 -52
  4. entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +122 -67
  5. entari_plugin_hyw/card-ui/src/components/StageCard.vue +46 -26
  6. entari_plugin_hyw/card-ui/src/test_regex.js +103 -0
  7. entari_plugin_hyw/card-ui/src/types.ts +1 -0
  8. entari_plugin_hyw/{core/history.py → history.py} +25 -1
  9. entari_plugin_hyw/image_cache.py +283 -0
  10. entari_plugin_hyw/{core/pipeline.py → pipeline.py} +102 -27
  11. entari_plugin_hyw/{utils/prompts.py → prompts.py} +7 -24
  12. entari_plugin_hyw/render_vue.py +314 -0
  13. entari_plugin_hyw/{utils/search.py → search.py} +227 -10
  14. {entari_plugin_hyw-3.5.0rc1.dist-info → entari_plugin_hyw-3.5.0rc2.dist-info}/METADATA +1 -1
  15. {entari_plugin_hyw-3.5.0rc1.dist-info → entari_plugin_hyw-3.5.0rc2.dist-info}/RECORD +18 -29
  16. entari_plugin_hyw/core/__init__.py +0 -0
  17. entari_plugin_hyw/core/config.py +0 -35
  18. entari_plugin_hyw/core/hyw.py +0 -48
  19. entari_plugin_hyw/core/render_vue.py +0 -255
  20. entari_plugin_hyw/test_output/render_0.jpg +0 -0
  21. entari_plugin_hyw/test_output/render_1.jpg +0 -0
  22. entari_plugin_hyw/test_output/render_2.jpg +0 -0
  23. entari_plugin_hyw/test_output/render_3.jpg +0 -0
  24. entari_plugin_hyw/test_output/render_4.jpg +0 -0
  25. entari_plugin_hyw/tests/ui_test_output.jpg +0 -0
  26. entari_plugin_hyw/tests/verify_ui.py +0 -139
  27. entari_plugin_hyw/utils/__init__.py +0 -2
  28. entari_plugin_hyw/utils/browser.py +0 -40
  29. entari_plugin_hyw/utils/playwright_tool.py +0 -36
  30. /entari_plugin_hyw/{utils/misc.py → misc.py} +0 -0
  31. {entari_plugin_hyw-3.5.0rc1.dist-info → entari_plugin_hyw-3.5.0rc2.dist-info}/WHEEL +0 -0
  32. {entari_plugin_hyw-3.5.0rc1.dist-info → entari_plugin_hyw-3.5.0rc2.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,31 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, onMounted } from 'vue'
3
3
  import { Icon } from '@iconify/vue'
4
+
4
5
  import type { RenderData } from './types'
5
6
  import StageCard from './components/StageCard.vue'
6
7
  import MarkdownContent from './components/MarkdownContent.vue'
7
8
 
9
+ // Get icon for card type
10
+ const getCardIcon = (contentType?: string): string => {
11
+ switch (contentType) {
12
+ case 'summary': return 'mdi:file-document-outline'
13
+ case 'code': return 'mdi:code-braces'
14
+ case 'table': return 'mdi:table'
15
+ default: return 'mdi:card-outline'
16
+ }
17
+ }
18
+
19
+ // Get display label for card
20
+ const getCardLabel = (contentType?: string, language?: string): string => {
21
+ switch (contentType) {
22
+ case 'summary': return 'Summary'
23
+ case 'code': return language ? language.charAt(0).toUpperCase() + language.slice(1) : 'Code'
24
+ case 'table': return 'Table'
25
+ default: return ''
26
+ }
27
+ }
28
+
8
29
  declare global {
9
30
  interface Window {
10
31
  RENDER_DATA: RenderData
@@ -22,6 +43,19 @@ window.updateRenderData = (newData: RenderData) => {
22
43
  const numSearchRefs = computed(() => data.value?.references?.length || 0)
23
44
  const numPageRefs = computed(() => data.value?.page_references?.length || 0)
24
45
 
46
+ // Calculate the reference offset for each stage (for unified badge numbering)
47
+ const getRefOffset = (stageIndex: number): number => {
48
+ if (!data.value?.stages) return 0
49
+ let offset = 0
50
+ for (let i = 0; i < stageIndex; i++) {
51
+ const stage = data.value.stages[i]
52
+ if (stage) {
53
+ offset += (stage.references?.length || 0) + (stage.crawled_pages?.length || 0)
54
+ }
55
+ }
56
+ return offset
57
+ }
58
+
25
59
  // Helper: Strips content before the first H1 heading (e.g., AI "thought" prefixes)
26
60
  const stripPrefixBeforeH1 = (text: string): string => {
27
61
  // Find the first line starting with "# " (H1)
@@ -40,12 +74,64 @@ const mainTitle = computed(() => {
40
74
  const match = md.match(/^#\s+(.+)$/m)
41
75
  return match && match[1] ? match[1].trim() : ''
42
76
  })
43
- const agentModel = computed(() => {
44
- const agentStage = data.value?.stages?.find(s => s.name.toLowerCase().includes('agent'))
45
- return agentStage?.model || 'AI AGENT'
77
+
78
+ // Process title to support <u> underline tags
79
+ const processedTitle = computed(() => {
80
+ return mainTitle.value.replace(/<u>([^<]*)<\/u>/g, (_, content) => {
81
+ return `<span class="underline decoration-[5px] underline-offset-8" style="text-decoration-color: var(--theme-color)">${content}</span>`
82
+ })
46
83
  })
47
84
 
48
85
 
86
+
87
+
88
+ const dedent = (text: string) => {
89
+ const lines = text.split('\n')
90
+ // Find minimum indentation of non-empty lines
91
+ let minIndent = Infinity
92
+ for (const line of lines) {
93
+ if (line.trim().length === 0) continue
94
+ const leadingSpace = line.match(/^\s*/)?.[0].length || 0
95
+ if (leadingSpace < minIndent) minIndent = leadingSpace
96
+ }
97
+
98
+ if (minIndent === Infinity || minIndent === 0) return text
99
+
100
+ return lines.map(line => {
101
+ if (line.trim().length === 0) return ''
102
+ return line.substring(minIndent)
103
+ }).join('\n')
104
+ }
105
+
106
+
107
+ const themeColor = computed(() => data.value?.theme_color || '#ef4444')
108
+
109
+ // Calculate relative luminance to determine if color is light or dark
110
+ const getLuminance = (hex: string): number => {
111
+ const match = hex.replace('#', '').match(/.{2}/g)
112
+ if (!match) return 0
113
+ const [r, g, b] = match.map(x => {
114
+ const c = parseInt(x, 16) / 255
115
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
116
+ })
117
+ return 0.2126 * (r ?? 0) + 0.7152 * (g ?? 0) + 0.0722 * (b ?? 0)
118
+ }
119
+
120
+ // Auto text color: dark text on light bg, white text on dark bg
121
+ const headerTextColor = computed(() => {
122
+ const luminance = getLuminance(themeColor.value)
123
+ return luminance > 0.4 ? '#1f2937' : '#ffffff' // gray-800 or white
124
+ })
125
+
126
+ const themeStyle = computed(() => ({
127
+ '--theme-color': themeColor.value,
128
+ '--header-text-color': headerTextColor.value,
129
+ '--text-muted': '#4b5563', // gray-600, higher contrast for mobile
130
+ '--border-color': '#e5e7eb', // gray-200, for borders
131
+ '--bg-subtle': '#f9fafb' // gray-50, for subtle backgrounds
132
+ }))
133
+
134
+
49
135
  const parsedSections = computed(() => {
50
136
  const rawMd = data.value?.markdown || ''
51
137
  if (!rawMd) return []
@@ -58,11 +144,12 @@ const parsedSections = computed(() => {
58
144
  content = content.replace(/(?:^|\n)\s*(?:#{1,3}|\*\*)\s*(?:References|Citations|Sources)[\s\S]*$/i, '')
59
145
  content = content.trim()
60
146
 
61
- const sections: Array<{ type: 'markdown' | 'card', content: string, title?: string, contentType?: 'table' | 'code', language?: string }> = []
147
+ const sections: Array<{ type: 'markdown' | 'card', content: string, title?: string, contentType?: 'table' | 'code' | 'summary', language?: string }> = []
62
148
 
63
149
  // Combine regex involves complexity, so we'll use a tokenizer approach
64
150
  // split tokens by Code Block or Table
65
- const combinedRegex = /(```[\s\S]*?```|((?:^|\n)\|[^\n]*\|(?:\n\|[^\n]*\|)*))/
151
+ // split tokens by Code Block or Table or Summary
152
+ const combinedRegex = /(```[\s\S]*?```|((?:^|\n)\|[^\n]*\|(?:\n\|[^\n]*\|)*)|<summary>[\s\S]*?<\/summary>)/
66
153
 
67
154
  let remaining = content
68
155
 
@@ -85,20 +172,28 @@ const parsedSections = computed(() => {
85
172
 
86
173
  // Determine type
87
174
  const isCode = matchedStr.startsWith('```')
175
+ const isSummary = matchedStr.startsWith('<summary>')
88
176
  // Tables might match with a leading newline, trim it for checking but render carefully
89
- const isTable = !isCode && matchedStr.trim().startsWith('|')
177
+ const isTable = !isCode && !isSummary && matchedStr.trim().startsWith('|')
90
178
 
91
- if (isCode || isTable) {
179
+ if (isCode || isTable || isSummary) {
92
180
  let language = ''
181
+ let content = matchedStr.trim()
182
+
93
183
  if (isCode) {
94
184
  const match = matchedStr.match(/^```(\w+)/)
95
185
  if (match && match[1]) language = match[1]
186
+ } else if (isSummary) {
187
+ // Strip tags
188
+ content = content.replace(/^<summary>/, '').replace(/<\/summary>$/, '')
189
+ content = dedent(content)
96
190
  }
191
+
97
192
  sections.push({
98
193
  type: 'card',
99
- title: isCode ? 'Code' : 'Table',
100
- content: matchedStr.trim(),
101
- contentType: isCode ? 'code' : 'table',
194
+ title: isCode ? 'Code' : (isSummary ? 'Summary' : 'Table'),
195
+ content: content,
196
+ contentType: isCode ? 'code' : (isSummary ? 'summary' : 'table'),
102
197
  language: language
103
198
  })
104
199
  } else {
@@ -116,36 +211,131 @@ onMounted(() => {
116
211
  if (window.RENDER_DATA && Object.keys(window.RENDER_DATA).length > 0) {
117
212
  data.value = window.RENDER_DATA
118
213
  } else {
214
+ // Demo data for development preview
119
215
  data.value = {
120
- markdown: '# 测试标题\n\n(Introduction text that should appear at top)\n\n<summary>Summary content here.</summary>\n\n## Normal Section\nContent mixed with text.\n\n## 播放时间表\n| 季度 | 开始日期 | 结束日期 | 集数 |\n| :--- | :--- | :--- | :--- |\n| S1 | 2007 | 2008 | 25 |\n\n## Another Text Section\nMore text here.\n\n## 代码示例\n```python\nprint("Hello")\n```',
121
- total_time: 2.5,
216
+ markdown: `# 终极硬核整合包格雷科技新视野
217
+
218
+ <summary>
219
+ 《格雷科技:新视野》(GregTech: New Horizons,简称 GTNH)是一款基于 Minecraft 1.7.10 版本的深度硬核科技向整合包。它以 GregTech 5 Unofficial 为核心,通过超过 8 年的持续开发,将 300 多个模组深度集成,构建了极其严苛且逻辑严密的科技树,是公认的生存挑战巅峰之作。
220
+ </summary>
221
+
222
+ ## 核心机制与游戏体验
223
+ GTNH 的核心在于"格雷化"改造,几乎所有模组的合成表都经过重新设计,以匹配其严苛的阶级制度 [4][8]。玩家需要从原始的石器时代开始,历经蒸汽时代、电力时代,最终向星际航行迈进。其游戏过程极其漫长,旨在让玩家在每一毫秒的进度中感受工业发展的成就感 [3][7]。
224
+
225
+ ![GTNH 游戏场景](https://i.ytimg.com/vi/5T-oSWAgaMM/maxresdefault.jpg)
226
+
227
+ ## 科技阶层与任务系统
228
+ 整合包拥有 15 个清晰的科技等级(Tiers),最终目标是建造"星门"(Stargate)[2]。为了引导玩家不迷失在复杂的工业流程中,GTNH 内置了超过 3900 条任务的巨型任务书,涵盖了从基础生存到高阶多方块结构的详细指导 [4][7]。
229
+
230
+ - 15 个科技等级
231
+ - 任务数量:3900+
232
+ - 最终目标:建造"星门"
233
+
234
+ > 机动战士高达系列是日本动画史上最具影响力的动画作品之一,深受全球观众的喜爱。
235
+
236
+ | 特性 | 详细描述 |
237
+ | :--- | :--- |
238
+ | **基础版本** | Minecraft 1.7.10 (高度优化) |
239
+ | **任务数量** | 3900+ 任务引导 [7] |
240
+ | **科技阶层** | 15 个技术等级 [2] |
241
+ | **核心模组** | GregTech 5 Unofficial, Thaumcraft 等 [8] |
242
+
243
+ ## 安装与运行建议
244
+ 由于其高度集成的特性,官方强烈建议使用 **Prism Launcher** 进行安装和管理 [5]。在运行环境方面,虽然基于旧版 MC,但通过社区努力,目前推荐使用 **Java 17-25** 版本以获得最佳的内存管理和性能优化,确保大型自动化工厂运行流畅 [5]。
245
+
246
+ \`\`\`bash
247
+ curl -s https://raw.githubusercontent.com/GTNewHorizons/GT-New-Horizons-Modpack/master/README.md
248
+ java -version
249
+ java -Xmx1024M -Xms1024M -jar prism-launcher.jar
250
+ \`\`\``,
251
+ total_time: 8.5,
122
252
  stages: [
123
- { name: 'Agent', model: 'gpt-4', provider: 'OpenAI', time: 1.23, cost: 0.001 },
253
+ {
254
+ name: 'instruct',
255
+ model: 'qwen/qwen3-235b-a22b-2507',
256
+ provider: 'Qwen',
257
+ time: 1.83,
258
+ cost: 0.0002,
259
+ },
260
+ {
261
+ name: 'search',
262
+ model: '',
263
+ provider: '',
264
+ time: 0.5,
265
+ cost: 0.0,
266
+ references: [
267
+ { title: 'GTNH 2025 Server Information', url: 'https://stonelegion.com/mc-gtnh-2026/' },
268
+ { title: 'GT New Horizons Wiki', url: 'https://gtnh.miraheze.org/wiki/Main_Page' },
269
+ { title: 'GT New Horizons - GitHub', url: 'https://github.com/GTNewHorizons/GT-New-Horizons-Modpack' },
270
+ { title: 'GT New Horizons - CurseForge', url: 'https://www.curseforge.com/minecraft/modpacks/gt-new-horizons' },
271
+ { title: 'Installing and Migrating - GTNH', url: 'https://gtnh.miraheze.org/wiki/Installing_and_Migrating' },
272
+ { title: 'Modlist - GT New Horizons', url: 'https://wiki.gtnewhorizons.com/wiki/Modlist' },
273
+ { title: 'GregTech: New Horizons - Home', url: 'https://www.gtnewhorizons.com/' },
274
+ { title: 'GT New Horizons - FTB Wiki', url: 'https://ftb.fandom.com/wiki/GT_New_Horizons' }
275
+ ],
276
+ image_references: [
277
+ { title: 'GTNH Live Lets Play', url: 'https://i.ytimg.com/vi/5T-oSWAgaMM/maxresdefault.jpg', thumbnail: 'https://tse4.mm.bing.net/th/id/OIP.b_56VnY4nyrzeqp1JetmFQHaEK?pid=Api' },
278
+ { title: 'GTNH Modpack Cover', url: 'https://i.mcmod.cn/modpack/cover/20240113/1705139595_29797_dSkE.jpg', thumbnail: 'https://tse1.mm.bing.net/th/id/OIP.KNKaZX1d_4Ueq6vpl1qJNAHaEo?pid=Api' },
279
+ { title: 'GTNH Steam Age', url: 'https://i.ytimg.com/vi/8IPwXxqB71w/maxresdefault.jpg', thumbnail: 'https://tse4.mm.bing.net/th/id/OIP.P-KrnI4GBH21yPgwpNPSzAHaEK?pid=Api' },
280
+ { title: 'GTNH MCMod Cover', url: 'https://i.mcmod.cn/post/cover/20230201/1675241030_2_VqDc.jpg', thumbnail: 'https://tse2.mm.bing.net/th/id/OIP.GvYz7YWrg-fnpAHjOiW3OAHaEo?pid=Api' },
281
+ { title: 'GTNH Tectech Tutorial', url: 'http://i0.hdslb.com/bfs/archive/1ed1e53341fd44018138f2823b2fe6c499fb9c9c.jpg', thumbnail: 'https://tse4.mm.bing.net/th/id/OIP.0Wg7xFHTjhxIV9hKuUo4xwHaEo?pid=Api' }
282
+ ]
283
+ },
284
+ {
285
+ name: 'crawler',
286
+ model: '',
287
+ provider: '',
288
+ time: 2.5,
289
+ cost: 0.0,
290
+ crawled_pages: [
291
+ { title: 'GregTech: New Horizons Official Wiki', url: 'https://gtnh.miraheze.org/wiki/Main_Page' },
292
+ { title: 'GT New Horizons Modpack Download', url: 'https://www.curseforge.com/minecraft/modpacks/gt-new-horizons' },
293
+ { title: 'Installing and Migrating Guide', url: 'https://gtnh.miraheze.org/wiki/Installing_and_Migrating' }
294
+ ]
295
+ },
296
+ {
297
+ name: 'agent',
298
+ model: 'google/gemini-3-flash-preview',
299
+ provider: 'Google',
300
+ time: 13.0,
301
+ cost: 0.0018,
302
+ }
303
+ ],
304
+ references: [
305
+ { title: 'GTNH 2025 Server Information', url: 'https://stonelegion.com/mc-gtnh-2026/' },
306
+ { title: 'GT New Horizons Wiki', url: 'https://gtnh.miraheze.org/wiki/Main_Page' },
307
+ { title: 'GT New Horizons - GitHub', url: 'https://github.com/GTNewHorizons/GT-New-Horizons-Modpack' },
308
+ { title: 'GT New Horizons - CurseForge', url: 'https://www.curseforge.com/minecraft/modpacks/gt-new-horizons' },
309
+ { title: 'Installing and Migrating - GTNH', url: 'https://gtnh.miraheze.org/wiki/Installing_and_Migrating' },
310
+ { title: 'Modlist - GT New Horizons', url: 'https://wiki.gtnewhorizons.com/wiki/Modlist' },
311
+ { title: 'GregTech: New Horizons - Home', url: 'https://www.gtnewhorizons.com/' },
312
+ { title: 'GT New Horizons - FTB Wiki', url: 'https://ftb.fandom.com/wiki/GT_New_Horizons' }
124
313
  ],
125
- references: [],
126
- page_references: [],
127
- image_references: [],
128
- stats: { total_time: 2.5 }
314
+ page_references: [
315
+ { title: 'GregTech: New Horizons Official Wiki', url: 'https://gtnh.miraheze.org/wiki/Main_Page' },
316
+ { title: 'GT New Horizons Modpack Download', url: 'https://www.curseforge.com/minecraft/modpacks/gt-new-horizons' }
317
+ ],
318
+ image_references: [
319
+ { title: 'GTNH Live Lets Play', url: 'https://i.ytimg.com/vi/5T-oSWAgaMM/maxresdefault.jpg', thumbnail: 'https://tse4.mm.bing.net/th/id/OIP.b_56VnY4nyrzeqp1JetmFQHaEK?pid=Api' },
320
+ { title: 'GTNH Modpack Cover', url: 'https://i.mcmod.cn/modpack/cover/20240113/1705139595_29797_dSkE.jpg', thumbnail: 'https://tse1.mm.bing.net/th/id/OIP.KNKaZX1d_4Ueq6vpl1qJNAHaEo?pid=Api' },
321
+ { title: 'GTNH Steam Age', url: 'https://i.ytimg.com/vi/8IPwXxqB71w/maxresdefault.jpg', thumbnail: 'https://tse4.mm.bing.net/th/id/OIP.P-KrnI4GBH21yPgwpNPSzAHaEK?pid=Api' }
322
+ ],
323
+ stats: { total_time: 8.5 },
324
+ theme_color: '#ef4444'
129
325
  }
130
326
  }
131
327
  })
132
328
  </script>
133
329
 
134
330
  <template>
135
- <div class="min-h-screen bg-[#f2f2f2] flex justify-center selection:bg-red-100 selection:text-red-900">
331
+ <div class="min-h-screen bg-[#f2f2f2] flex justify-center" :style="themeStyle">
136
332
  <!-- Main container with explicit background for screenshot capture -->
137
- <div id="main-container" class="w-full max-w-[450px] py-10 space-y-6 !bg-[#f2f2f2]" data-theme="light">
333
+ <div id="main-container" class="w-[540px] pt-16 pb-28 space-y-8 !bg-[#f2f2f2]" data-theme="light">
138
334
 
139
335
  <!-- Title -->
140
- <header v-if="mainTitle" class="px-7 mb-8">
141
- <div class="h-1 w-12 bg-red-500 mb-4"></div>
142
- <h1 class="text-3xl font-black text-gray-900 leading-tight tracking-tighter uppercase tabular-nums">
143
- {{ mainTitle }}
144
- </h1>
145
- <div class="mt-2 text-[10px] font-mono text-gray-900 tracking-widest flex items-center justify-end gap-2">
146
- Report by {{ agentModel }}
147
- <span class="w-2 h-2 bg-red-500"></span>
148
- </div>
336
+ <header v-if="mainTitle" class="px-6 mb-8">
337
+ <!-- Removed Time/Icon Badge as requested -->
338
+ <h1 class="text-4xl font-black text-gray-800 leading-tight tracking-tighter uppercase tabular-nums" v-html="processedTitle"></h1>
149
339
  </header>
150
340
 
151
341
  <!-- Content Sections -->
@@ -157,24 +347,28 @@ onMounted(() => {
157
347
  :markdown="section.content"
158
348
  :num-search-refs="numSearchRefs"
159
349
  :num-page-refs="numPageRefs"
160
- class="prose-h2:text-xl prose-h2:font-black prose-h2:uppercase prose-h2:tracking-tight prose-h2:mb-4"
350
+ class="prose-h2:text-[26px] prose-h2:font-black prose-h2:uppercase prose-h2:tracking-tight prose-h2:mb-4 prose-h2:text-gray-800"
161
351
  />
162
352
  </div>
163
353
 
164
- <!-- Special Card (Table/Code) -->
165
- <div v-else-if="section.type === 'card'" class="mx-6">
166
- <!-- Header for both Table and Code -->
167
- <!-- Header for Code only -->
168
- <div v-if="section.contentType !== 'table'" class="px-4 py-1.5 bg-gray-50 flex items-center justify-between ">
169
- <div class="flex items-center gap-2">
170
- <Icon icon="mdi:code-braces" class="text-red-500 text-sm" />
171
- <span class="font-black text-[10px] text-gray-700 uppercase tracking-widest">{{ section.title }}</span>
172
- </div>
173
- <div v-if="section.language" class="text-gray-600 text-[9px] font-mono tabular-nums tracking-tighter">
174
- {{ section.language }}
175
- </div>
354
+ <!-- Special Card (Table/Code/Summary) -->
355
+ <div v-else-if="section.type === 'card'" class="mx-6 relative">
356
+ <!-- Corner Rectangle Badge with Icon and Label -->
357
+ <div
358
+ class="absolute -top-2 -left-2 h-7 px-2.5 z-10 flex items-center gap-1.5"
359
+ :style="{ backgroundColor: themeColor, color: headerTextColor, boxShadow: '0 2px 4px 0 rgba(0,0,0,0.15)' }"
360
+ >
361
+ <Icon :icon="getCardIcon(section.contentType)" class="text-base" />
362
+ <span class="text-xs font-bold uppercase tracking-wide">{{ getCardLabel(section.contentType, section.language) }}</span>
176
363
  </div>
177
- <div class="bg-white ">
364
+ <div
365
+ class="shadow-sm shadow-black/10 bg-white"
366
+ :class="[
367
+ section.contentType === 'summary' ? 'pt-8 px-5 pb-3 text-base leading-relaxed' : '',
368
+ section.contentType === 'code' ? 'pt-7 pb-2' : '',
369
+ section.contentType === 'table' ? 'pt-5' : ''
370
+ ]"
371
+ >
178
372
  <MarkdownContent
179
373
  :markdown="section.content"
180
374
  :bare="true"
@@ -187,17 +381,16 @@ onMounted(() => {
187
381
  </template>
188
382
 
189
383
  <!-- Workflow -->
190
- <div v-if="data?.stages?.length" class="mx-6">
191
- <div class="px-4 py-2 bg-gray-50 flex items-center justify-between ">
192
- <div class="flex items-center gap-2">
193
- <Icon icon="mdi:chart-timeline-variant" class="text-red-500 text-sm" />
194
- <span class="font-black text-[10px] text-gray-700 uppercase tracking-widest">FLOW</span>
195
- </div>
196
- <div class="text-[9px] font-mono text-gray-600 tracking-tighter tabular-nums">
197
- {{ data.stages.length }} Stages
198
- </div>
384
+ <div v-if="data?.stages?.length" class="mx-6 relative">
385
+ <!-- Corner Rectangle Badge with Icon and Label -->
386
+ <div
387
+ class="absolute -top-2 -left-2 h-7 px-2.5 z-10 flex items-center gap-1.5"
388
+ :style="{ backgroundColor: themeColor, color: headerTextColor, boxShadow: '0 2px 4px 0 rgba(0,0,0,0.15)' }"
389
+ >
390
+ <Icon icon="mdi:link-variant" class="text-base" />
391
+ <span class="text-xs font-bold uppercase tracking-wide">Flow</span>
199
392
  </div>
200
- <div class="p-4 bg-white ">
393
+ <div class="p-2 pt-5 bg-white shadow-sm shadow-black/10">
201
394
  <StageCard
202
395
  v-for="(stage, index) in data.stages"
203
396
  :key="index"
@@ -205,6 +398,7 @@ onMounted(() => {
205
398
  :is-first="index === 0"
206
399
  :is-last="index === data.stages.length - 1"
207
400
  :prev-stage-name="index > 0 ? data.stages[index - 1]?.name : undefined"
401
+ :ref-offset="getRefOffset(index)"
208
402
  />
209
403
  </div>
210
404
  </div>