@alphacifer/slidev-addon-theme 0.0.8 → 0.0.10

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 (41) hide show
  1. package/README.md +80 -0
  2. package/components/defense-in-depth/arc-did/ArcDiD.vue +732 -0
  3. package/components/defense-in-depth/arc-did/ArcDiDLayer.vue +321 -0
  4. package/components/defense-in-depth/arc-did/ArcDiDLayerPart.vue +449 -0
  5. package/components/defense-in-depth/arc-did/ArcDiDLayerParts.vue +11 -0
  6. package/components/defense-in-depth/arc-did/ArcDiDLayerSector.vue +148 -0
  7. package/components/defense-in-depth/arc-did/ArcDiDLayerTitle.vue +98 -0
  8. package/components/pyramid/circular-pyramid/CircularPyramid.vue +858 -0
  9. package/components/pyramid/circular-pyramid/CircularPyramidStack.vue +342 -0
  10. package/components/pyramid/circular-pyramid/CircularPyramidStackContent.vue +305 -0
  11. package/components/pyramid/circular-pyramid/CircularPyramidStackTitle.vue +208 -0
  12. package/components/pyramid/square-pyramid/SquarePyramid.vue +863 -0
  13. package/components/pyramid/square-pyramid/SquarePyramidStack.vue +333 -0
  14. package/components/pyramid/square-pyramid/SquarePyramidStackContent.vue +301 -0
  15. package/components/pyramid/square-pyramid/SquarePyramidStackTitle.vue +204 -0
  16. package/package.json +2 -1
  17. package/utils/defenseInDepth/arcDid/context.ts +110 -0
  18. package/utils/defenseInDepth/arcDid/createArcDiDGeometry.ts +570 -0
  19. package/utils/defenseInDepth/arcDid/index.ts +3 -0
  20. package/utils/defenseInDepth/arcDid/types.ts +140 -0
  21. package/utils/defenseInDepth/index.ts +2 -0
  22. package/utils/defenseInDepth/shared/colors.ts +121 -0
  23. package/utils/defenseInDepth/shared/constants.ts +14 -0
  24. package/utils/defenseInDepth/shared/index.ts +3 -0
  25. package/utils/defenseInDepth/shared/types.ts +12 -0
  26. package/utils/pyramid/circular-pyramid/context.ts +46 -0
  27. package/utils/pyramid/circular-pyramid/createCircularPyramidGeometry.ts +143 -0
  28. package/utils/pyramid/circular-pyramid/index.ts +3 -0
  29. package/utils/pyramid/circular-pyramid/types.ts +34 -0
  30. package/utils/pyramid/index.ts +3 -0
  31. package/utils/pyramid/shared/colors.ts +92 -0
  32. package/utils/pyramid/shared/connector.ts +111 -0
  33. package/utils/pyramid/shared/constants.ts +9 -0
  34. package/utils/pyramid/shared/index.ts +5 -0
  35. package/utils/pyramid/shared/stacking.ts +76 -0
  36. package/utils/pyramid/shared/types.ts +36 -0
  37. package/utils/pyramid/square-pyramid/context.ts +46 -0
  38. package/utils/pyramid/square-pyramid/createSquarePyramidGeometry.ts +265 -0
  39. package/utils/pyramid/square-pyramid/index.ts +3 -0
  40. package/utils/pyramid/square-pyramid/types.ts +39 -0
  41. package/utils/useDiagramAutoScale.ts +64 -20
@@ -0,0 +1,342 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ computed,
4
+ Fragment,
5
+ inject,
6
+ onUnmounted,
7
+ provide,
8
+ useSlots,
9
+ type VNode,
10
+ } from 'vue';
11
+
12
+ import {
13
+ CIRCULAR_PYRAMID_ROOT_KEY,
14
+ CIRCULAR_PYRAMID_STACK_KEY,
15
+ DEFAULT_CIRCULAR_PYRAMID_COLOR,
16
+ DEFAULT_CIRCULAR_PYRAMID_STACK_COLORS,
17
+ } from '../../../utils/pyramid/circular-pyramid';
18
+ import { PYRAMID_DOT_GAP_PX } from '../../../utils/pyramid/shared';
19
+ import CircularPyramidStackContent from './CircularPyramidStackContent.vue';
20
+ import CircularPyramidStackTitle from './CircularPyramidStackTitle.vue';
21
+
22
+ defineOptions({
23
+ name: 'CircularPyramidStack',
24
+ });
25
+
26
+ export interface ICircularPyramidStackProps {
27
+ readonly index?: number;
28
+ readonly step?: string | number;
29
+ readonly title?: string;
30
+ readonly content?: string;
31
+ readonly color?: string;
32
+ readonly active?: boolean;
33
+ readonly connectorWidth?: number;
34
+ }
35
+
36
+ const props = withDefaults(defineProps<ICircularPyramidStackProps>(), {
37
+ index: undefined,
38
+ step: undefined,
39
+ title: undefined,
40
+ content: undefined,
41
+ color: undefined,
42
+ active: undefined,
43
+ connectorWidth: undefined,
44
+ });
45
+
46
+ const slots = useSlots();
47
+ const rootContext = inject(CIRCULAR_PYRAMID_ROOT_KEY, undefined);
48
+
49
+ interface INamedComponent {
50
+ readonly type?: {
51
+ readonly name?: string;
52
+ readonly displayName?: string;
53
+ };
54
+ }
55
+
56
+ function hasComponentName(type: unknown, targetName: string): boolean {
57
+ if (typeof type === 'object' && type !== null) {
58
+ const comp = type as INamedComponent['type'];
59
+ return comp?.name === targetName || comp?.displayName === targetName;
60
+ }
61
+ return false;
62
+ }
63
+
64
+ function flattenVNodes(nodes: VNode[]): VNode[] {
65
+ const result: VNode[] = [];
66
+ for (const node of nodes) {
67
+ if (node.type === Fragment && Array.isArray(node.children)) {
68
+ result.push(...flattenVNodes(node.children as VNode[]));
69
+ } else {
70
+ result.push(node);
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+
76
+ const defaultNodes = computed(() => {
77
+ if (!slots.default) {
78
+ return [];
79
+ }
80
+ return flattenVNodes(slots.default());
81
+ });
82
+
83
+ const slottedTitle = computed(() => {
84
+ return defaultNodes.value.find((node) =>
85
+ hasComponentName(node.type, 'CircularPyramidStackTitle'),
86
+ );
87
+ });
88
+
89
+ const slottedContent = computed(() => {
90
+ return defaultNodes.value.find((node) =>
91
+ hasComponentName(node.type, 'CircularPyramidStackContent'),
92
+ );
93
+ });
94
+
95
+ const stackToken = Symbol();
96
+ const effectiveColor = computed(() => {
97
+ return (
98
+ props.color || (slottedTitle.value?.props?.color as string | undefined)
99
+ );
100
+ });
101
+ const registration = rootContext?.registerStack(stackToken, effectiveColor);
102
+
103
+ onUnmounted(() => {
104
+ registration?.unregister();
105
+ });
106
+
107
+ const resolvedIndex = computed<number>(() => {
108
+ if (props.index !== undefined) {
109
+ return Number(props.index);
110
+ }
111
+ return registration?.index.value ?? 0;
112
+ });
113
+
114
+ const isActive = computed<boolean>(() => {
115
+ if (props.active !== undefined) {
116
+ return props.active;
117
+ }
118
+ if (!rootContext) {
119
+ return false;
120
+ }
121
+ return rootContext.activeIndex.value === resolvedIndex.value;
122
+ });
123
+
124
+ const resolvedStep = computed(() => {
125
+ if (props.step !== undefined) {
126
+ return props.step;
127
+ }
128
+ return String(resolvedIndex.value + 1).padStart(2, '0');
129
+ });
130
+
131
+ const layerGeometry = computed(() => {
132
+ if (!rootContext) {
133
+ return undefined;
134
+ }
135
+ return rootContext.geo.value.layers[resolvedIndex.value];
136
+ });
137
+
138
+ const posY = computed(() => {
139
+ if (!layerGeometry.value) {
140
+ return 0;
141
+ }
142
+ const geo = layerGeometry.value;
143
+ return (
144
+ geo.slabCenterY ??
145
+ Number((geo.cy + (geo.cylinderHeight ?? 0) / 2).toFixed(2))
146
+ );
147
+ });
148
+
149
+ const discRadiusX = computed(() => {
150
+ if (!layerGeometry.value) {
151
+ return 140;
152
+ }
153
+ return layerGeometry.value.rx;
154
+ });
155
+
156
+ const DOT_POSITION_X = 210;
157
+
158
+ const leftWidth = computed(() => {
159
+ if (layerGeometry.value) {
160
+ const rx = layerGeometry.value.rx;
161
+ const cx = 465;
162
+ return Math.max(180, Math.round(cx - rx - PYRAMID_DOT_GAP_PX));
163
+ }
164
+ return DOT_POSITION_X;
165
+ });
166
+
167
+ const rightWidth = computed(() => rootContext?.contentWidthPx.value ?? 240);
168
+
169
+ const rightWingWidth = computed(() => {
170
+ if (layerGeometry.value) {
171
+ const rx = layerGeometry.value.rx;
172
+ const cx = 465;
173
+ const dotRightX = cx + rx + PYRAMID_DOT_GAP_PX;
174
+ const containerW = 960;
175
+ return Math.max(rightWidth.value + 20, Math.round(containerW - dotRightX));
176
+ }
177
+ return rightWidth.value + PYRAMID_DOT_GAP_PX;
178
+ });
179
+
180
+ const rightOffset = computed(
181
+ () => rootContext?.contentRightOffsetPx.value ?? 0,
182
+ );
183
+
184
+ const resolvedColor = computed<string>(() => {
185
+ if (effectiveColor.value) {
186
+ return effectiveColor.value;
187
+ }
188
+ return (
189
+ rootContext?.getLayerColor(resolvedIndex.value) ??
190
+ DEFAULT_CIRCULAR_PYRAMID_STACK_COLORS[
191
+ Math.abs(resolvedIndex.value) %
192
+ DEFAULT_CIRCULAR_PYRAMID_STACK_COLORS.length
193
+ ] ??
194
+ DEFAULT_CIRCULAR_PYRAMID_COLOR
195
+ );
196
+ });
197
+
198
+ const hasActiveStack = computed(() => {
199
+ return (rootContext?.activeIndex.value ?? -1) >= 0;
200
+ });
201
+
202
+ function handleStackClick(): void {
203
+ if (rootContext?.interactive.value) {
204
+ if (rootContext.activeIndex.value === resolvedIndex.value) {
205
+ rootContext.setActiveIndex(-1);
206
+ } else {
207
+ rootContext.setActiveIndex(resolvedIndex.value);
208
+ }
209
+ }
210
+ }
211
+
212
+ const deltaY = computed(() => {
213
+ return rootContext?.getCardDeltaY?.(resolvedIndex.value) ?? 0;
214
+ });
215
+
216
+ const connectorWidth = computed(() => {
217
+ return Math.max(20, rightWingWidth.value - rightWidth.value);
218
+ });
219
+
220
+ const stepIndex = computed(() => {
221
+ return resolvedIndex.value;
222
+ });
223
+
224
+ provide(CIRCULAR_PYRAMID_STACK_KEY, {
225
+ index: resolvedIndex,
226
+ isActive,
227
+ step: resolvedStep,
228
+ layerGeo: layerGeometry,
229
+ color: resolvedColor,
230
+ deltaY,
231
+ connectorWidth,
232
+ setActive: handleStackClick,
233
+ });
234
+ </script>
235
+
236
+ <template>
237
+ <div
238
+ class="alpha-circular-pyramid-stack absolute inset-x-0 pointer-events-none select-none flex items-center justify-between"
239
+ :class="[
240
+ isActive
241
+ ? 'alpha-circular-pyramid-stack--active'
242
+ : 'alpha-circular-pyramid-stack--inactive',
243
+ `alpha-circular-pyramid-stack--${resolvedIndex}`,
244
+ ]"
245
+ :data-index="resolvedIndex"
246
+ :data-active="isActive"
247
+ :style="{
248
+ top: `${posY}px`,
249
+ transform: 'translateY(-50%)',
250
+ }"
251
+ >
252
+ <!-- Left Wing: Title Pill (150px) -->
253
+ <div
254
+ class="alpha-circular-pyramid-stack__left flex-shrink-0 flex items-center justify-start pointer-events-auto cursor-pointer"
255
+ :class="[
256
+ `alpha-circular-pyramid-stack__left--${resolvedIndex}`,
257
+ { 'alpha-circular-pyramid-stack--animated': rootContext?.animation.value },
258
+ ]"
259
+ :style="{
260
+ '--wing-delay': `calc(var(--circular-pyramid-start-delay, 0ms) + ${460 + stepIndex * 320}ms)`,
261
+ width: `${leftWidth}px`,
262
+ minWidth: `${leftWidth}px`,
263
+ maxWidth: `${leftWidth}px`,
264
+ opacity: !hasActiveStack || isActive ? 1 : 0.65,
265
+ }"
266
+ @click="handleStackClick"
267
+ >
268
+ <component :is="slottedTitle" v-if="slottedTitle" />
269
+ <CircularPyramidStackTitle
270
+ v-else
271
+ :step="resolvedStep"
272
+ :title="props.title || `Stage ${resolvedStep}`"
273
+ :connector-width="props.connectorWidth"
274
+ />
275
+ </div>
276
+ <!-- Right Wing: Dot + Connector + Content Card (10px from slab) -->
277
+ <div
278
+ class="alpha-circular-pyramid-stack__right flex-shrink-0 flex items-center justify-end pointer-events-auto cursor-pointer"
279
+ :class="[
280
+ `alpha-circular-pyramid-stack__right--${resolvedIndex}`,
281
+ { 'alpha-circular-pyramid-stack--animated': rootContext?.animation.value },
282
+ ]"
283
+ :style="{
284
+ '--wing-delay': `calc(var(--circular-pyramid-start-delay, 0ms) + ${470 + stepIndex * 320}ms)`,
285
+ width: `${rightWingWidth}px`,
286
+ minWidth: `${rightWidth}px`,
287
+ maxWidth: `${rightWingWidth}px`,
288
+ marginRight: rightOffset ? `${rightOffset}px` : undefined,
289
+ opacity: !hasActiveStack || isActive ? 1 : 0.65,
290
+ }"
291
+ @click="handleStackClick"
292
+ >
293
+ <component :is="slottedContent" v-if="slottedContent" />
294
+ <CircularPyramidStackContent
295
+ v-else
296
+ :content="props.content || 'Layer details and operational description.'"
297
+ />
298
+ </div>
299
+ </div>
300
+ </template>
301
+
302
+ <style scoped>
303
+ .alpha-circular-pyramid-stack {
304
+ height: 0;
305
+ overflow: visible;
306
+ }
307
+
308
+ .alpha-circular-pyramid-stack__left,
309
+ .alpha-circular-pyramid-stack__right {
310
+ transition: opacity 0.3s ease, transform 0.3s ease;
311
+ }
312
+
313
+ .alpha-circular-pyramid-stack--animated .alpha-circular-pyramid-stack__left {
314
+ animation: alpha-pyramid-left-in 140ms cubic-bezier(0.16, 1, 0.3, 1) var(--wing-delay, calc(var(--circular-pyramid-start-delay, 0ms) + 460ms)) both;
315
+ }
316
+
317
+ @keyframes alpha-pyramid-left-in {
318
+ 0% {
319
+ opacity: 0;
320
+ transform: translateX(-10px);
321
+ }
322
+ 100% {
323
+ opacity: 1;
324
+ transform: translateX(0);
325
+ }
326
+ }
327
+
328
+ .alpha-circular-pyramid-stack--animated .alpha-circular-pyramid-stack__right {
329
+ animation: alpha-pyramid-right-in 140ms cubic-bezier(0.16, 1, 0.3, 1) var(--wing-delay, calc(var(--circular-pyramid-start-delay, 0ms) + 470ms)) both;
330
+ }
331
+
332
+ @keyframes alpha-pyramid-right-in {
333
+ 0% {
334
+ opacity: 0;
335
+ transform: translateX(10px);
336
+ }
337
+ 100% {
338
+ opacity: 1;
339
+ transform: translateX(0);
340
+ }
341
+ }
342
+ </style>
@@ -0,0 +1,305 @@
1
+ <script setup lang="ts">
2
+ import { computed, inject, onMounted, onUnmounted, ref } from 'vue';
3
+
4
+ import {
5
+ CIRCULAR_PYRAMID_ROOT_KEY,
6
+ CIRCULAR_PYRAMID_STACK_KEY,
7
+ } from '../../../utils/pyramid/circular-pyramid';
8
+ import { createPyramidConnectorPath } from '../../../utils/pyramid/shared';
9
+
10
+ defineOptions({
11
+ name: 'CircularPyramidStackContent',
12
+ });
13
+
14
+ export interface ICircularPyramidStackContentProps {
15
+ readonly content?: string;
16
+ readonly color?: string;
17
+ readonly connectorWidth?: number;
18
+ }
19
+
20
+ const props = withDefaults(defineProps<ICircularPyramidStackContentProps>(), {
21
+ content: undefined,
22
+ color: undefined,
23
+ connectorWidth: undefined,
24
+ });
25
+
26
+ const rootContext = inject(CIRCULAR_PYRAMID_ROOT_KEY, undefined);
27
+ const stackContext = inject(CIRCULAR_PYRAMID_STACK_KEY, undefined);
28
+
29
+ const connectorRef = ref<HTMLElement | null>(null);
30
+ const measuredConnectorWidth = ref<number>(0);
31
+
32
+ let resizeObserver: ResizeObserver | null = null;
33
+
34
+ function updateWidth(): void {
35
+ if (connectorRef.value && connectorRef.value.clientWidth > 0) {
36
+ measuredConnectorWidth.value = connectorRef.value.clientWidth;
37
+ }
38
+ }
39
+
40
+ onMounted(() => {
41
+ updateWidth();
42
+ if (typeof ResizeObserver !== 'undefined' && connectorRef.value) {
43
+ resizeObserver = new ResizeObserver(updateWidth);
44
+ resizeObserver.observe(connectorRef.value);
45
+ }
46
+ });
47
+
48
+ onUnmounted(() => {
49
+ resizeObserver?.disconnect();
50
+ });
51
+
52
+ const isActive = computed(() => {
53
+ return stackContext?.isActive.value ?? false;
54
+ });
55
+
56
+ const isAnimated = computed(() => {
57
+ return rootContext?.animation.value ?? false;
58
+ });
59
+
60
+ const stepIndex = computed(() => {
61
+ return stackContext?.index.value ?? 0;
62
+ });
63
+
64
+ const palette = computed(() => {
65
+ return rootContext?.palette.value;
66
+ });
67
+
68
+ const contentWidth = computed(() => rootContext?.contentWidthPx.value ?? 240);
69
+
70
+ const deltaY = computed(() => {
71
+ if (stackContext?.deltaY?.value !== undefined) {
72
+ return stackContext.deltaY.value;
73
+ }
74
+ const idx = stackContext?.index.value ?? 0;
75
+ return rootContext?.getCardDeltaY?.(idx) ?? 0;
76
+ });
77
+
78
+ const effectiveDeltaY = computed(() => deltaY.value ?? 0);
79
+
80
+ const connectorWidth = computed(() => {
81
+ if (props.connectorWidth !== undefined) {
82
+ return props.connectorWidth;
83
+ }
84
+ if (stackContext?.connectorWidth?.value !== undefined) {
85
+ return stackContext.connectorWidth.value;
86
+ }
87
+ return measuredConnectorWidth.value || 40;
88
+ });
89
+
90
+ const svgMargin = 4;
91
+ const svgHeight = computed(() => {
92
+ return Math.max(8, Math.abs(effectiveDeltaY.value) + svgMargin * 2);
93
+ });
94
+
95
+ const svgMinY = computed(() => {
96
+ return Math.min(0, effectiveDeltaY.value) - svgMargin;
97
+ });
98
+
99
+ const svgTop = computed(() => {
100
+ return Math.min(0, effectiveDeltaY.value) - svgMargin;
101
+ });
102
+
103
+ const connectorPath = computed(() => {
104
+ const w = connectorWidth.value;
105
+ return createPyramidConnectorPath(
106
+ { x: 0, y: 0 },
107
+ { x: w, y: effectiveDeltaY.value },
108
+ );
109
+ });
110
+
111
+ const stackColor = computed(() => {
112
+ return (
113
+ props.color ??
114
+ stackContext?.color?.value ??
115
+ rootContext?.color.value ??
116
+ '#3b82f6'
117
+ );
118
+ });
119
+ </script>
120
+
121
+ <template>
122
+ <div
123
+ class="alpha-circular-pyramid-stack-content-wrapper relative w-full flex items-center select-none overflow-visible"
124
+ :style="{
125
+ '--content-delay': `calc(var(--circular-pyramid-start-delay, 0ms) + ${470 + stepIndex * 320}ms)`,
126
+ }"
127
+ >
128
+ <!-- Scalable Line between Dot and Content Card (always at least 20px) -->
129
+ <div
130
+ ref="connectorRef"
131
+ class="alpha-circular-pyramid-stack-content__connector flex-1 flex items-center min-w-[20px] pointer-events-none relative overflow-visible h-0"
132
+ >
133
+ <div
134
+ class="alpha-circular-pyramid-stack-content__dot absolute left-0 top-0 -translate-x-1/2 -translate-y-1/2 w-1.5 h-1.5 rounded-full flex-shrink-0 transition-all duration-300 pointer-events-auto"
135
+ :class="{
136
+ 'alpha-circular-pyramid-stack-content__dot--animated': isAnimated,
137
+ }"
138
+ :style="{
139
+ backgroundColor: stackColor,
140
+ boxShadow: isActive
141
+ ? `0 0 6px ${stackColor}`
142
+ : `0 0 2px ${stackColor}80`,
143
+ }"
144
+ />
145
+ <!-- Straight horizontal line when deltaY is 0 -->
146
+ <div
147
+ v-if="!effectiveDeltaY"
148
+ class="alpha-circular-pyramid-stack-content__line absolute left-0 top-0 w-full h-[1px] -translate-y-1/2 pointer-events-none transition-colors duration-300"
149
+ :class="{
150
+ 'alpha-circular-pyramid-stack-content__line--animated': isAnimated,
151
+ }"
152
+ :style="{
153
+ backgroundColor: stackColor,
154
+ opacity: isActive ? 1 : 0.65,
155
+ }"
156
+ />
157
+ <!-- Zigzag SVG connector when deltaY !== 0 -->
158
+ <svg
159
+ v-else
160
+ class="alpha-circular-pyramid-stack-content__line-svg absolute left-0 pointer-events-none overflow-visible"
161
+ :width="connectorWidth"
162
+ :height="svgHeight"
163
+ :viewBox="`0 ${svgMinY} ${connectorWidth} ${svgHeight}`"
164
+ :style="{
165
+ top: `${svgTop}px`,
166
+ width: `${connectorWidth}px`,
167
+ height: `${svgHeight}px`,
168
+ }"
169
+ >
170
+ <path
171
+ class="alpha-circular-pyramid-stack-content__line transition-all duration-300"
172
+ :class="{
173
+ 'alpha-circular-pyramid-stack-content__line--svg-animated': isAnimated,
174
+ }"
175
+ :d="connectorPath"
176
+ fill="none"
177
+ :stroke="stackColor"
178
+ stroke-width="1.25"
179
+ stroke-linecap="round"
180
+ stroke-linejoin="round"
181
+ pathLength="1"
182
+ :style="{
183
+ opacity: isActive ? 1 : 0.65,
184
+ }"
185
+ />
186
+ </svg>
187
+ </div>
188
+ <!-- Content Card -->
189
+ <div
190
+ class="alpha-circular-pyramid-stack-content rounded-xl p-2 px-2.5 transition-all duration-300 border select-none box-border flex-shrink-0"
191
+ :class="[
192
+ isActive
193
+ ? 'alpha-circular-pyramid-stack-content--active'
194
+ : 'alpha-circular-pyramid-stack-content--default',
195
+ {
196
+ 'alpha-circular-pyramid-stack-content--animated': isAnimated,
197
+ },
198
+ ]"
199
+ :style="{
200
+ width: `${contentWidth}px`,
201
+ minWidth: `${contentWidth}px`,
202
+ maxWidth: `${contentWidth}px`,
203
+ boxSizing: 'border-box',
204
+ borderColor: stackColor,
205
+ backgroundColor: isActive
206
+ ? 'rgba(255, 255, 255, 0.95)'
207
+ : 'rgba(255, 255, 255, 0.85)',
208
+ boxShadow: isActive
209
+ ? `0 4px 14px ${stackColor}26`
210
+ : 'none',
211
+ '--card-transform': deltaY ? `translateY(${deltaY}px)` : 'none',
212
+ transform: deltaY ? `translateY(${deltaY}px)` : undefined,
213
+ }"
214
+ >
215
+ <div
216
+ class="text-[10px] leading-snug transition-colors duration-300 break-words line-clamp-3"
217
+ :style="{
218
+ color: isActive ? '#0f172a' : '#475569',
219
+ }"
220
+ >
221
+ <slot>{{ props.content }}</slot>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ </template>
226
+
227
+ <style scoped>
228
+ .alpha-circular-pyramid-stack-content {
229
+ box-sizing: border-box;
230
+ }
231
+
232
+ .alpha-circular-pyramid-stack-content--active {
233
+ backdrop-filter: blur(8px);
234
+ }
235
+
236
+ .alpha-circular-pyramid-stack-content__dot--animated {
237
+ animation: alpha-pyramid-dot-pop 120ms cubic-bezier(0.16, 1, 0.3, 1) var(--content-delay) both;
238
+ }
239
+
240
+ @keyframes alpha-pyramid-dot-pop {
241
+ 0% {
242
+ opacity: 0;
243
+ transform: translate(-50%, -50%) scale(0);
244
+ }
245
+ 100% {
246
+ opacity: 1;
247
+ transform: translate(-50%, -50%) scale(1);
248
+ }
249
+ }
250
+
251
+ .alpha-circular-pyramid-stack-content__line--animated {
252
+ transform-origin: left center;
253
+ animation: alpha-pyramid-line-draw 500ms cubic-bezier(0.4, 0, 0.2, 1) calc(var(--content-delay) + 120ms) both;
254
+ }
255
+
256
+ @keyframes alpha-pyramid-line-draw {
257
+ 0% {
258
+ opacity: 0;
259
+ transform: translateY(-50%) scaleX(0);
260
+ }
261
+ 2% {
262
+ opacity: 1;
263
+ transform: translateY(-50%) scaleX(0);
264
+ }
265
+ 100% {
266
+ opacity: 1;
267
+ transform: translateY(-50%) scaleX(1);
268
+ }
269
+ }
270
+
271
+ .alpha-circular-pyramid-stack-content__line--svg-animated {
272
+ stroke-dasharray: 1;
273
+ animation: alpha-pyramid-svg-line-draw 500ms cubic-bezier(0.4, 0, 0.2, 1) calc(var(--content-delay) + 120ms) both;
274
+ }
275
+
276
+ @keyframes alpha-pyramid-svg-line-draw {
277
+ 0% {
278
+ opacity: 0;
279
+ stroke-dashoffset: 1;
280
+ }
281
+ 2% {
282
+ opacity: 1;
283
+ stroke-dashoffset: 1;
284
+ }
285
+ 100% {
286
+ opacity: 1;
287
+ stroke-dashoffset: 0;
288
+ }
289
+ }
290
+
291
+ .alpha-circular-pyramid-stack-content--animated {
292
+ animation: alpha-pyramid-card-in 240ms cubic-bezier(0.16, 1, 0.3, 1) calc(var(--content-delay) + 380ms) both;
293
+ }
294
+
295
+ @keyframes alpha-pyramid-card-in {
296
+ 0% {
297
+ opacity: 0;
298
+ transform: var(--card-transform, none) translateX(10px);
299
+ }
300
+ 100% {
301
+ opacity: 1;
302
+ transform: var(--card-transform, none) translateX(0);
303
+ }
304
+ }
305
+ </style>