lumina-slides 8.9.4 → 9.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 (119) hide show
  1. package/LUMINA_LLM_EXAMPLES.json +234 -0
  2. package/README.md +18 -18
  3. package/dist/lumina-slides.js +13207 -12659
  4. package/dist/lumina-slides.umd.cjs +215 -215
  5. package/dist/style.css +1 -1
  6. package/package.json +5 -4
  7. package/src/App.vue +16 -0
  8. package/src/animation/index.ts +11 -0
  9. package/src/animation/registry.ts +126 -0
  10. package/src/animation/stagger.ts +95 -0
  11. package/src/animation/types.ts +53 -0
  12. package/src/components/LandingPage.vue +229 -0
  13. package/src/components/LuminaDeck.vue +224 -0
  14. package/src/components/LuminaSpeakerNotes.vue +701 -0
  15. package/src/components/base/BaseSlide.vue +122 -0
  16. package/src/components/base/LuminaElement.vue +67 -0
  17. package/src/components/base/VideoPlayer.vue +204 -0
  18. package/src/components/layouts/LayoutAuto.vue +71 -0
  19. package/src/components/layouts/LayoutChart.vue +287 -0
  20. package/src/components/layouts/LayoutCustom.vue +92 -0
  21. package/src/components/layouts/LayoutDiagram.vue +253 -0
  22. package/src/components/layouts/LayoutFeatures.vue +121 -0
  23. package/src/components/layouts/LayoutFlex.vue +172 -0
  24. package/src/components/layouts/LayoutFree.vue +62 -0
  25. package/src/components/layouts/LayoutHalf.vue +127 -0
  26. package/src/components/layouts/LayoutStatement.vue +74 -0
  27. package/src/components/layouts/LayoutSteps.vue +106 -0
  28. package/src/components/layouts/LayoutTimeline.vue +104 -0
  29. package/src/components/layouts/LayoutVideo.vue +41 -0
  30. package/src/components/parts/FlexBullets.vue +45 -0
  31. package/src/components/parts/FlexButton.vue +132 -0
  32. package/src/components/parts/FlexImage.vue +54 -0
  33. package/src/components/parts/FlexOrdered.vue +44 -0
  34. package/src/components/parts/FlexSpacer.vue +13 -0
  35. package/src/components/parts/FlexStepper.vue +59 -0
  36. package/src/components/parts/FlexText.vue +29 -0
  37. package/src/components/parts/FlexTimeline.vue +67 -0
  38. package/src/components/parts/FlexTitle.vue +39 -0
  39. package/src/components/parts/LuminaBackground.vue +100 -0
  40. package/src/components/site/LivePreview.vue +101 -0
  41. package/src/components/site/SiteApi.vue +301 -0
  42. package/src/components/site/SiteDashboard.vue +604 -0
  43. package/src/components/site/SiteDocs.vue +3267 -0
  44. package/src/components/site/SiteExamples.vue +65 -0
  45. package/src/components/site/SiteFooter.vue +6 -0
  46. package/src/components/site/SiteHome.vue +362 -0
  47. package/src/components/site/SiteNavBar.vue +122 -0
  48. package/src/components/site/SitePlayground.vue +389 -0
  49. package/src/components/site/SitePromptBuilder.vue +266 -0
  50. package/src/components/site/SiteUserMenu.vue +90 -0
  51. package/src/components/studio/ActionEditor.vue +108 -0
  52. package/src/components/studio/ArrayEditor.vue +124 -0
  53. package/src/components/studio/CollapsibleSection.vue +33 -0
  54. package/src/components/studio/ColorField.vue +22 -0
  55. package/src/components/studio/EditorCanvas.vue +326 -0
  56. package/src/components/studio/EditorLayoutFeatures.vue +18 -0
  57. package/src/components/studio/EditorLayoutFixed.vue +46 -0
  58. package/src/components/studio/EditorLayoutFlex.vue +133 -0
  59. package/src/components/studio/EditorLayoutHalf.vue +18 -0
  60. package/src/components/studio/EditorLayoutStatement.vue +18 -0
  61. package/src/components/studio/EditorLayoutSteps.vue +18 -0
  62. package/src/components/studio/EditorLayoutTimeline.vue +18 -0
  63. package/src/components/studio/EditorNode.vue +89 -0
  64. package/src/components/studio/FieldEditor.vue +133 -0
  65. package/src/components/studio/IconPicker.vue +109 -0
  66. package/src/components/studio/LayerItem.vue +117 -0
  67. package/src/components/studio/LuminaStudio.vue +30 -0
  68. package/src/components/studio/SaveSuccessModal.vue +138 -0
  69. package/src/components/studio/SlideNavigator.vue +373 -0
  70. package/src/components/studio/SliderField.vue +44 -0
  71. package/src/components/studio/StudioInspector.vue +595 -0
  72. package/src/components/studio/StudioJsonEditor.vue +191 -0
  73. package/src/components/studio/StudioLayers.vue +145 -0
  74. package/src/components/studio/StudioSettings.vue +514 -0
  75. package/src/components/studio/StudioSidebar.vue +29 -0
  76. package/src/components/studio/StudioToolbar.vue +222 -0
  77. package/src/components/studio/fieldLabels.ts +224 -0
  78. package/src/components/studio/inspectors/DiagramEdgeEditor.vue +77 -0
  79. package/src/components/studio/inspectors/DiagramNodeEditor.vue +117 -0
  80. package/src/components/studio/nodes/StudioDiagramNode.vue +138 -0
  81. package/src/composables/useAuth.ts +87 -0
  82. package/src/composables/useEditor.ts +224 -0
  83. package/src/composables/useElementState.ts +81 -0
  84. package/src/composables/useFlexLayout.ts +122 -0
  85. package/src/composables/useKeyboard.ts +45 -0
  86. package/src/composables/useLumina.ts +32 -0
  87. package/src/composables/useStudio.ts +87 -0
  88. package/src/composables/useSwipeNav.ts +53 -0
  89. package/src/composables/useTransition.ts +373 -0
  90. package/src/core/Lumina.ts +819 -0
  91. package/src/core/animationConfig.ts +251 -0
  92. package/src/core/compression.ts +34 -0
  93. package/src/core/elementController.ts +170 -0
  94. package/src/core/elementId.ts +27 -0
  95. package/src/core/elementResolver.ts +207 -0
  96. package/src/core/events.ts +53 -0
  97. package/src/core/fonts.ts +100 -0
  98. package/src/core/presets.ts +231 -0
  99. package/src/core/prompts.ts +272 -0
  100. package/src/core/schema.ts +478 -0
  101. package/src/core/speaker-channel.ts +250 -0
  102. package/src/core/store.ts +461 -0
  103. package/src/core/theme.ts +666 -0
  104. package/src/core/types.ts +1611 -0
  105. package/src/directives/vStudio.ts +45 -0
  106. package/src/index.ts +175 -0
  107. package/src/main.ts +17 -0
  108. package/src/router/index.ts +92 -0
  109. package/src/style/main.css +462 -0
  110. package/src/utils/deep.ts +127 -0
  111. package/src/utils/firebase.ts +184 -0
  112. package/src/utils/streaming.ts +134 -0
  113. package/src/views/DashboardView.vue +32 -0
  114. package/src/views/DeckView.vue +289 -0
  115. package/src/views/HomeView.vue +17 -0
  116. package/src/views/SiteLayout.vue +21 -0
  117. package/src/views/StudioView.vue +61 -0
  118. package/src/vite-env.d.ts +6 -0
  119. package/IMPLEMENTATION.md +0 -418
@@ -0,0 +1,100 @@
1
+ <template>
2
+ <div class="absolute inset-0 z-0 overflow-hidden pointer-events-none select-none" ref="containerRef">
3
+ <!-- Ambient Orb -->
4
+ <div ref="orbRef" class="ambient-orb" :style="orbStyle"></div>
5
+ <!-- Noise Texture -->
6
+ <div class="absolute inset-0 opacity-[0.04]"
7
+ style="background-image: url('https://grainy-gradients.vercel.app/noise.svg');"></div>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
13
+ import gsap from 'gsap';
14
+
15
+ const props = withDefaults(defineProps<{
16
+ orbColor?: string;
17
+ orbPos?: { top: string; left: string };
18
+ orbOpacity?: number;
19
+ orbBlur?: string;
20
+ orbSize?: string;
21
+ }>(), {
22
+ orbColor: 'var(--lumina-color-primary, #3b82f6)',
23
+ orbPos: () => ({ top: '-20%', left: '-10%' }),
24
+ orbSize: 'var(--lumina-orb-size, 60vw)',
25
+ });
26
+
27
+ const orbRef = ref<HTMLElement | null>(null);
28
+ const containerRef = ref<HTMLElement | null>(null);
29
+ let ctx: gsap.Context | null = null; // GSAP Context for cleanup
30
+
31
+ const orbStyle = computed(() => {
32
+ return {
33
+ backgroundColor: props.orbColor,
34
+ width: props.orbSize,
35
+ height: props.orbSize,
36
+ top: props.orbPos.top,
37
+ left: props.orbPos.left,
38
+ transition: 'top 1.2s cubic-bezier(0.2, 0.8, 0.2, 1), left 1.2s cubic-bezier(0.2, 0.8, 0.2, 1)' // Smoother transition
39
+ };
40
+ });
41
+
42
+ // Setup GSAP Animations
43
+ onMounted(() => {
44
+ ctx = gsap.context(() => {
45
+ if (!orbRef.value) return;
46
+
47
+ // 1. Breathing Animation (Subtle Pulse)
48
+ gsap.to(orbRef.value, {
49
+ scale: 1.1,
50
+ opacity: '+=0.1',
51
+ duration: 8,
52
+ ease: 'sine.inOut',
53
+ yoyo: true,
54
+ repeat: -1
55
+ });
56
+
57
+ // 2. Mouse Parallax Effect
58
+ const onMouseMove = (e: MouseEvent) => {
59
+ const { innerWidth, innerHeight } = window;
60
+ const x = (e.clientX / innerWidth - 0.5) * 50; // Move 50px max
61
+ const y = (e.clientY / innerHeight - 0.5) * 50;
62
+
63
+ gsap.to(orbRef.value, {
64
+ x: x,
65
+ y: y,
66
+ duration: 2,
67
+ ease: 'power2.out',
68
+ overwrite: 'auto'
69
+ });
70
+ };
71
+
72
+ window.addEventListener('mousemove', onMouseMove);
73
+
74
+ // Cleanup listener inside context cleanup? No, GSAP context doesn't auto-remove event listeners.
75
+ // We need to remove it manually.
76
+ // But we can store the remove function to call it in onUnmounted.
77
+ return () => window.removeEventListener('mousemove', onMouseMove);
78
+
79
+ }, containerRef.value || undefined);
80
+ });
81
+
82
+ onUnmounted(() => {
83
+ ctx?.revert(); // Restores original state and kills animations
84
+ });
85
+ </script>
86
+
87
+ <style scoped>
88
+ /* Ensure the orb style is available locally even if global css fails (fallback) */
89
+ .ambient-orb {
90
+ position: absolute;
91
+ border-radius: 50%;
92
+ /* Use global variables injected by ThemeManager */
93
+ filter: blur(var(--lumina-orb-blur, 120px));
94
+ opacity: var(--lumina-orb-opacity-effective, 0.20);
95
+ pointer-events: none;
96
+ will-change: transform, top, left;
97
+ /* Hint to browser */
98
+ transform-origin: center center;
99
+ }
100
+ </style>
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <div class="flex flex-col lg:flex-row gap-6 h-[600px] lg:h-[400px] not-prose">
3
+ <!-- Editor -->
4
+ <div class="w-full lg:w-1/3 flex flex-col bg-[#0f0f0f] border border-white/10 rounded-xl overflow-hidden">
5
+ <div class="bg-white/5 px-4 py-2 border-b border-white/10 flex justify-between items-center">
6
+ <span class="text-xs font-bold text-white/50 uppercase tracking-wider">JSON Editor</span>
7
+ <span v-if="error" class="text-xs text-red-400">{{ error }}</span>
8
+ </div>
9
+ <textarea v-model="code"
10
+ class="flex-1 bg-transparent p-4 font-mono text-xs text-blue-300 resize-none focus:outline-none leading-relaxed"
11
+ spellcheck="false"></textarea>
12
+ </div>
13
+
14
+ <!-- Preview -->
15
+ <div class="w-full lg:w-2/3 bg-black border border-white/10 rounded-xl overflow-hidden relative group">
16
+ <div :id="containerId" class="w-full h-full"></div>
17
+
18
+ <!-- Reset Button (Visible on hover) -->
19
+ <button @click="reset"
20
+ class="absolute top-4 right-4 px-3 py-1.5 bg-black/50 hover:bg-black/80 text-white/50 hover:text-white text-xs rounded-lg backdrop-blur-sm border border-white/10 transition opacity-0 group-hover:opacity-100">
21
+ <i class="ph-thin ph-arrow-counter-clockwise mr-1"></i> Reset
22
+ </button>
23
+ </div>
24
+ </div>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
29
+ import { Lumina } from '../../core/Lumina';
30
+ import { parsePartialJson } from '../../utils/streaming';
31
+
32
+ const props = defineProps<{
33
+ initialCode: string;
34
+ }>();
35
+
36
+ const code = ref(props.initialCode);
37
+ const error = ref<string | null>(null);
38
+ const containerId = `preview-${Math.random().toString(36).substr(2, 9)}`;
39
+ let engine: Lumina | null = null;
40
+
41
+ function initEngine() {
42
+ if (engine) engine.destroy();
43
+
44
+ // Clear container
45
+ const el = document.getElementById(containerId);
46
+ if (el) el.innerHTML = '';
47
+
48
+ // Initialize
49
+ engine = new Lumina(`#${containerId}`, {
50
+ ui: { visible: true, showControls: true, showProgressBar: false },
51
+ keyboard: false,
52
+ animation: { enabled: true, durationIn: 0.5 }
53
+ });
54
+
55
+ updateSlide();
56
+ }
57
+
58
+ function updateSlide() {
59
+ if (!engine) return;
60
+
61
+ try {
62
+ const parsed = parsePartialJson(code.value);
63
+ if (parsed && typeof parsed === 'object') {
64
+ // Force container sizing for docs
65
+ if (Array.isArray(parsed.slides)) {
66
+ parsed.slides.forEach((s: any) => s.sizing = 'container');
67
+ engine.load(parsed);
68
+ } else {
69
+ // If it's a single slide object, wrap it
70
+ engine.load({
71
+ meta: { title: "Preview" },
72
+ slides: [{ ...parsed, sizing: 'container' }]
73
+ });
74
+ }
75
+ error.value = null;
76
+ }
77
+ } catch (e) {
78
+ // Silent fail on partial parse, but maybe show error if strictly invalid JSON?
79
+ // parsePartialJson is very forgiving.
80
+ }
81
+ }
82
+
83
+ function reset() {
84
+ code.value = props.initialCode;
85
+ }
86
+
87
+ watch(code, () => {
88
+ updateSlide();
89
+ });
90
+
91
+ onMounted(() => {
92
+ // Delay init to ensure DOM is ready
93
+ setTimeout(() => {
94
+ initEngine();
95
+ }, 100);
96
+ });
97
+
98
+ onUnmounted(() => {
99
+ if (engine) engine.destroy();
100
+ });
101
+ </script>
@@ -0,0 +1,301 @@
1
+ <template>
2
+ <div class="min-h-screen pt-32 px-8 max-w-7xl mx-auto pb-32">
3
+ <!-- Unified Header -->
4
+ <div class="text-center mb-16 max-w-3xl mx-auto">
5
+ <h1 class="text-4xl md:text-5xl font-black mb-4 tracking-tight text-white">API Reference</h1>
6
+ <p class="text-lg text-white/50 leading-relaxed">Technical documentation and module reference for the Lumina
7
+ Engine.</p>
8
+ </div>
9
+
10
+ <div v-if="loading" class="text-white/50 animate-pulse text-center">Generated docs loading...</div>
11
+ <div v-else-if="error" class="text-red-400 text-center">Failed to load API docs. {{ error }}</div>
12
+
13
+ <div v-else class="space-y-24">
14
+ <div v-for="kind in apiData" :key="kind.name" class="space-y-12">
15
+ <div class="border-b border-white/10 pb-12 mb-12">
16
+ <h2 class="text-3xl font-bold text-white mb-2">{{ kind.name }}</h2>
17
+ <p class="text-blue-400 font-mono text-sm tracking-widest uppercase">@PailletJuanPablo/lumina-slides
18
+ </p>
19
+ </div>
20
+
21
+ <!-- 1. Classes -->
22
+ <div v-if="hasChildren(kind, [128])" class="space-y-16">
23
+ <div v-for="cls in getChildren(kind, [128])" :key="cls.id" class="space-y-6">
24
+ <div class="flex items-baseline gap-4">
25
+ <h3 class="text-3xl font-bold text-emerald-400">{{ cls.name }}</h3>
26
+ <span
27
+ class="px-3 py-1 rounded-full bg-emerald-500/10 text-emerald-300 text-xs font-bold uppercase tracking-wider">Class</span>
28
+ </div>
29
+
30
+ <div class="prose prose-invert max-w-none text-white/80 text-lg leading-relaxed"
31
+ v-html="renderComment(cls.comment)"></div>
32
+
33
+ <!-- Examples -->
34
+ <div v-if="getDbTags(cls.comment, '@example').length" class="space-y-4 my-8">
35
+ <div v-for="(example, i) in getDbTags(cls.comment, '@example')" :key="i"
36
+ class="rounded-xl overflow-hidden border border-white/10 bg-[#0A0A0A]">
37
+ <div
38
+ class="px-4 py-2 bg-white/5 border-b border-white/5 text-xs font-bold text-white/40 uppercase">
39
+ Example
40
+ </div>
41
+ <div class="p-6 font-mono text-sm text-blue-200 whitespace-pre-wrap overflow-x-auto"
42
+ v-html="renderMarkdown(example)"></div>
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Constructor -->
47
+ <div class="bg-white/5 rounded-2xl border border-white/10 overflow-hidden">
48
+ <div class="px-8 py-6 border-b border-white/5">
49
+ <h4 class="text-xl font-bold">Constructor</h4>
50
+ </div>
51
+ <div class="p-8 space-y-8">
52
+ <div v-for="method in getChildren(cls, [512])" :key="method.id">
53
+ <code class="text-lg md:text-xl font-bold text-emerald-200 block mb-4">
54
+ new {{ cls.name }}(<span v-html="renderParams(method.signatures[0])"></span>)
55
+ </code>
56
+ <div class="text-white/70 mb-4"
57
+ v-html="renderComment(method.signatures[0].comment)"></div>
58
+ <div v-if="method.signatures[0].parameters" class="space-y-2">
59
+ <div v-for="param in method.signatures[0].parameters" :key="param.id"
60
+ class="flex gap-4 text-sm">
61
+ <div class="w-32 shrink-0 font-mono text-purple-300">{{ param.name }}:</div>
62
+ <div class="text-white/50">{{ renderType(param.type) }}</div>
63
+ <div class="text-white/70" v-html="renderComment(param.comment)"></div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- Methods -->
71
+ <div v-if="getChildren(cls, [2048]).length">
72
+ <h4 class="text-2xl font-bold mb-6 mt-12">Methods</h4>
73
+ <div class="grid gap-6">
74
+ <div v-for="method in getChildren(cls, [2048])" :key="method.id"
75
+ class="bg-white/5 p-8 rounded-2xl border border-white/10 hover:border-emerald-500/30 transition duration-300">
76
+ <div class="flex items-start justify-between mb-4">
77
+ <code class="text-xl font-bold text-emerald-200">
78
+ {{ method.name }}(<span v-html="renderParams(method.signatures[0])"></span>)
79
+ </code>
80
+ <span class="text-xs font-mono text-white/40">Points: {{ method.id }}</span>
81
+ </div>
82
+
83
+ <div class="prose prose-invert max-w-none text-white/70 mb-4"
84
+ v-html="renderComment(method.signatures[0].comment)"></div>
85
+
86
+ <!-- Method Params Table -->
87
+ <div v-if="method.signatures[0].parameters"
88
+ class="mt-6 pt-6 border-t border-white/5">
89
+ <h5 class="text-xs font-bold uppercase text-white/40 mb-3">Parameters</h5>
90
+ <div class="space-y-3">
91
+ <div v-for="param in method.signatures[0].parameters" :key="param.id"
92
+ class="grid grid-cols-[120px_1fr] md:grid-cols-[150px_200px_1fr] gap-4 text-sm">
93
+ <div class="font-mono text-purple-300">{{ param.name }}</div>
94
+ <div class="font-mono text-white/50 truncate"
95
+ v-html="renderType(param.type, true)"></div>
96
+ <div class="text-white/70" v-html="renderComment(param.comment)"></div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <!-- 2. Interfaces -->
107
+ <div v-if="hasChildren(kind, [256])" class="space-y-12">
108
+ <h3 class="text-3xl font-bold text-pink-400 border-b border-white/10 pb-4">Interfaces & Types</h3>
109
+ <div class="grid gap-12">
110
+ <div v-for="iface in getChildren(kind, [256])" :key="iface.id" class="space-y-6">
111
+ <div class="flex items-baseline gap-4">
112
+ <h4 class="text-2xl font-bold text-pink-300">{{ iface.name }}</h4>
113
+ <span
114
+ class="px-2 py-0.5 rounded bg-pink-500/10 text-pink-300 text-xs font-bold uppercase">Interface</span>
115
+ </div>
116
+ <div class="prose prose-invert max-w-none text-white/80"
117
+ v-html="renderComment(iface.comment)"></div>
118
+
119
+ <!-- Examples -->
120
+ <div v-if="getDbTags(iface.comment, '@example').length" class="space-y-4 my-6">
121
+ <div v-for="(example, i) in getDbTags(iface.comment, '@example')" :key="i"
122
+ class="rounded-xl overflow-hidden border border-white/10 bg-[#0A0A0A]">
123
+ <div
124
+ class="px-4 py-2 bg-white/5 border-b border-white/5 text-xs font-bold text-white/40 uppercase">
125
+ Example Structure
126
+ </div>
127
+ <div class="p-6 font-mono text-xs md:text-sm text-blue-200 whitespace-pre-wrap overflow-x-auto"
128
+ v-html="renderMarkdown(example)"></div>
129
+ </div>
130
+ </div>
131
+
132
+ <!-- Interface Properties -->
133
+ <div v-if="getChildren(iface, [1024]).length"
134
+ class="bg-white/5 rounded-xl border border-white/10 overflow-hidden">
135
+ <div
136
+ class="grid grid-cols-[200px_1fr_1fr] gap-0 text-sm font-bold bg-white/5 border-b border-white/10 p-4 text-white/40 uppercase tracking-wider">
137
+ <div>Property</div>
138
+ <div>Type</div>
139
+ <div>Description</div>
140
+ </div>
141
+ <div class="divide-y divide-white/5">
142
+ <div v-for="prop in getChildren(iface, [1024])" :key="prop.id"
143
+ class="grid grid-cols-[200px_1fr_1fr] gap-4 p-4 hover:bg-white/5 transition">
144
+ <div class="font-mono text-purple-300">{{ prop.name }}<span
145
+ v-if="prop.flags.isOptional" class="text-white/30">?</span></div>
146
+ <div class="font-mono text-white/60 break-words"
147
+ v-html="renderType(prop.type, true)"></div>
148
+ <div class="text-white/70" v-html="renderComment(prop.comment)"></div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </template>
160
+
161
+ <script setup lang="ts">
162
+ import { ref, onMounted } from 'vue';
163
+
164
+ const apiData = ref<any[]>([]);
165
+ const loading = ref(true);
166
+ const error = ref<string | null>(null);
167
+
168
+ // Kind Mapping for TypeDoc
169
+ const KIND_MAP: Record<number, string> = {
170
+ 32: 'Variable',
171
+ 64: 'Function',
172
+ 128: 'Class',
173
+ 256: 'Interface',
174
+ 512: 'Constructor',
175
+ 1024: 'Property',
176
+ 2048: 'Method',
177
+ 4194304: 'Type'
178
+ };
179
+ const hasChildren = (node: any, kinds: number[]) => node.children?.some((c: any) => kinds.includes(c.kind));
180
+ const getChildren = (node: any, kinds: number[]) => node.children ? node.children.filter((c: any) => kinds.includes(c.kind)) : [];
181
+
182
+ // --- Content Rendering Helpers ---
183
+
184
+ const renderComment = (comment: any) => {
185
+ if (!comment?.summary) return '';
186
+ const text = comment.summary.map((s: any) => s.text).join('');
187
+ return renderMarkdown(text);
188
+ };
189
+
190
+ // Simple Markdown Parser (Bold, Code, Links)
191
+ const renderMarkdown = (text: string) => {
192
+ if (!text) return '';
193
+ return text
194
+ .replace(/```([\s\S]*?)```/g, '<pre class="bg-black/50 p-4 rounded-lg my-2"><code class="text-sm">$1</code></pre>') // Code Blocks
195
+ .replace(/`([^`]+)`/g, '<code class="bg-white/10 px-1.5 py-0.5 rounded text-sm font-mono text-blue-200">$1</code>') // Inline Code
196
+ .replace(/\*\*(.*?)\*\*/g, '<strong class="text-white">$1</strong>') // Bold
197
+ .replace(/\n/g, '<br>'); // Line Breaks
198
+ };
199
+
200
+ const getDbTags = (comment: any, tagName: string) => {
201
+ if (!comment?.blockTags) return [];
202
+ return comment.blockTags
203
+ .filter((t: any) => t.tag === tagName)
204
+ .map((t: any) => t.content.map((c: any) => c.text).join(''));
205
+ };
206
+
207
+ // --- Type Resolution & Rendering ---
208
+ const findTypeByName = (name: string) => {
209
+ // Search top-level children for the type definition
210
+ if (!apiData.value[0]?.children) return null;
211
+ return apiData.value[0].children.find((c: any) => c.name === name);
212
+ };
213
+
214
+ const renderType = (type: any, expandRefs = false, depth = 0): string => {
215
+ if (!type) return 'void';
216
+ if (depth > 2) return '<span class="text-white/30">...</span>'; // Prevent infinite recursion
217
+
218
+ // String Logic
219
+ if (type.type === 'intrinsic') return `<span class="text-blue-300 font-mono">${type.name}</span>`;
220
+ if (type.type === 'literal') return `<span class="text-green-300 font-mono">'${type.value}'</span>`;
221
+
222
+ // References (Links or Expanded)
223
+ if (type.type === 'reference') {
224
+ const typeName = type.name;
225
+
226
+ // Always expand specific complex types inline for better DX
227
+ const typesToExpand = ['SlideType', 'LuminaEventType', 'ThemeConfig', 'LuminaKeyBindings', 'LuminaUIOptions', 'LuminaAnimationOptions'];
228
+ if (expandRefs || typesToExpand.includes(typeName)) {
229
+ const def = findTypeByName(typeName);
230
+ if (def) {
231
+ // If it's a type alias (union/reflection), render it
232
+ if (def.kind === 4194304 && def.type) return renderType(def.type, false, depth + 1);
233
+ // If it's an interface (likely has properties)
234
+ if (def.kind === 256) {
235
+ // Check if it has children properties we can render as an object literal
236
+ const props = getChildren(def, [1024]);
237
+ if (props && props.length) {
238
+ // Build synthetic reflection to reuse object rendering logic
239
+ return renderType({
240
+ type: 'reflection',
241
+ declaration: { children: props }
242
+ }, false, depth + 1);
243
+ }
244
+ }
245
+ }
246
+ }
247
+
248
+ return `<span class="text-yellow-300 font-bold border-b border-white/10 decoration-dotted cursor-help" title="Reference">${typeName}</span>`;
249
+ }
250
+
251
+ // Unions (e.g. 'a' | 'b')
252
+ if (type.type === 'union') {
253
+ return type.types.map((t: any) => renderType(t, expandRefs, depth)).join('<span class="text-white/40 mx-2">|</span>');
254
+ }
255
+
256
+ // Arrays (e.g. Slide[])
257
+ if (type.type === 'array') {
258
+ return `${renderType(type.elementType, expandRefs, depth)}<span class="text-white/60">[]</span>`;
259
+ }
260
+
261
+ // Reflection (Object literals: { a: string })
262
+ if (type.type === 'reflection' && type.declaration) {
263
+ if (type.declaration.children) {
264
+ const props = type.declaration.children.map((c: any) => {
265
+ return `<span class="text-purple-300">${c.name}</span>: ${renderType(c.type, false, depth + 1)}`;
266
+ });
267
+ return `{ <span class="text-white/80">${props.join(', ')}</span> }`;
268
+ }
269
+ return 'object';
270
+ }
271
+
272
+ return 'any';
273
+ };
274
+
275
+ const renderParams = (sig: any) => {
276
+ if (!sig?.parameters) return '';
277
+ return sig.parameters.map((p: any) => `<span class="text-purple-300">${p.name}</span>: ${renderType(p.type)}`).join(', ');
278
+ };
279
+
280
+ onMounted(async () => {
281
+ try {
282
+ // Use relative path to ensure it works on both local and GH pages
283
+ // The base URL is handled by the browser resolving relative paths
284
+ const res = await fetch('./api-docs.json');
285
+ if (!res.ok) {
286
+ console.error('Fetch failed:', res.status, res.statusText, res.url);
287
+ throw new Error(`Docs not found (${res.status})`);
288
+ }
289
+ const json = await res.json();
290
+ // TypeDoc structure often wraps everything in a root object
291
+ // We ensure we have an array for the loop
292
+ if (json.children) apiData.value = [json];
293
+ else apiData.value = [];
294
+
295
+ } catch (e) {
296
+ error.value = (e as Error).message;
297
+ } finally {
298
+ loading.value = false;
299
+ }
300
+ });
301
+ </script>