@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,732 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ type ComputedRef,
4
+ computed,
5
+ Fragment,
6
+ inject,
7
+ provide,
8
+ ref,
9
+ useSlots,
10
+ type VNode,
11
+ watch,
12
+ } from 'vue';
13
+
14
+ import {
15
+ ARC_DID_ROOT_KEY,
16
+ type IArcDiDRegisteredLayerData,
17
+ type IArcDiDRootContext,
18
+ } from '../../../utils/defenseInDepth/arcDid/context';
19
+ import { createArcDiDGeometry } from '../../../utils/defenseInDepth/arcDid/createArcDiDGeometry';
20
+ import type {
21
+ IArcDiDLayerItem,
22
+ TArcDiDOrder,
23
+ } from '../../../utils/defenseInDepth/arcDid/types';
24
+ import {
25
+ lightenColor,
26
+ resolveArcDiDPalette,
27
+ } from '../../../utils/defenseInDepth/shared/colors';
28
+ import { DEFAULT_DID_COLOR } from '../../../utils/defenseInDepth/shared/constants';
29
+ import { useDiagramAutoScale } from '../../../utils/useDiagramAutoScale';
30
+ import { useMergedUnoAttrs } from '../../../utils/useMergedUnoAttrs';
31
+
32
+ defineOptions({
33
+ name: 'ArcDiD',
34
+ inheritAttrs: false,
35
+ });
36
+
37
+ export interface IArcDiDProps {
38
+ readonly order?: TArcDiDOrder;
39
+ readonly count?: number;
40
+ readonly activeIndex?: number;
41
+ readonly color?: string;
42
+ readonly colors?: readonly string[] | string[];
43
+ readonly layers?: readonly IArcDiDLayerItem[];
44
+ readonly width?: number | string;
45
+ readonly height?: number | string;
46
+ readonly scale?: number;
47
+ readonly autoScale?: boolean;
48
+ readonly maxScale?: number;
49
+ readonly animation?: boolean;
50
+ readonly startDelay?: number;
51
+ readonly interactive?: boolean;
52
+ readonly viewBoxWidth?: number;
53
+ readonly viewBoxHeight?: number;
54
+ readonly innerRadius?: number;
55
+ readonly outerRadius?: number;
56
+ readonly gap?: number;
57
+ readonly showDecorativeArcs?: boolean;
58
+ readonly titleAngleSpan?: number;
59
+ readonly showPartBoundaries?: boolean;
60
+ readonly activePart?: string | number;
61
+ }
62
+
63
+ const props = withDefaults(defineProps<IArcDiDProps>(), {
64
+ order: 'outer-to-inner',
65
+ count: undefined,
66
+ activeIndex: -1,
67
+ activePart: undefined,
68
+ color: DEFAULT_DID_COLOR,
69
+ colors: undefined,
70
+ layers: undefined,
71
+ width: 720,
72
+ height: undefined,
73
+ scale: undefined,
74
+ autoScale: true,
75
+ maxScale: undefined,
76
+ animation: true,
77
+ startDelay: undefined,
78
+ interactive: true,
79
+ viewBoxWidth: 900,
80
+ viewBoxHeight: 460,
81
+ innerRadius: 90,
82
+ outerRadius: 445,
83
+ gap: 0,
84
+ showDecorativeArcs: false,
85
+ titleAngleSpan: undefined,
86
+ showPartBoundaries: true,
87
+ });
88
+
89
+ const emit = defineEmits<{
90
+ (e: 'update:activeIndex', index: number): void;
91
+ (e: 'update:activePart', part: string | number | undefined): void;
92
+ (e: 'select', index: number): void;
93
+ (e: 'part-click', part: IArcDiDPartGeometry): void;
94
+ (e: 'click', event: MouseEvent): void;
95
+ }>();
96
+
97
+ const slots = useSlots();
98
+ const rootRef = ref<HTMLElement | null>(null);
99
+
100
+ const shiftingIntro = inject<
101
+ | {
102
+ active: ComputedRef<boolean>;
103
+ isShifting?: ComputedRef<boolean>;
104
+ }
105
+ | undefined
106
+ >('SLIDEV_LAYOUT_SHIFTING_INTRO', undefined);
107
+
108
+ const isAnimated = computed(() => {
109
+ if (!props.animation) {
110
+ return false;
111
+ }
112
+ if (shiftingIntro) {
113
+ return shiftingIntro.active.value;
114
+ }
115
+ return true;
116
+ });
117
+
118
+ const resolvedStartDelay = computed(() => {
119
+ if (props.startDelay !== undefined) {
120
+ return props.startDelay;
121
+ }
122
+ if (shiftingIntro?.isShifting?.value) {
123
+ return 500;
124
+ }
125
+ return 0;
126
+ });
127
+
128
+ interface INamedComponent {
129
+ readonly type?: {
130
+ readonly name?: string;
131
+ readonly displayName?: string;
132
+ readonly __name?: string;
133
+ };
134
+ }
135
+
136
+ function hasComponentName(type: unknown, targetName: string): boolean {
137
+ if (typeof type === 'object' && type !== null) {
138
+ const comp = type as INamedComponent['type'];
139
+ return (
140
+ comp?.name === targetName ||
141
+ comp?.displayName === targetName ||
142
+ comp?.__name === targetName
143
+ );
144
+ }
145
+ return false;
146
+ }
147
+
148
+ function countSlottedLayers(slotNodes?: VNode[]): number {
149
+ if (!slotNodes) return 0;
150
+ let count = 0;
151
+ for (const node of slotNodes) {
152
+ if (hasComponentName(node.type, 'ArcDiDLayer')) {
153
+ count++;
154
+ continue;
155
+ }
156
+ if (node.type === Fragment && Array.isArray(node.children)) {
157
+ count += countSlottedLayers(node.children as VNode[]);
158
+ }
159
+ }
160
+ return count;
161
+ }
162
+
163
+ const slottedLayerCount = computed(() => {
164
+ const defaultSlotNodes = slots.default?.();
165
+ return countSlottedLayers(defaultSlotNodes);
166
+ });
167
+
168
+ const registeredLayerTokens = ref<symbol[]>([]);
169
+ const registeredLayerData = ref<Map<symbol, IArcDiDRegisteredLayerData>>(
170
+ new Map(),
171
+ );
172
+
173
+ function registerLayer(
174
+ token?: symbol,
175
+ dataRef?: ComputedRef<IArcDiDRegisteredLayerData | undefined>,
176
+ ) {
177
+ const lToken = token ?? Symbol();
178
+ if (!registeredLayerTokens.value.includes(lToken)) {
179
+ registeredLayerTokens.value = [...registeredLayerTokens.value, lToken];
180
+ }
181
+
182
+ let stopWatch: (() => void) | undefined;
183
+ if (dataRef) {
184
+ stopWatch = watch(
185
+ dataRef,
186
+ (val) => {
187
+ const nextMap = new Map(registeredLayerData.value);
188
+ if (val) {
189
+ nextMap.set(lToken, val);
190
+ } else {
191
+ nextMap.delete(lToken);
192
+ }
193
+ registeredLayerData.value = nextMap;
194
+ },
195
+ { immediate: true },
196
+ );
197
+ }
198
+
199
+ const index = computed(() => registeredLayerTokens.value.indexOf(lToken));
200
+
201
+ return {
202
+ index,
203
+ unregister: () => {
204
+ stopWatch?.();
205
+ registeredLayerTokens.value = registeredLayerTokens.value.filter(
206
+ (t) => t !== lToken,
207
+ );
208
+ const nextMap = new Map(registeredLayerData.value);
209
+ nextMap.delete(lToken);
210
+ registeredLayerData.value = nextMap;
211
+ },
212
+ };
213
+ }
214
+
215
+ const resolvedCount = computed<number>(() => {
216
+ if (props.count !== undefined && props.count > 0) {
217
+ return Math.max(1, Math.min(props.count, 6));
218
+ }
219
+ if (props.layers && props.layers.length > 0) {
220
+ return Math.max(1, Math.min(props.layers.length, 6));
221
+ }
222
+ if (slottedLayerCount.value > 0) {
223
+ return Math.max(1, Math.min(slottedLayerCount.value, 6));
224
+ }
225
+ if (registeredLayerTokens.value.length > 0) {
226
+ return Math.max(1, Math.min(registeredLayerTokens.value.length, 6));
227
+ }
228
+ return 0;
229
+ });
230
+
231
+ const resolvedLayers = computed<IArcDiDLayerItem[]>(() => {
232
+ const indexed: (IArcDiDLayerItem | undefined)[] = [];
233
+ const unindexed: IArcDiDLayerItem[] = [];
234
+
235
+ for (let i = 0; i < registeredLayerTokens.value.length; i++) {
236
+ const token = registeredLayerTokens.value[i];
237
+ if (!token) continue;
238
+ const data = registeredLayerData.value.get(token);
239
+ const item: IArcDiDLayerItem = {
240
+ title: data?.title ?? '',
241
+ color: data?.color,
242
+ parts: data?.parts ?? [],
243
+ sectors: data?.sectors ?? [],
244
+ titleAngleSpan: data?.titleAngleSpan,
245
+ titleWidth: data?.titleWidth,
246
+ };
247
+ if (data?.index !== undefined && data.index >= 0) {
248
+ indexed[data.index] = item;
249
+ } else {
250
+ unindexed.push(item);
251
+ }
252
+ }
253
+
254
+ let unindexedIdx = 0;
255
+ const result: IArcDiDLayerItem[] = [];
256
+ const total = Math.max(indexed.length, registeredLayerTokens.value.length);
257
+ for (let i = 0; i < total; i++) {
258
+ const item = indexed[i];
259
+ if (item) {
260
+ result.push(item);
261
+ } else {
262
+ const fallback = unindexed[unindexedIdx++];
263
+ if (fallback) {
264
+ result.push(fallback);
265
+ }
266
+ }
267
+ }
268
+ return result;
269
+ });
270
+
271
+ const effectiveLayers = computed<readonly IArcDiDLayerItem[]>(() => {
272
+ if (props.layers && props.layers.length > 0) {
273
+ return props.layers;
274
+ }
275
+ return resolvedLayers.value;
276
+ });
277
+
278
+ const palette = computed(() =>
279
+ resolveArcDiDPalette(props.color, resolvedCount.value),
280
+ );
281
+
282
+ function getLayerColor(index: number): string {
283
+ if (index >= 0 && index < registeredLayerTokens.value.length) {
284
+ const token = registeredLayerTokens.value[index];
285
+ if (token) {
286
+ const custom = registeredLayerData.value.get(token)?.color;
287
+ if (custom) {
288
+ return custom;
289
+ }
290
+ }
291
+ }
292
+
293
+ const customColor = props.colors?.[index];
294
+ if (customColor) {
295
+ return customColor;
296
+ }
297
+
298
+ const defaultLayers = palette.value.layerColors;
299
+ return defaultLayers[index % defaultLayers.length] ?? DEFAULT_DID_COLOR;
300
+ }
301
+
302
+ const internalActiveIndex = ref<number>(props.activeIndex);
303
+
304
+ watch(
305
+ () => props.activeIndex,
306
+ (val) => {
307
+ internalActiveIndex.value = val;
308
+ },
309
+ );
310
+
311
+ function setActiveIndex(index: number): void {
312
+ internalActiveIndex.value = index;
313
+ emit('update:activeIndex', index);
314
+ emit('select', index);
315
+ }
316
+
317
+ const internalActivePart = ref<string | number | undefined>(props.activePart);
318
+ watch(
319
+ () => props.activePart,
320
+ (val) => {
321
+ internalActivePart.value = val;
322
+ },
323
+ );
324
+
325
+ function setActivePart(part: string | number | undefined): void {
326
+ internalActivePart.value = part;
327
+ emit('update:activePart', part);
328
+ }
329
+
330
+ function isPartActive(part: IArcDiDPartGeometry): boolean {
331
+ if (part.active) {
332
+ return true;
333
+ }
334
+ if (internalActivePart.value !== undefined) {
335
+ return (
336
+ internalActivePart.value === part.label ||
337
+ internalActivePart.value === part.index
338
+ );
339
+ }
340
+ return false;
341
+ }
342
+
343
+ const geo = computed(() => {
344
+ return createArcDiDGeometry({
345
+ viewBoxWidth: props.viewBoxWidth,
346
+ viewBoxHeight: props.viewBoxHeight,
347
+ innerRadius: props.innerRadius,
348
+ outerRadius: props.outerRadius,
349
+ count: resolvedCount.value,
350
+ gap: props.gap,
351
+ order: props.order,
352
+ titleAngleSpan: props.titleAngleSpan,
353
+ layers: effectiveLayers.value,
354
+ });
355
+ });
356
+
357
+ const hasAnyActivePart = computed<boolean>(() => {
358
+ if (internalActivePart.value !== undefined) {
359
+ return true;
360
+ }
361
+ for (const layer of geo.value.layers) {
362
+ for (const part of layer.parts) {
363
+ if (part.active) {
364
+ return true;
365
+ }
366
+ }
367
+ }
368
+ return false;
369
+ });
370
+
371
+ function getActivePartFill(
372
+ customColor?: string,
373
+ fallbackBaseColor?: string,
374
+ ): string {
375
+ const base = customColor || fallbackBaseColor || DEFAULT_DID_COLOR;
376
+ return lightenColor({ hex: base, ratio: 0.5 });
377
+ }
378
+
379
+ function getPartOpacity(layerIndex: number, isActive: boolean): number {
380
+ if (hasAnyActivePart.value) {
381
+ return isActive ? 1 : 0.28;
382
+ }
383
+ if (
384
+ internalActiveIndex.value === -1 ||
385
+ internalActiveIndex.value === layerIndex
386
+ ) {
387
+ return 1;
388
+ }
389
+ return 0.45;
390
+ }
391
+
392
+ function handlePartClick(part: IArcDiDPartGeometry, layerIndex: number): void {
393
+ if (!props.interactive) return;
394
+ emit('part-click', part);
395
+ if (isPartActive(part)) {
396
+ setActivePart(undefined);
397
+ } else {
398
+ setActivePart(part.label);
399
+ if (
400
+ internalActiveIndex.value !== -1 &&
401
+ internalActiveIndex.value !== layerIndex
402
+ ) {
403
+ setActiveIndex(layerIndex);
404
+ }
405
+ }
406
+ }
407
+
408
+ const rootContext: IArcDiDRootContext = {
409
+ geo,
410
+ count: resolvedCount,
411
+ activeIndex: internalActiveIndex,
412
+ setActiveIndex,
413
+ activePart: internalActivePart,
414
+ setActivePart,
415
+ hasAnyActivePart,
416
+ isPartActive: (label, index, activeProp) => {
417
+ if (activeProp) return true;
418
+ if (internalActivePart.value !== undefined) {
419
+ return (
420
+ internalActivePart.value === label || internalActivePart.value === index
421
+ );
422
+ }
423
+ return false;
424
+ },
425
+ color: computed(() => props.color),
426
+ colors: computed(() => props.colors),
427
+ order: computed(() => props.order),
428
+ animation: isAnimated,
429
+ interactive: computed(() => props.interactive),
430
+ startDelay: resolvedStartDelay,
431
+ getLayerColor,
432
+ registerLayer,
433
+ };
434
+
435
+ provide(ARC_DID_ROOT_KEY, rootContext);
436
+
437
+ const { resolvedScale, containerHeight } = useDiagramAutoScale({
438
+ rootRef,
439
+ baseWidth: props.viewBoxWidth,
440
+ baseHeight: props.viewBoxHeight,
441
+ scale: () => props.scale,
442
+ autoScale: () => props.autoScale,
443
+ maxScale: () => props.maxScale,
444
+ width: () => props.width,
445
+ height: () => props.height,
446
+ });
447
+
448
+ const stageTransform = computed(() => {
449
+ if (resolvedScale.value !== 1) {
450
+ return `scale(${resolvedScale.value})`;
451
+ }
452
+ return undefined;
453
+ });
454
+
455
+ const resolvedWidthStyle = computed(() => {
456
+ if (props.width !== undefined) {
457
+ return typeof props.width === 'number' ? `${props.width}px` : props.width;
458
+ }
459
+ return '100%';
460
+ });
461
+
462
+ const resolvedHeightStyle = computed(() => {
463
+ if (props.height !== undefined) {
464
+ return typeof props.height === 'number'
465
+ ? `${props.height}px`
466
+ : props.height;
467
+ }
468
+ if (containerHeight.value > 0) {
469
+ return `${containerHeight.value}px`;
470
+ }
471
+ return `${props.viewBoxHeight}px`;
472
+ });
473
+
474
+ function handleLayerClick(index: number): void {
475
+ if (props.interactive) {
476
+ if (internalActiveIndex.value === index) {
477
+ setActiveIndex(-1);
478
+ } else {
479
+ setActiveIndex(index);
480
+ }
481
+ }
482
+ }
483
+
484
+ function handleContainerClick(event: MouseEvent): void {
485
+ emit('click', event);
486
+ }
487
+
488
+ const uid = Math.random().toString(36).slice(2, 8);
489
+ const gradIdPrefix = `alpha-arc-did-grad-${uid}`;
490
+
491
+ const { className, forwardedAttrs } = useMergedUnoAttrs(
492
+ 'alpha-arc-did w-full flex items-center justify-center select-none relative',
493
+ );
494
+ </script>
495
+
496
+ <template>
497
+ <div
498
+ ref="rootRef"
499
+ v-bind="forwardedAttrs()"
500
+ :class="className()"
501
+ :data-count="resolvedCount"
502
+ :data-active-index="internalActiveIndex"
503
+ :data-order="props.order"
504
+ :style="{
505
+ width: resolvedWidthStyle,
506
+ height: resolvedHeightStyle,
507
+ '--arc-did-start-delay': `${resolvedStartDelay}ms`,
508
+ }"
509
+ @click="handleContainerClick"
510
+ >
511
+ <!-- Stage Container with diagram auto-scale -->
512
+ <div
513
+ class="alpha-arc-did__stage relative flex items-center justify-center overflow-visible flex-shrink-0"
514
+ :style="{
515
+ width: `${props.viewBoxWidth}px`,
516
+ height: `${props.viewBoxHeight}px`,
517
+ transform: stageTransform,
518
+ transformOrigin: 'center center',
519
+ }"
520
+ >
521
+ <!-- Base SVG Layer: Concentric Arcs and Decorative Flairs -->
522
+ <svg
523
+ class="alpha-arc-did__svg absolute inset-0 w-full h-full overflow-visible pointer-events-none"
524
+ :viewBox="`0 0 ${props.viewBoxWidth} ${props.viewBoxHeight}`"
525
+ xmlns="http://www.w3.org/2000/svg"
526
+ >
527
+ <defs>
528
+ <filter id="alpha-arc-did-glow" x="-20%" y="-20%" width="140%" height="140%">
529
+ <feGaussianBlur stdDeviation="3" result="blur" />
530
+ <feComposite in="SourceGraphic" in2="blur" operator="over" />
531
+ </filter>
532
+ </defs>
533
+
534
+ <!-- Concentric Arc Bands (Seamless with zero spacing) -->
535
+ <g class="alpha-arc-did__layers">
536
+ <g
537
+ v-for="layer in geo.layers"
538
+ :key="layer.index"
539
+ class="alpha-arc-did__layer-group"
540
+ >
541
+ <!-- Full base band -->
542
+ <path
543
+ :d="layer.path"
544
+ :fill="getLayerColor(layer.index)"
545
+ :stroke="props.showPartBoundaries ? palette.strokeColor : 'none'"
546
+ :stroke-width="props.showPartBoundaries ? 1.5 : 0"
547
+ stroke-linejoin="round"
548
+ class="alpha-arc-did__band transition-all duration-300 ease-out"
549
+ :class="[
550
+ { 'alpha-arc-did__band--animated': isAnimated },
551
+ props.interactive ? 'cursor-pointer pointer-events-auto' : 'pointer-events-none',
552
+ ]"
553
+ :style="{
554
+ opacity: hasAnyActivePart
555
+ ? (internalActiveIndex === layer.index ? 0.45 : 0.25)
556
+ : (internalActiveIndex === -1 || internalActiveIndex === layer.index ? 1 : 0.45),
557
+ filter:
558
+ internalActiveIndex === layer.index && !hasAnyActivePart
559
+ ? 'drop-shadow(0 0 12px rgba(14, 165, 233, 0.45))'
560
+ : undefined,
561
+ '--layer-anim-delay': `${resolvedStartDelay + layer.index * 100}ms`,
562
+ }"
563
+ @click="handleLayerClick(layer.index)"
564
+ />
565
+
566
+ <!-- Title Sector Boundary/Area (when layer has parts) -->
567
+ <path
568
+ v-if="props.showPartBoundaries && layer.titlePath && layer.parts.length > 0"
569
+ :d="layer.titlePath"
570
+ :fill="getLayerColor(layer.index)"
571
+ :stroke="palette.strokeColor"
572
+ stroke-width="1.5"
573
+ stroke-linejoin="round"
574
+ class="alpha-arc-did__band alpha-arc-did__sector alpha-arc-did__sector--title transition-all duration-300 ease-out hover:brightness-105"
575
+ :class="[
576
+ { 'alpha-arc-did__band--animated': isAnimated },
577
+ props.interactive ? 'cursor-pointer pointer-events-auto' : 'pointer-events-none',
578
+ ]"
579
+ :style="{
580
+ opacity: hasAnyActivePart
581
+ ? (internalActiveIndex === layer.index ? 0.45 : 0.25)
582
+ : (internalActiveIndex === -1 || internalActiveIndex === layer.index ? 1 : 0.45),
583
+ '--layer-anim-delay': `${resolvedStartDelay + layer.index * 100}ms`,
584
+ }"
585
+ @click="handleLayerClick(layer.index)"
586
+ />
587
+
588
+ <!-- Interactive Part Sector Areas on top with visible boundaries -->
589
+ <template v-if="layer.sectors.length > 0">
590
+ <template v-for="sector in layer.sectors" :key="sector.index">
591
+ <!-- If sector has split: true, render individual track sub-bands -->
592
+ <template v-if="sector.split">
593
+ <path
594
+ v-for="part in sector.parts"
595
+ :key="part.index"
596
+ :d="part.path"
597
+ :fill="isPartActive(part) ? getActivePartFill(part.color || sector.color, getLayerColor(layer.index)) : (part.color || sector.color || getLayerColor(layer.index))"
598
+ :stroke="isPartActive(part) ? '#ffffff' : (props.showPartBoundaries ? palette.strokeColor : 'none')"
599
+ :stroke-width="isPartActive(part) ? 3.5 : (props.showPartBoundaries ? 1.5 : 0)"
600
+ stroke-linejoin="round"
601
+ class="alpha-arc-did__band alpha-arc-did__sector alpha-arc-did__sector--part transition-all duration-300 ease-out hover:brightness-110"
602
+ :class="[
603
+ { 'alpha-arc-did__band--animated': isAnimated },
604
+ { 'alpha-arc-did__sector--active': isPartActive(part) },
605
+ props.interactive ? 'cursor-pointer pointer-events-auto' : 'pointer-events-none',
606
+ ]"
607
+ :style="{
608
+ opacity: getPartOpacity(layer.index, isPartActive(part)),
609
+ filter: isPartActive(part)
610
+ ? 'drop-shadow(0 0 14px rgba(255, 255, 255, 0.85)) drop-shadow(0 0 6px rgba(56, 189, 248, 0.8)) brightness(1.2)'
611
+ : undefined,
612
+ '--layer-anim-delay': `${resolvedStartDelay + layer.index * 100}ms`,
613
+ }"
614
+ @click.stop="handlePartClick(part, layer.index)"
615
+ />
616
+ </template>
617
+ <!-- If sector is NOT split: render the entire sector band -->
618
+ <template v-else>
619
+ <path
620
+ :d="sector.path"
621
+ :fill="sector.parts[0] && isPartActive(sector.parts[0]) ? getActivePartFill(sector.color, getLayerColor(layer.index)) : (sector.color || getLayerColor(layer.index))"
622
+ :stroke="sector.parts[0] && isPartActive(sector.parts[0]) ? '#ffffff' : (props.showPartBoundaries ? palette.strokeColor : 'none')"
623
+ :stroke-width="sector.parts[0] && isPartActive(sector.parts[0]) ? 3.5 : (props.showPartBoundaries ? 1.5 : 0)"
624
+ stroke-linejoin="round"
625
+ class="alpha-arc-did__band alpha-arc-did__sector alpha-arc-did__sector--part transition-all duration-300 ease-out hover:brightness-110"
626
+ :class="[
627
+ { 'alpha-arc-did__band--animated': isAnimated },
628
+ { 'alpha-arc-did__sector--active': sector.parts[0] && isPartActive(sector.parts[0]) },
629
+ props.interactive ? 'cursor-pointer pointer-events-auto' : 'pointer-events-none',
630
+ ]"
631
+ :style="{
632
+ opacity: getPartOpacity(layer.index, !!(sector.parts[0] && isPartActive(sector.parts[0]))),
633
+ filter: sector.parts[0] && isPartActive(sector.parts[0])
634
+ ? 'drop-shadow(0 0 14px rgba(255, 255, 255, 0.85)) drop-shadow(0 0 6px rgba(56, 189, 248, 0.8)) brightness(1.2)'
635
+ : undefined,
636
+ '--layer-anim-delay': `${resolvedStartDelay + layer.index * 100}ms`,
637
+ }"
638
+ @click.stop="sector.parts[0] ? handlePartClick(sector.parts[0], layer.index) : handleLayerClick(layer.index)"
639
+ />
640
+ </template>
641
+
642
+ <!-- Divider concentric lines when sector is split -->
643
+ <template v-if="props.showPartBoundaries && sector.split && sector.dividerPaths.length > 0">
644
+ <path
645
+ v-for="(dPath, dIdx) in sector.dividerPaths"
646
+ :key="dIdx"
647
+ :d="dPath"
648
+ fill="none"
649
+ :stroke="palette.strokeColor"
650
+ stroke-width="1.5"
651
+ stroke-linecap="round"
652
+ class="pointer-events-none"
653
+ />
654
+ </template>
655
+ </template>
656
+ </template>
657
+ </g>
658
+ </g>
659
+
660
+ <!-- Active Sector Highlight Overlay (rendered on top of all layers and dividers so stroke & glow are never clipped) -->
661
+ <g v-if="hasAnyActivePart" class="alpha-arc-did__active-sectors pointer-events-none">
662
+ <template v-for="layer in geo.layers" :key="layer.index">
663
+ <template v-for="sector in layer.sectors" :key="sector.index">
664
+ <template v-if="sector.split">
665
+ <template v-for="part in sector.parts" :key="part.index">
666
+ <path
667
+ v-if="isPartActive(part) && part.path"
668
+ :d="part.path"
669
+ :fill="getActivePartFill(part.color || sector.color, getLayerColor(layer.index))"
670
+ stroke="#ffffff"
671
+ stroke-width="3.5"
672
+ stroke-linejoin="round"
673
+ class="alpha-arc-did__band alpha-arc-did__sector alpha-arc-did__sector--active pointer-events-none"
674
+ :style="{
675
+ filter: 'drop-shadow(0 0 14px rgba(255, 255, 255, 0.85)) drop-shadow(0 0 6px rgba(56, 189, 248, 0.8)) brightness(1.2)',
676
+ }"
677
+ />
678
+ </template>
679
+ </template>
680
+ <template v-else>
681
+ <path
682
+ v-if="sector.parts[0] && isPartActive(sector.parts[0]) && sector.path"
683
+ :d="sector.path"
684
+ :fill="getActivePartFill(sector.color, getLayerColor(layer.index))"
685
+ stroke="#ffffff"
686
+ stroke-width="3.5"
687
+ stroke-linejoin="round"
688
+ class="alpha-arc-did__band alpha-arc-did__sector alpha-arc-did__sector--active pointer-events-none"
689
+ :style="{
690
+ filter: 'drop-shadow(0 0 14px rgba(255, 255, 255, 0.85)) drop-shadow(0 0 6px rgba(56, 189, 248, 0.8)) brightness(1.2)',
691
+ }"
692
+ />
693
+ </template>
694
+ </template>
695
+ </template>
696
+ </g>
697
+
698
+ <!-- Center Cutout Hole (Solid White Semicircle) -->
699
+ <path
700
+ v-if="geo.innerCutoutPath"
701
+ :d="geo.innerCutoutPath"
702
+ fill="#ffffff"
703
+ stroke="none"
704
+ class="pointer-events-none"
705
+ />
706
+ </svg>
707
+
708
+ <!-- Stage Overlays: Slotted Layers -->
709
+ <div class="alpha-arc-did__stage-overlay absolute inset-0 w-full h-full pointer-events-none">
710
+ <slot />
711
+ </div>
712
+ </div>
713
+ </div>
714
+ </template>
715
+
716
+ <style scoped>
717
+ .alpha-arc-did__band--animated {
718
+ animation: arc-did-band-in 600ms cubic-bezier(0.16, 1, 0.3, 1) var(--layer-anim-delay, 0ms) both;
719
+ transform-origin: 50% 90%;
720
+ }
721
+
722
+ @keyframes arc-did-band-in {
723
+ 0% {
724
+ opacity: 0;
725
+ transform: scale(0.92);
726
+ }
727
+ 100% {
728
+ opacity: 1;
729
+ transform: scale(1);
730
+ }
731
+ }
732
+ </style>