@alphacifer/slidev-addon-theme 0.0.4 → 0.0.6

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 (84) hide show
  1. package/README.md +127 -2
  2. package/components/arc-arrow-process/ArcArrowProcess.vue +361 -0
  3. package/components/arc-arrow-process/ArcArrowProcessArrow.vue +143 -0
  4. package/components/arc-arrow-process/ArcArrowProcessCallout.vue +361 -0
  5. package/components/arc-arrow-process/ArcArrowProcessContent.vue +38 -0
  6. package/components/arc-arrow-process/ArcArrowProcessHeading.vue +43 -0
  7. package/components/arc-arrow-process/ArcArrowProcessIcon.vue +154 -0
  8. package/components/arc-compare/ArcCompare.vue +443 -0
  9. package/components/arc-compare/ArcCompareBadge.vue +63 -0
  10. package/components/arc-compare/ArcCompareCallout.vue +386 -0
  11. package/components/arc-compare/ArcCompareContent.vue +52 -0
  12. package/components/arc-compare/ArcCompareContents.vue +18 -0
  13. package/components/arc-compare/ArcCompareHeading.vue +52 -0
  14. package/components/arc-compare/ArcCompareLeft.vue +183 -0
  15. package/components/arc-compare/ArcCompareRight.vue +183 -0
  16. package/components/arc-compare/ArcCompareTitle.vue +47 -0
  17. package/components/arrow-triad/ArrowTriad.vue +368 -0
  18. package/components/arrow-triad/ArrowTriadCallout.vue +300 -0
  19. package/components/arrow-triad/ArrowTriadContent.vue +38 -0
  20. package/components/arrow-triad/ArrowTriadHeading.vue +38 -0
  21. package/components/arrow-triad/ArrowTriadIcon.vue +96 -0
  22. package/components/chevron-card/ChevronCard.vue +237 -0
  23. package/components/chevron-card/ChevronCardBadge.vue +74 -0
  24. package/components/chevron-card/ChevronCardContent.vue +39 -0
  25. package/components/chevron-card/ChevronCardHeading.vue +39 -0
  26. package/components/hex-triad/HexTriad.vue +438 -0
  27. package/components/hex-triad/HexTriadBadge.vue +110 -0
  28. package/components/hex-triad/HexTriadCallout.vue +200 -0
  29. package/components/hex-triad/HexTriadCenterBadge.vue +51 -0
  30. package/components/hex-triad/HexTriadContent.vue +20 -0
  31. package/components/hex-triad/HexTriadHeading.vue +28 -0
  32. package/components/hex-triad/HexTriadLayer.vue +28 -0
  33. package/components/horiz-card/HorizCard.vue +318 -0
  34. package/components/horiz-card/HorizCardBadge.vue +75 -0
  35. package/components/horiz-card/HorizCardContent.vue +46 -0
  36. package/components/horiz-card/HorizCardHeading.vue +47 -0
  37. package/components/horiz-card/HorizCardIcon.vue +118 -0
  38. package/components/quad-hub/QuadHub.vue +642 -0
  39. package/components/quad-hub/QuadHubCallout.vue +267 -0
  40. package/components/quad-hub/QuadHubCenter.vue +53 -0
  41. package/components/quad-hub/QuadHubContent.vue +39 -0
  42. package/components/quad-hub/QuadHubHeading.vue +44 -0
  43. package/components/quad-hub/QuadHubIcon.vue +112 -0
  44. package/components/shifting-heading/TransitionHeading.vue +3 -3
  45. package/components/vert-card/VertCard.vue +389 -0
  46. package/components/vert-card/VertCardBadge.vue +72 -0
  47. package/components/vert-card/VertCardContent.vue +45 -0
  48. package/components/vert-card/VertCardHeading.vue +68 -0
  49. package/components/vert-card/VertCardIcon.vue +126 -0
  50. package/components/vert-card/VertCardTitle.vue +76 -0
  51. package/layouts/core/arc-toc.vue +76 -113
  52. package/layouts/core/bg-center.vue +32 -4
  53. package/layouts/shifting-heading/shifting-intro.vue +35 -7
  54. package/package.json +3 -2
  55. package/utils/arcArrowProcess/context.ts +28 -0
  56. package/utils/arcArrowProcess/createArcArrowProcessGeometry.ts +137 -0
  57. package/utils/arcArrowProcess/index.ts +3 -0
  58. package/utils/arcArrowProcess/resolveArcArrowProcessItems.ts +189 -0
  59. package/utils/arcCompare/context.ts +36 -0
  60. package/utils/arcCompare/createArcCompareGeometry.ts +254 -0
  61. package/utils/arcCompare/index.ts +3 -0
  62. package/utils/arcCompare/resolveArcCompareItems.ts +123 -0
  63. package/utils/arrowTriad/context.ts +38 -0
  64. package/utils/arrowTriad/createArrowTriadGeometry.ts +63 -0
  65. package/utils/arrowTriad/index.ts +3 -0
  66. package/utils/arrowTriad/resolveArrowTriadItems.ts +121 -0
  67. package/utils/chevronCard/context.ts +12 -0
  68. package/utils/chevronCard/createChevronCardGeometry.ts +48 -0
  69. package/utils/chevronCard/index.ts +2 -0
  70. package/utils/hexTriad/context.ts +25 -0
  71. package/utils/hexTriad/createHexTriadGeometry.ts +182 -0
  72. package/utils/hexTriad/index.ts +3 -0
  73. package/utils/hexTriad/resolveHexTriadItems.ts +63 -0
  74. package/utils/horizCard/context.ts +13 -0
  75. package/utils/horizCard/createHorizCardGeometry.ts +243 -0
  76. package/utils/horizCard/index.ts +2 -0
  77. package/utils/quadHub/context.ts +26 -0
  78. package/utils/quadHub/createQuadHubGeometry.ts +331 -0
  79. package/utils/quadHub/index.ts +3 -0
  80. package/utils/quadHub/resolveQuadHubItems.ts +214 -0
  81. package/utils/useDiagramAutoScale.ts +300 -0
  82. package/utils/vertCard/context.ts +18 -0
  83. package/utils/vertCard/createVertCardGeometry.ts +242 -0
  84. package/utils/vertCard/index.ts +2 -0
@@ -0,0 +1,642 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ type ComputedRef,
4
+ computed,
5
+ Fragment,
6
+ inject,
7
+ onBeforeUpdate,
8
+ onMounted,
9
+ onUnmounted,
10
+ provide,
11
+ ref,
12
+ shallowReactive,
13
+ useId,
14
+ useSlots,
15
+ type VNode,
16
+ } from 'vue';
17
+
18
+ import {
19
+ calculateQuadHubCoordinates,
20
+ DEFAULT_QUAD_HUB_ITEMS,
21
+ type IQuadBox,
22
+ type IQuadHubItem,
23
+ type IQuadHubRegistration,
24
+ type IQuadPoint,
25
+ type IResolvedQuadHubItem,
26
+ QUAD_HUB_ROOT_KEY,
27
+ resolveQuadHubItems,
28
+ } from '../../utils/quadHub';
29
+ import { useMergedUnoAttrs } from '../../utils/useMergedUnoAttrs';
30
+ import QuadHubCallout from './QuadHubCallout.vue';
31
+ import QuadHubCenter from './QuadHubCenter.vue';
32
+ import QuadHubIcon from './QuadHubIcon.vue';
33
+
34
+ defineOptions({
35
+ name: 'QuadHub',
36
+ inheritAttrs: false,
37
+ });
38
+
39
+ export interface IQuadBlockData {
40
+ readonly box: IQuadBox;
41
+ readonly arcPath: string;
42
+ readonly iconPos: IQuadPoint;
43
+ readonly numberPos: IQuadPoint;
44
+ readonly item: IResolvedQuadHubItem;
45
+ }
46
+
47
+ export interface IQuadHubProps {
48
+ readonly title?: string;
49
+ readonly items?: readonly IQuadHubItem[];
50
+ readonly active?: boolean;
51
+ readonly height?: number | string;
52
+ readonly arcRatio?: number;
53
+ readonly subtitle?: string;
54
+ readonly animation?: boolean;
55
+ readonly startDelay?: number;
56
+ readonly arcBorderRatio?: number;
57
+ readonly scale?: number;
58
+ readonly autoScale?: boolean;
59
+ readonly maxScale?: number;
60
+ }
61
+
62
+ const props = withDefaults(defineProps<IQuadHubProps>(), {
63
+ title: undefined,
64
+ items: undefined,
65
+ active: undefined,
66
+ height: undefined,
67
+ arcRatio: 0.55,
68
+ subtitle: undefined,
69
+ animation: true,
70
+ startDelay: undefined,
71
+ arcBorderRatio: 0.8,
72
+ scale: undefined,
73
+ autoScale: true,
74
+ maxScale: undefined,
75
+ });
76
+
77
+ const resolvedHeightStyle = computed(() => {
78
+ if (props.height === undefined) {
79
+ return undefined;
80
+ }
81
+
82
+ return typeof props.height === 'number' ? `${props.height}px` : props.height;
83
+ });
84
+
85
+ const uid = useId();
86
+ const slots = useSlots();
87
+
88
+ interface INamedComponent {
89
+ readonly name?: string;
90
+ readonly __name?: string;
91
+ }
92
+
93
+ function hasComponentName(type: unknown, name: string): boolean {
94
+ if (typeof type === 'object' && type !== null) {
95
+ const comp = type as INamedComponent;
96
+
97
+ return comp.name === name || comp.__name === name;
98
+ }
99
+
100
+ return false;
101
+ }
102
+
103
+ function flattenVNodes(nodes: readonly VNode[]): readonly VNode[] {
104
+ const result: VNode[] = [];
105
+
106
+ for (const node of nodes) {
107
+ if (node.type === Fragment && Array.isArray(node.children)) {
108
+ result.push(...flattenVNodes(node.children as readonly VNode[]));
109
+ } else {
110
+ result.push(node);
111
+ }
112
+ }
113
+
114
+ return result;
115
+ }
116
+
117
+ const shiftingIntro = inject<
118
+ | {
119
+ active: ComputedRef<boolean>;
120
+ isShifting?: ComputedRef<boolean>;
121
+ }
122
+ | undefined
123
+ >('SLIDEV_LAYOUT_SHIFTING_INTRO', undefined);
124
+
125
+ const isAnimated = computed((): boolean => {
126
+ if (!props.animation) {
127
+ return false;
128
+ }
129
+
130
+ if (props.active !== undefined) {
131
+ return props.active;
132
+ }
133
+
134
+ if (shiftingIntro) {
135
+ return shiftingIntro.active.value;
136
+ }
137
+
138
+ return true;
139
+ });
140
+
141
+ const resolvedStartDelay = computed((): number => {
142
+ if (props.startDelay !== undefined) {
143
+ return props.startDelay;
144
+ }
145
+
146
+ if (shiftingIntro) {
147
+ return shiftingIntro.isShifting?.value ? 600 : 0;
148
+ }
149
+
150
+ return 0;
151
+ });
152
+
153
+ const registeredCallouts = shallowReactive<Map<number, IQuadHubItem>>(
154
+ new Map(),
155
+ );
156
+
157
+ const resolvedItems = computed(
158
+ (): readonly [
159
+ IResolvedQuadHubItem,
160
+ IResolvedQuadHubItem,
161
+ IResolvedQuadHubItem,
162
+ IResolvedQuadHubItem,
163
+ ] => {
164
+ const rawDefaultNodes = slots.default ? slots.default() : [];
165
+ const flattenedNodes = flattenVNodes(rawDefaultNodes);
166
+ const calloutNodes = flattenedNodes.filter((node) => {
167
+ return (
168
+ hasComponentName(node.type, 'QuadHubCallout') ||
169
+ node.type === QuadHubCallout
170
+ );
171
+ });
172
+
173
+ const mergedItems: IQuadHubItem[] = [];
174
+
175
+ for (let i = 0; i < 4; i++) {
176
+ const slotProps =
177
+ (calloutNodes[i]?.props as IQuadHubItem | undefined) ?? {};
178
+ const regProps = registeredCallouts.get(i) ?? {};
179
+ const propItem = props.items?.[i] ?? {};
180
+
181
+ mergedItems.push({
182
+ ...propItem,
183
+ ...slotProps,
184
+ ...regProps,
185
+ });
186
+ }
187
+
188
+ return resolveQuadHubItems({
189
+ items: mergedItems,
190
+ arcRatio: props.arcRatio,
191
+ arcBorderRatio: props.arcBorderRatio,
192
+ });
193
+ },
194
+ );
195
+
196
+ const [firstFallbackItem] = DEFAULT_QUAD_HUB_ITEMS;
197
+
198
+ let calloutCounter = 0;
199
+
200
+ const registerCallout = (calloutProps?: IQuadHubItem): IQuadHubRegistration => {
201
+ const idx = calloutCounter++;
202
+
203
+ if (calloutProps) {
204
+ registeredCallouts.set(idx, calloutProps);
205
+ }
206
+
207
+ return {
208
+ index: idx,
209
+ item: computed((): IResolvedQuadHubItem => {
210
+ const current = resolvedItems.value[idx];
211
+ return current ?? resolvedItems.value[0] ?? firstFallbackItem;
212
+ }),
213
+ };
214
+ };
215
+
216
+ onBeforeUpdate(() => {
217
+ calloutCounter = 0;
218
+ });
219
+
220
+ provide(QUAD_HUB_ROOT_KEY, {
221
+ items: resolvedItems,
222
+ animation: isAnimated,
223
+ registerCallout,
224
+ });
225
+
226
+ // Precision coordinates calculation for SVG elements
227
+ const coords = calculateQuadHubCoordinates({
228
+ gap: 14,
229
+ width: 136,
230
+ height: 150,
231
+ arcRadius: 104,
232
+ hubRadius: 58,
233
+ arcInsetDeg: 11,
234
+ viewBoxSize: 360,
235
+ cornerRadius: 26,
236
+ iconPaddingX: 30,
237
+ iconPaddingY: 32,
238
+ arcBorderWidth: 5,
239
+ });
240
+
241
+ const {
242
+ centerX,
243
+ centerY,
244
+ arcPaths,
245
+ arcRadius,
246
+ hubRadius,
247
+ quadrants,
248
+ viewBoxSize,
249
+ iconPositions,
250
+ arcBorderWidth,
251
+ numberPositions,
252
+ } = coords;
253
+
254
+ const quadrantBlocks = computed(
255
+ (): readonly [
256
+ IQuadBlockData,
257
+ IQuadBlockData,
258
+ IQuadBlockData,
259
+ IQuadBlockData,
260
+ ] => {
261
+ const [i0, i1, i2, i3] = resolvedItems.value;
262
+ return [
263
+ {
264
+ box: quadrants[0],
265
+ item: i0,
266
+ arcPath: arcPaths[0],
267
+ iconPos: iconPositions[0],
268
+ numberPos: numberPositions[0],
269
+ },
270
+ {
271
+ box: quadrants[1],
272
+ item: i1,
273
+ arcPath: arcPaths[1],
274
+ iconPos: iconPositions[1],
275
+ numberPos: numberPositions[1],
276
+ },
277
+ {
278
+ box: quadrants[2],
279
+ item: i2,
280
+ arcPath: arcPaths[2],
281
+ iconPos: iconPositions[2],
282
+ numberPos: numberPositions[2],
283
+ },
284
+ {
285
+ box: quadrants[3],
286
+ item: i3,
287
+ arcPath: arcPaths[3],
288
+ iconPos: iconPositions[3],
289
+ numberPos: numberPositions[3],
290
+ },
291
+ ] as const;
292
+ },
293
+ );
294
+
295
+ const { className, forwardedAttrs } = useMergedUnoAttrs(
296
+ 'alpha-quad-hub w-full flex-1 flex flex-col items-center justify-center select-none font-sans min-h-0',
297
+ );
298
+
299
+ const rootRef = ref<HTMLElement | null>(null);
300
+ const autoScaleValue = ref<number>(1);
301
+
302
+ const BASE_WIDTH = 940;
303
+ const BASE_HEIGHT = 350;
304
+
305
+ const updateScale = () => {
306
+ if (!rootRef.value) {
307
+ return;
308
+ }
309
+
310
+ const el = rootRef.value;
311
+ const parent = el.parentElement;
312
+
313
+ if (!parent) {
314
+ return;
315
+ }
316
+
317
+ // 1. Measure the immediate parent container
318
+ const parentStyle = window.getComputedStyle(parent);
319
+ const padLeft = Number.parseFloat(parentStyle.paddingLeft) || 0;
320
+ const padRight = Number.parseFloat(parentStyle.paddingRight) || 0;
321
+ const padTop = Number.parseFloat(parentStyle.paddingTop) || 0;
322
+ const padBottom = Number.parseFloat(parentStyle.paddingBottom) || 0;
323
+
324
+ // Usable width inside parent block
325
+ let availW = 0;
326
+ if (parent.clientWidth > 0) {
327
+ availW = parent.clientWidth - padLeft - padRight;
328
+ } else if (el.clientWidth > 0) {
329
+ availW = el.clientWidth;
330
+ }
331
+
332
+ // Usable height inside parent block
333
+ let availH = 0;
334
+
335
+ if (typeof props.height === 'number') {
336
+ availH = props.height;
337
+ } else if (typeof props.height === 'string' && props.height.endsWith('px')) {
338
+ availH = Number.parseFloat(props.height);
339
+ }
340
+
341
+ if (!availH) {
342
+ // Count height of sibling elements inside parent (e.g. titles, headers)
343
+ let siblingsH = 0;
344
+ for (let i = 0; i < parent.children.length; i++) {
345
+ const child = parent.children[i];
346
+
347
+ if (child !== el && child instanceof HTMLElement) {
348
+ siblingsH += child.offsetHeight;
349
+ const cs = window.getComputedStyle(child);
350
+ siblingsH +=
351
+ (Number.parseFloat(cs.marginTop) || 0) +
352
+ (Number.parseFloat(cs.marginBottom) || 0);
353
+ }
354
+ }
355
+
356
+ // If parent has a defined height (flex-1, h-full, explicit height)
357
+ if (parent.clientHeight > 0) {
358
+ availH = Math.max(
359
+ 0,
360
+ parent.clientHeight - padTop - padBottom - siblingsH,
361
+ );
362
+ }
363
+
364
+ // If still 0 or parent has height: auto, check slide layout container
365
+ if (availH <= 0) {
366
+ const slideEl = el.closest<HTMLElement>(
367
+ '.slidev-layout, .slidev-page, [data-slidev-page]',
368
+ );
369
+
370
+ if (slideEl) {
371
+ const slideStyle = window.getComputedStyle(slideEl);
372
+ const sPadTop = Number.parseFloat(slideStyle.paddingTop) || 0;
373
+ const sPadBottom = Number.parseFloat(slideStyle.paddingBottom) || 0;
374
+ const sPadLeft = Number.parseFloat(slideStyle.paddingLeft) || 0;
375
+ const sPadRight = Number.parseFloat(slideStyle.paddingRight) || 0;
376
+
377
+ if (availW <= 0 && slideEl.clientWidth > 0) {
378
+ availW = slideEl.clientWidth - sPadLeft - sPadRight;
379
+ }
380
+
381
+ if (slideEl.clientHeight > 0) {
382
+ availH = Math.max(
383
+ 0,
384
+ slideEl.clientHeight - sPadTop - sPadBottom - siblingsH,
385
+ );
386
+ }
387
+ }
388
+ }
389
+ }
390
+
391
+ if (availW <= 0) {
392
+ availW = BASE_WIDTH;
393
+ }
394
+
395
+ let targetScale = 1;
396
+
397
+ if (availW > 0 && availH > 0) {
398
+ const scaleX = availW / BASE_WIDTH;
399
+ const scaleY = availH / BASE_HEIGHT;
400
+ targetScale = Math.min(scaleX, scaleY);
401
+ } else if (availW > 0) {
402
+ targetScale = availW / BASE_WIDTH;
403
+ }
404
+
405
+ if (props.maxScale !== undefined) {
406
+ targetScale = Math.min(targetScale, props.maxScale);
407
+ }
408
+
409
+ if (Math.abs(targetScale - autoScaleValue.value) > 0.005) {
410
+ autoScaleValue.value = targetScale;
411
+ }
412
+ };
413
+
414
+ let observer: ResizeObserver | null = null;
415
+
416
+ onMounted(() => {
417
+ if (typeof window === 'undefined' || !rootRef.value) {
418
+ return;
419
+ }
420
+
421
+ updateScale();
422
+
423
+ requestAnimationFrame(() => {
424
+ updateScale();
425
+ });
426
+
427
+ if (typeof ResizeObserver !== 'undefined') {
428
+ observer = new ResizeObserver(() => {
429
+ updateScale();
430
+ });
431
+
432
+ observer.observe(rootRef.value);
433
+
434
+ if (rootRef.value.parentElement) {
435
+ observer.observe(rootRef.value.parentElement);
436
+ }
437
+
438
+ const slideEl = rootRef.value.closest<HTMLElement>(
439
+ '.slidev-layout, .slidev-page, [data-slidev-page]',
440
+ );
441
+
442
+ if (slideEl && slideEl !== rootRef.value.parentElement) {
443
+ observer.observe(slideEl);
444
+ }
445
+ } else {
446
+ window.addEventListener('resize', updateScale);
447
+ }
448
+ });
449
+
450
+ onUnmounted(() => {
451
+ if (observer) {
452
+ observer.disconnect();
453
+ observer = null;
454
+ } else if (typeof window !== 'undefined') {
455
+ window.removeEventListener('resize', updateScale);
456
+ }
457
+ });
458
+
459
+ const resolvedScale = computed((): number => {
460
+ if (props.scale !== undefined) {
461
+ return props.scale;
462
+ }
463
+
464
+ if (props.autoScale === false) {
465
+ return 1;
466
+ }
467
+
468
+ if (props.maxScale !== undefined) {
469
+ return Math.min(autoScaleValue.value, props.maxScale);
470
+ }
471
+
472
+ return autoScaleValue.value;
473
+ });
474
+ </script>
475
+
476
+ <template>
477
+ <div
478
+ ref="rootRef"
479
+ v-bind="forwardedAttrs()"
480
+ :class="className()"
481
+ :style="{
482
+ height: resolvedHeightStyle,
483
+ fontFamily:
484
+ 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif',
485
+ '--quad-start-delay': `${resolvedStartDelay}ms`,
486
+ }"
487
+ >
488
+ <div
489
+ class="alpha-quad-hub__stage relative w-full h-full min-h-[350px] max-w-[1020px] mx-auto flex items-center justify-center overflow-visible"
490
+ :style="{
491
+ transform: props.scale !== undefined ? `scale(${props.scale})` : undefined,
492
+ transformOrigin: 'center center',
493
+ }"
494
+ >
495
+ <!-- Center Diagram: 4 Colored Quadrants + Concentric Arcs -->
496
+ <div
497
+ class="alpha-quad-hub__center absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center justify-center w-[390px] h-[390px] z-10 pointer-events-none"
498
+ >
499
+ <svg
500
+ class="alpha-quad-hub__svg w-[390px] h-[390px] overflow-visible"
501
+ :viewBox="`0 0 ${viewBoxSize} ${viewBoxSize}`"
502
+ >
503
+ <defs>
504
+ <!-- Drop shadow for the quadrant blocks (unique per component instance) -->
505
+ <filter :id="`quad-block-shadow-${uid}`" x="-20%" y="-20%" width="140%" height="140%">
506
+ <feDropShadow dx="0" dy="4" stdDeviation="6" flood-opacity="0.10" />
507
+ </filter>
508
+ <!-- Quadrant Clip Paths for Rounded Corner Squares (unique per instance) -->
509
+ <clipPath
510
+ v-for="(block, idx) in quadrantBlocks"
511
+ :id="`clip-quad-${uid}-${idx}`"
512
+ :key="idx"
513
+ >
514
+ <rect
515
+ :x="block.box.x"
516
+ :y="block.box.y"
517
+ :width="block.box.width"
518
+ :height="block.box.height"
519
+ :rx="block.box.rx"
520
+ :ry="block.box.rx"
521
+ />
522
+ </clipPath>
523
+ </defs>
524
+ <!-- Quadrant Blocks (0: TL, 1: TR, 2: BR, 3: BL) -->
525
+ <g
526
+ v-for="(block, idx) in quadrantBlocks"
527
+ :key="idx"
528
+ :class="[
529
+ 'alpha-quad-block',
530
+ `alpha-quad-block--${idx}`,
531
+ { 'alpha-quad-block--animated': isAnimated },
532
+ ]"
533
+ :filter="`url(#quad-block-shadow-${uid})`"
534
+ >
535
+ <!-- Base solid colored rounded rectangle -->
536
+ <rect
537
+ :x="block.box.x"
538
+ :y="block.box.y"
539
+ :width="block.box.width"
540
+ :height="block.box.height"
541
+ :rx="block.box.rx"
542
+ :ry="block.box.rx"
543
+ :fill="block.item.color"
544
+ />
545
+ <!-- Concentric Opaque Pastel Inner Arc Band (Clipped to Quadrant) -->
546
+ <circle
547
+ :cx="centerX"
548
+ :cy="centerY"
549
+ :r="arcRadius"
550
+ :fill="block.item.arcColor"
551
+ :clip-path="`url(#clip-quad-${uid}-${idx})`"
552
+ />
553
+ <!-- Floating Arc Separator Border (Touches pastel area without overlay) -->
554
+ <path
555
+ :d="block.arcPath"
556
+ fill="none"
557
+ :stroke="block.item.arcBorderColor"
558
+ :stroke-width="arcBorderWidth"
559
+ stroke-linecap="butt"
560
+ />
561
+ <!-- Step Number (01, 02, 03, 04) -->
562
+ <text
563
+ :x="block.numberPos.x"
564
+ :y="block.numberPos.y + 6"
565
+ text-anchor="middle"
566
+ fill="#2c3e50"
567
+ class="font-extrabold text-[18.5px] select-none font-sans"
568
+ style="font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;"
569
+ >
570
+ {{ block.item.id }}
571
+ </text>
572
+ </g>
573
+ </svg>
574
+ <!-- HTML Icon Overlay Layer (ensures UnoCSS mask icons render reliably) -->
575
+ <div class="alpha-quad-hub__icons absolute top-0 left-0 w-[360px] h-[360px] pointer-events-none">
576
+ <div
577
+ v-for="(block, idx) in quadrantBlocks"
578
+ :key="idx"
579
+ :class="[
580
+ 'alpha-quad-block',
581
+ `alpha-quad-block--${idx}`,
582
+ { 'alpha-quad-block--animated': isAnimated },
583
+ ]"
584
+ class="absolute flex items-center justify-center w-[34px] h-[34px]"
585
+ :style="{
586
+ top: `${block.iconPos.y - 17}px`,
587
+ left: `${block.iconPos.x - 17}px`,
588
+ }"
589
+ >
590
+ <QuadHubIcon
591
+ v-if="block.item.icon !== false && block.item.icon !== undefined"
592
+ :icon="block.item.icon"
593
+ :size="34"
594
+ color="#ffffff"
595
+ />
596
+ </div>
597
+ </div>
598
+ <!-- Central Circular Hub -->
599
+ <slot name="hub">
600
+ <QuadHubCenter
601
+ :title="props.title"
602
+ :subtitle="props.subtitle"
603
+ :radius="hubRadius"
604
+ />
605
+ </slot>
606
+ </div>
607
+ <!-- Callout Cards -->
608
+ <slot />
609
+ </div>
610
+ </div>
611
+ </template>
612
+
613
+ <style scoped>
614
+ .alpha-quad-block--animated.alpha-quad-block--0 {
615
+ animation: quad-block-pop 400ms cubic-bezier(0.2, 0.9, 0.4, 1) calc(var(--quad-start-delay, 0ms) + 100ms) both;
616
+ }
617
+
618
+ .alpha-quad-block--animated.alpha-quad-block--1 {
619
+ animation: quad-block-pop 400ms cubic-bezier(0.2, 0.9, 0.4, 1) calc(var(--quad-start-delay, 0ms) + 200ms) both;
620
+ }
621
+
622
+ .alpha-quad-block--animated.alpha-quad-block--2 {
623
+ animation: quad-block-pop 400ms cubic-bezier(0.2, 0.9, 0.4, 1) calc(var(--quad-start-delay, 0ms) + 300ms) both;
624
+ }
625
+
626
+ .alpha-quad-block--animated.alpha-quad-block--3 {
627
+ animation: quad-block-pop 400ms cubic-bezier(0.2, 0.9, 0.4, 1) calc(var(--quad-start-delay, 0ms) + 400ms) both;
628
+ }
629
+
630
+ @keyframes quad-block-pop {
631
+ 0% {
632
+ opacity: 0;
633
+ transform: scale(0.92);
634
+ transform-origin: center center;
635
+ }
636
+ 100% {
637
+ opacity: 1;
638
+ transform: scale(1);
639
+ transform-origin: center center;
640
+ }
641
+ }
642
+ </style>