@alphacifer/slidev-addon-theme 0.0.5 → 0.0.7
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.
- package/README.md +163 -2
- package/components/arc-arrow-process/ArcArrowProcess.vue +361 -0
- package/components/arc-arrow-process/ArcArrowProcessArrow.vue +143 -0
- package/components/arc-arrow-process/ArcArrowProcessCallout.vue +361 -0
- package/components/arc-arrow-process/ArcArrowProcessContent.vue +38 -0
- package/components/arc-arrow-process/ArcArrowProcessHeading.vue +43 -0
- package/components/arc-arrow-process/ArcArrowProcessIcon.vue +154 -0
- package/components/arc-compare/ArcCompare.vue +443 -0
- package/components/arc-compare/ArcCompareBadge.vue +63 -0
- package/components/arc-compare/ArcCompareCallout.vue +386 -0
- package/components/arc-compare/ArcCompareContent.vue +52 -0
- package/components/arc-compare/ArcCompareContents.vue +18 -0
- package/components/arc-compare/ArcCompareHeading.vue +52 -0
- package/components/arc-compare/ArcCompareLeft.vue +183 -0
- package/components/arc-compare/ArcCompareRight.vue +183 -0
- package/components/arc-compare/ArcCompareTitle.vue +47 -0
- package/components/arrow-triad/ArrowTriad.vue +368 -0
- package/components/arrow-triad/ArrowTriadCallout.vue +300 -0
- package/components/arrow-triad/ArrowTriadContent.vue +38 -0
- package/components/arrow-triad/ArrowTriadHeading.vue +38 -0
- package/components/arrow-triad/ArrowTriadIcon.vue +96 -0
- package/components/chevron-card/ChevronCard.vue +237 -0
- package/components/chevron-card/ChevronCardBadge.vue +74 -0
- package/components/chevron-card/ChevronCardContent.vue +39 -0
- package/components/chevron-card/ChevronCardHeading.vue +39 -0
- package/components/comparison-table/ComparisonTable.vue +244 -0
- package/components/comparison-table/ComparisonTableCell.vue +88 -0
- package/components/comparison-table/ComparisonTableCol.vue +97 -0
- package/components/comparison-table/ComparisonTableCols.vue +74 -0
- package/components/comparison-table/ComparisonTableRow.vue +92 -0
- package/components/comparison-table/ComparisonTableRows.vue +21 -0
- package/components/hex-triad/HexTriad.vue +438 -0
- package/components/hex-triad/HexTriadBadge.vue +110 -0
- package/components/hex-triad/HexTriadCallout.vue +200 -0
- package/components/hex-triad/HexTriadCenterBadge.vue +51 -0
- package/components/hex-triad/HexTriadContent.vue +20 -0
- package/components/hex-triad/HexTriadHeading.vue +28 -0
- package/components/hex-triad/HexTriadLayer.vue +28 -0
- package/components/horiz-card/HorizCard.vue +318 -0
- package/components/horiz-card/HorizCardBadge.vue +75 -0
- package/components/horiz-card/HorizCardContent.vue +46 -0
- package/components/horiz-card/HorizCardHeading.vue +47 -0
- package/components/horiz-card/HorizCardIcon.vue +118 -0
- package/components/quad-hub/QuadHub.vue +642 -0
- package/components/quad-hub/QuadHubCallout.vue +267 -0
- package/components/quad-hub/QuadHubCenter.vue +53 -0
- package/components/quad-hub/QuadHubContent.vue +39 -0
- package/components/quad-hub/QuadHubHeading.vue +44 -0
- package/components/quad-hub/QuadHubIcon.vue +112 -0
- package/components/shifting-heading/TransitionHeading.vue +2 -2
- package/components/vert-card/VertCard.vue +389 -0
- package/components/vert-card/VertCardBadge.vue +72 -0
- package/components/vert-card/VertCardContent.vue +45 -0
- package/components/vert-card/VertCardHeading.vue +68 -0
- package/components/vert-card/VertCardIcon.vue +126 -0
- package/components/vert-card/VertCardTitle.vue +76 -0
- package/layouts/core/arc-toc.vue +76 -113
- package/layouts/core/bg-center.vue +32 -4
- package/layouts/shifting-heading/shifting-intro.vue +35 -7
- package/package.json +3 -2
- package/utils/arcArrowProcess/context.ts +28 -0
- package/utils/arcArrowProcess/createArcArrowProcessGeometry.ts +137 -0
- package/utils/arcArrowProcess/index.ts +3 -0
- package/utils/arcArrowProcess/resolveArcArrowProcessItems.ts +189 -0
- package/utils/arcCompare/context.ts +36 -0
- package/utils/arcCompare/createArcCompareGeometry.ts +254 -0
- package/utils/arcCompare/index.ts +3 -0
- package/utils/arcCompare/resolveArcCompareItems.ts +123 -0
- package/utils/arrowTriad/context.ts +38 -0
- package/utils/arrowTriad/createArrowTriadGeometry.ts +63 -0
- package/utils/arrowTriad/index.ts +3 -0
- package/utils/arrowTriad/resolveArrowTriadItems.ts +121 -0
- package/utils/chevronCard/context.ts +12 -0
- package/utils/chevronCard/createChevronCardGeometry.ts +48 -0
- package/utils/chevronCard/index.ts +2 -0
- package/utils/comparisonTable/constants.ts +72 -0
- package/utils/comparisonTable/context.ts +24 -0
- package/utils/comparisonTable/index.ts +2 -0
- package/utils/hexTriad/context.ts +25 -0
- package/utils/hexTriad/createHexTriadGeometry.ts +182 -0
- package/utils/hexTriad/index.ts +3 -0
- package/utils/hexTriad/resolveHexTriadItems.ts +63 -0
- package/utils/horizCard/context.ts +13 -0
- package/utils/horizCard/createHorizCardGeometry.ts +243 -0
- package/utils/horizCard/index.ts +2 -0
- package/utils/quadHub/context.ts +26 -0
- package/utils/quadHub/createQuadHubGeometry.ts +331 -0
- package/utils/quadHub/index.ts +3 -0
- package/utils/quadHub/resolveQuadHubItems.ts +214 -0
- package/utils/useDiagramAutoScale.ts +300 -0
- package/utils/vertCard/context.ts +18 -0
- package/utils/vertCard/createVertCardGeometry.ts +242 -0
- package/utils/vertCard/index.ts +2 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
export interface IHexTriadPoint {
|
|
2
|
+
readonly x: number;
|
|
3
|
+
readonly y: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface IHexTriadCoordinates {
|
|
7
|
+
readonly centerX: number;
|
|
8
|
+
readonly centerY: number;
|
|
9
|
+
readonly hexRadius: number;
|
|
10
|
+
readonly triadRadius: number;
|
|
11
|
+
readonly badgeRadius: number;
|
|
12
|
+
readonly hex1: IHexTriadPoint;
|
|
13
|
+
readonly hex2: IHexTriadPoint;
|
|
14
|
+
readonly hex3: IHexTriadPoint;
|
|
15
|
+
readonly badge1: IHexTriadPoint;
|
|
16
|
+
readonly badge2: IHexTriadPoint;
|
|
17
|
+
readonly badge3: IHexTriadPoint;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates pointy-topped hexagon SVG points with sharp vertices
|
|
22
|
+
*/
|
|
23
|
+
export function createHexagonPoints(cx: number, cy: number, r: number): string {
|
|
24
|
+
const angles = [-90, -30, 30, 90, 150, 210];
|
|
25
|
+
|
|
26
|
+
return angles
|
|
27
|
+
.map((deg) => {
|
|
28
|
+
const rad = (deg * Math.PI) / 180;
|
|
29
|
+
const x = (cx + r * Math.cos(rad)).toFixed(1);
|
|
30
|
+
const y = (cy + r * Math.sin(rad)).toFixed(1);
|
|
31
|
+
|
|
32
|
+
return `${x},${y}`;
|
|
33
|
+
})
|
|
34
|
+
.join(' ');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a pointy-topped hexagon SVG path with smoothly rounded corners
|
|
39
|
+
*/
|
|
40
|
+
export function createRoundedHexagonPath(
|
|
41
|
+
cx: number,
|
|
42
|
+
cy: number,
|
|
43
|
+
r: number,
|
|
44
|
+
cornerRadius = 16,
|
|
45
|
+
): string {
|
|
46
|
+
const angles = [-90, -30, 30, 90, 150, 210];
|
|
47
|
+
const vertices = angles.map((deg) => {
|
|
48
|
+
const rad = (deg * Math.PI) / 180;
|
|
49
|
+
return {
|
|
50
|
+
x: cx + r * Math.cos(rad),
|
|
51
|
+
y: cy + r * Math.sin(rad),
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const d = Math.min(cornerRadius, r * 0.3);
|
|
56
|
+
const commands: string[] = [];
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < 6; i += 1) {
|
|
59
|
+
const prev = vertices[(i + 5) % 6];
|
|
60
|
+
const curr = vertices[i];
|
|
61
|
+
const next = vertices[(i + 1) % 6];
|
|
62
|
+
|
|
63
|
+
if (!prev || !curr || !next) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const lenPrev = Math.hypot(prev.x - curr.x, prev.y - curr.y);
|
|
68
|
+
const lenNext = Math.hypot(next.x - curr.x, next.y - curr.y);
|
|
69
|
+
|
|
70
|
+
const a = {
|
|
71
|
+
x: curr.x + ((prev.x - curr.x) / lenPrev) * d,
|
|
72
|
+
y: curr.y + ((prev.y - curr.y) / lenPrev) * d,
|
|
73
|
+
};
|
|
74
|
+
const b = {
|
|
75
|
+
x: curr.x + ((next.x - curr.x) / lenNext) * d,
|
|
76
|
+
y: curr.y + ((next.y - curr.y) / lenNext) * d,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (i === 0) {
|
|
80
|
+
commands.push(`M ${b.x.toFixed(1)} ${b.y.toFixed(1)}`);
|
|
81
|
+
} else {
|
|
82
|
+
commands.push(
|
|
83
|
+
`L ${a.x.toFixed(1)} ${a.y.toFixed(1)} Q ${curr.x.toFixed(1)} ${curr.y.toFixed(1)} ${b.x.toFixed(1)} ${b.y.toFixed(1)}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const prev0 = vertices[5];
|
|
89
|
+
const curr0 = vertices[0];
|
|
90
|
+
const next0 = vertices[1];
|
|
91
|
+
|
|
92
|
+
if (prev0 && curr0 && next0) {
|
|
93
|
+
const len0 = Math.hypot(prev0.x - curr0.x, prev0.y - curr0.y);
|
|
94
|
+
const lenNext0 = Math.hypot(next0.x - curr0.x, next0.y - curr0.y);
|
|
95
|
+
const a0 = {
|
|
96
|
+
x: curr0.x + ((prev0.x - curr0.x) / len0) * d,
|
|
97
|
+
y: curr0.y + ((prev0.y - curr0.y) / len0) * d,
|
|
98
|
+
};
|
|
99
|
+
const b0 = {
|
|
100
|
+
x: curr0.x + ((next0.x - curr0.x) / lenNext0) * d,
|
|
101
|
+
y: curr0.y + ((next0.y - curr0.y) / lenNext0) * d,
|
|
102
|
+
};
|
|
103
|
+
commands.push(
|
|
104
|
+
`L ${a0.x.toFixed(1)} ${a0.y.toFixed(1)} Q ${curr0.x.toFixed(1)} ${curr0.y.toFixed(1)} ${b0.x.toFixed(1)} ${b0.y.toFixed(1)} Z`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return commands.join(' ');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function calculateHexTriadCoordinates(
|
|
112
|
+
centerX = 270,
|
|
113
|
+
centerY = 224,
|
|
114
|
+
triadRadius = 66,
|
|
115
|
+
hexRadius = 114,
|
|
116
|
+
badgeRadius = 28,
|
|
117
|
+
horizontalSpread = 28,
|
|
118
|
+
): IHexTriadCoordinates {
|
|
119
|
+
const angleRad30 = (30 * Math.PI) / 180;
|
|
120
|
+
|
|
121
|
+
const hex2X = Number(
|
|
122
|
+
(centerX + triadRadius * Math.cos(angleRad30) + horizontalSpread).toFixed(
|
|
123
|
+
2,
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
const hex3X = Number(
|
|
127
|
+
(centerX - triadRadius * Math.cos(angleRad30) - horizontalSpread).toFixed(
|
|
128
|
+
2,
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
const bottomY = Number(
|
|
132
|
+
(centerY + triadRadius * Math.sin(angleRad30)).toFixed(2),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Exact equilateral height so that all three pairwise overlap areas are equal:
|
|
136
|
+
const spreadDistance = hex2X - hex3X;
|
|
137
|
+
const equilateralHeight = Number(
|
|
138
|
+
(spreadDistance * (Math.sqrt(3) / 2)).toFixed(2),
|
|
139
|
+
);
|
|
140
|
+
const hex1Y = Number((bottomY - equilateralHeight).toFixed(2));
|
|
141
|
+
|
|
142
|
+
const circumCenterY = Number(((hex1Y + 2 * bottomY) / 3).toFixed(2));
|
|
143
|
+
// Set circle radius to hexRadius (114) so it crosses the three yellow points
|
|
144
|
+
const circleRadius = hexRadius;
|
|
145
|
+
|
|
146
|
+
// Badges centered directly ON the white circle:
|
|
147
|
+
const badge1 = {
|
|
148
|
+
x: centerX,
|
|
149
|
+
y: Number((circumCenterY - circleRadius).toFixed(2)),
|
|
150
|
+
};
|
|
151
|
+
const badge2 = {
|
|
152
|
+
x: Number((centerX + circleRadius * Math.cos(angleRad30)).toFixed(2)),
|
|
153
|
+
y: Number((circumCenterY + circleRadius * Math.sin(angleRad30)).toFixed(2)),
|
|
154
|
+
};
|
|
155
|
+
const badge3 = {
|
|
156
|
+
x: Number((centerX - circleRadius * Math.cos(angleRad30)).toFixed(2)),
|
|
157
|
+
y: Number((circumCenterY + circleRadius * Math.sin(angleRad30)).toFixed(2)),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
centerX,
|
|
162
|
+
centerY: circumCenterY,
|
|
163
|
+
hexRadius,
|
|
164
|
+
triadRadius: circleRadius,
|
|
165
|
+
badgeRadius,
|
|
166
|
+
hex1: {
|
|
167
|
+
x: centerX,
|
|
168
|
+
y: hex1Y,
|
|
169
|
+
},
|
|
170
|
+
hex2: {
|
|
171
|
+
x: hex2X,
|
|
172
|
+
y: bottomY,
|
|
173
|
+
},
|
|
174
|
+
hex3: {
|
|
175
|
+
x: hex3X,
|
|
176
|
+
y: bottomY,
|
|
177
|
+
},
|
|
178
|
+
badge1,
|
|
179
|
+
badge2,
|
|
180
|
+
badge3,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface IHexTriadItem {
|
|
2
|
+
readonly id?: string | number;
|
|
3
|
+
readonly title: string;
|
|
4
|
+
readonly description?: string;
|
|
5
|
+
readonly color?: string;
|
|
6
|
+
readonly depthColor?: string;
|
|
7
|
+
readonly lightColor?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_HEX_TRIAD_ITEMS: readonly [
|
|
11
|
+
IHexTriadItem,
|
|
12
|
+
IHexTriadItem,
|
|
13
|
+
IHexTriadItem,
|
|
14
|
+
] = [
|
|
15
|
+
{
|
|
16
|
+
id: '01',
|
|
17
|
+
title: 'Text Here',
|
|
18
|
+
description:
|
|
19
|
+
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod sed diam nonummy nibh',
|
|
20
|
+
color: '#ee3248',
|
|
21
|
+
depthColor: '#9e1526',
|
|
22
|
+
lightColor: '#ff8595',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: '02',
|
|
26
|
+
title: 'Text Here',
|
|
27
|
+
description:
|
|
28
|
+
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod sed diam nonummy nibh',
|
|
29
|
+
color: '#f26522',
|
|
30
|
+
depthColor: '#a83908',
|
|
31
|
+
lightColor: '#ffb088',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: '03',
|
|
35
|
+
title: 'Text Here',
|
|
36
|
+
description:
|
|
37
|
+
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod sed diam nonummy nibh',
|
|
38
|
+
color: '#2b4594',
|
|
39
|
+
depthColor: '#172554',
|
|
40
|
+
lightColor: '#8da4e2',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export function resolveHexTriadItems(
|
|
45
|
+
userItems?: readonly IHexTriadItem[],
|
|
46
|
+
): readonly [IHexTriadItem, IHexTriadItem, IHexTriadItem] {
|
|
47
|
+
const items = userItems ?? [];
|
|
48
|
+
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
...DEFAULT_HEX_TRIAD_ITEMS[0],
|
|
52
|
+
...(items[0] ?? {}),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
...DEFAULT_HEX_TRIAD_ITEMS[1],
|
|
56
|
+
...(items[1] ?? {}),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
...DEFAULT_HEX_TRIAD_ITEMS[2],
|
|
60
|
+
...(items[2] ?? {}),
|
|
61
|
+
},
|
|
62
|
+
] as const;
|
|
63
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComputedRef, InjectionKey } from 'vue';
|
|
2
|
+
|
|
3
|
+
export interface IHorizCardContext {
|
|
4
|
+
readonly color: ComputedRef<string>;
|
|
5
|
+
readonly cardBg: ComputedRef<string>;
|
|
6
|
+
readonly lightColor: ComputedRef<string>;
|
|
7
|
+
readonly titleColor: ComputedRef<string>;
|
|
8
|
+
readonly step: ComputedRef<string | number>;
|
|
9
|
+
readonly textColor: ComputedRef<string | undefined>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const HORIZ_CARD_KEY: InjectionKey<IHorizCardContext> =
|
|
13
|
+
Symbol('HORIZ_CARD');
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
export interface ICreateHorizCardPathParams {
|
|
2
|
+
readonly width: number;
|
|
3
|
+
readonly height: number;
|
|
4
|
+
readonly chamferX?: number;
|
|
5
|
+
readonly chamferY?: number;
|
|
6
|
+
readonly borderRadius?: number;
|
|
7
|
+
readonly chamferFillet?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ICreateHorizCardAccentPathParams {
|
|
11
|
+
readonly width: number;
|
|
12
|
+
readonly height: number;
|
|
13
|
+
readonly chamferX?: number;
|
|
14
|
+
readonly chamferY?: number;
|
|
15
|
+
readonly accentEndX?: number;
|
|
16
|
+
readonly accentStartY?: number;
|
|
17
|
+
readonly chamferFillet?: number;
|
|
18
|
+
readonly accentOutsetX?: number;
|
|
19
|
+
readonly accentThickness?: number;
|
|
20
|
+
readonly accentTopFillet?: number;
|
|
21
|
+
readonly accentBottomFillet?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ICreateHorizCardBadgePathParams {
|
|
25
|
+
readonly width?: number;
|
|
26
|
+
readonly height?: number;
|
|
27
|
+
readonly topRadius?: number;
|
|
28
|
+
readonly tipRadius?: number;
|
|
29
|
+
readonly sideHeight?: number;
|
|
30
|
+
readonly bottomRadius?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface IHexToRgbaParams {
|
|
34
|
+
readonly hex: string;
|
|
35
|
+
readonly alpha?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generates an SVG path for the main HorizCard body.
|
|
40
|
+
* Features rounded top-left, top-right, and bottom-right corners,
|
|
41
|
+
* and a smooth filleted chamfer at the bottom-left corner matching the design.
|
|
42
|
+
*/
|
|
43
|
+
export function createHorizCardPath({
|
|
44
|
+
width,
|
|
45
|
+
height,
|
|
46
|
+
chamferX = Math.min(60, Math.round(width * 0.17)),
|
|
47
|
+
chamferY = Math.min(76, Math.round(height * 0.29)),
|
|
48
|
+
borderRadius = 24,
|
|
49
|
+
chamferFillet = 22,
|
|
50
|
+
}: ICreateHorizCardPathParams): string {
|
|
51
|
+
const safeR = Math.max(
|
|
52
|
+
0,
|
|
53
|
+
Math.min(borderRadius, width / 2, (height - chamferY) / 2),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const diagLen = Math.hypot(chamferX, chamferY);
|
|
57
|
+
const ux = diagLen > 0 ? chamferX / diagLen : 0;
|
|
58
|
+
const uy = diagLen > 0 ? chamferY / diagLen : 0;
|
|
59
|
+
|
|
60
|
+
const p1Y = height - chamferY - chamferFillet;
|
|
61
|
+
const p2X = chamferFillet * ux;
|
|
62
|
+
const p2Y = height - chamferY + chamferFillet * uy;
|
|
63
|
+
|
|
64
|
+
const p3X = chamferX - chamferFillet * ux;
|
|
65
|
+
const p3Y = height - chamferFillet * uy;
|
|
66
|
+
const p4X = chamferX + chamferFillet;
|
|
67
|
+
|
|
68
|
+
return [
|
|
69
|
+
`M ${safeR} 0`,
|
|
70
|
+
`H ${width - safeR}`,
|
|
71
|
+
`A ${safeR} ${safeR} 0 0 1 ${width} ${safeR}`,
|
|
72
|
+
`V ${height - safeR}`,
|
|
73
|
+
`A ${safeR} ${safeR} 0 0 1 ${width - safeR} ${height}`,
|
|
74
|
+
`H ${p4X.toFixed(2)}`,
|
|
75
|
+
`Q ${chamferX.toFixed(2)} ${height.toFixed(2)} ${p3X.toFixed(2)} ${p3Y.toFixed(2)}`,
|
|
76
|
+
`L ${p2X.toFixed(2)} ${p2Y.toFixed(2)}`,
|
|
77
|
+
`Q 0 ${(height - chamferY).toFixed(2)} 0 ${p1Y.toFixed(2)}`,
|
|
78
|
+
`V ${safeR}`,
|
|
79
|
+
`A ${safeR} ${safeR} 0 0 1 ${safeR} 0`,
|
|
80
|
+
'Z',
|
|
81
|
+
].join(' ');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generates an SVG path for the organic accent shape at the bottom-left of the card.
|
|
86
|
+
* Curves in parallel along with the white card body, featuring 2 distinct rounded curves
|
|
87
|
+
* (a top fillet and a bottom fillet) flanking the diagonal segment, perfectly mirroring
|
|
88
|
+
* the white card's dual filleted transitions.
|
|
89
|
+
*/
|
|
90
|
+
export function createHorizCardAccentPath({
|
|
91
|
+
width,
|
|
92
|
+
height,
|
|
93
|
+
chamferX = Math.min(60, Math.round(width * 0.17)),
|
|
94
|
+
chamferY = Math.min(76, Math.round(height * 0.29)),
|
|
95
|
+
accentEndX,
|
|
96
|
+
accentStartY = Math.round(height * 0.28),
|
|
97
|
+
chamferFillet = 22,
|
|
98
|
+
accentOutsetX,
|
|
99
|
+
accentThickness = 16,
|
|
100
|
+
accentTopFillet = 22,
|
|
101
|
+
accentBottomFillet = 22,
|
|
102
|
+
}: ICreateHorizCardAccentPathParams): string {
|
|
103
|
+
const diagLen = Math.hypot(chamferX, chamferY);
|
|
104
|
+
const ux = diagLen > 0 ? chamferX / diagLen : 0;
|
|
105
|
+
const uy = diagLen > 0 ? chamferY / diagLen : 0;
|
|
106
|
+
|
|
107
|
+
const p1Y = height - chamferY - chamferFillet;
|
|
108
|
+
const p2X = chamferFillet * ux;
|
|
109
|
+
const p2Y = height - chamferY + chamferFillet * uy;
|
|
110
|
+
|
|
111
|
+
const p3X = chamferX - chamferFillet * ux;
|
|
112
|
+
const p3Y = height - chamferFillet * uy;
|
|
113
|
+
const p4X = chamferX + chamferFillet;
|
|
114
|
+
|
|
115
|
+
// Normal vector pointing outward/down-left
|
|
116
|
+
const nx = -uy;
|
|
117
|
+
const ny = ux;
|
|
118
|
+
|
|
119
|
+
// Parallel offset thickness
|
|
120
|
+
const o2X = p2X + accentThickness * nx;
|
|
121
|
+
const o2Y = p2Y + accentThickness * ny;
|
|
122
|
+
const o3X = p3X + accentThickness * nx;
|
|
123
|
+
const o3Y = p3Y + accentThickness * ny;
|
|
124
|
+
|
|
125
|
+
// 1. Bottom vertex & curve (matching white bottom fillet)
|
|
126
|
+
const tBottom = (height - o3Y) / uy;
|
|
127
|
+
const vBottomX = o3X + tBottom * ux;
|
|
128
|
+
const vBottomY = height;
|
|
129
|
+
const bStartX = vBottomX - accentBottomFillet * ux;
|
|
130
|
+
const bStartY = vBottomY - accentBottomFillet * uy;
|
|
131
|
+
const bEndX = vBottomX + accentBottomFillet;
|
|
132
|
+
|
|
133
|
+
// 2. Top vertex & curve (matching white top fillet)
|
|
134
|
+
const outsetX = accentOutsetX ?? accentThickness;
|
|
135
|
+
const vTopX = -outsetX;
|
|
136
|
+
const tTop = (vTopX - o2X) / ux;
|
|
137
|
+
const vTopY = o2Y + tTop * uy;
|
|
138
|
+
const tStartX = vTopX;
|
|
139
|
+
const tStartY = vTopY - accentTopFillet;
|
|
140
|
+
const tEndX = vTopX + accentTopFillet * ux;
|
|
141
|
+
const tEndY = vTopY + accentTopFillet * uy;
|
|
142
|
+
|
|
143
|
+
const startY = accentStartY;
|
|
144
|
+
const endX = accentEndX ?? Math.round(p4X + 4);
|
|
145
|
+
|
|
146
|
+
return [
|
|
147
|
+
`M 0 ${startY}`,
|
|
148
|
+
`C -8 ${startY}, ${vTopX} ${startY + 20}, ${tStartX.toFixed(2)} ${tStartY.toFixed(2)}`,
|
|
149
|
+
// Curve 1: Top fillet transitioning vertical edge into diagonal
|
|
150
|
+
`Q ${vTopX.toFixed(2)} ${vTopY.toFixed(2)} ${tEndX.toFixed(2)} ${tEndY.toFixed(2)}`,
|
|
151
|
+
// Straight diagonal running parallel to the white card
|
|
152
|
+
`L ${bStartX.toFixed(2)} ${bStartY.toFixed(2)}`,
|
|
153
|
+
// Curve 2: Bottom fillet transitioning diagonal into horizontal edge
|
|
154
|
+
`Q ${vBottomX.toFixed(2)} ${vBottomY.toFixed(2)} ${bEndX.toFixed(2)} ${height}`,
|
|
155
|
+
`H ${endX}`,
|
|
156
|
+
`L ${p4X.toFixed(2)} ${height}`,
|
|
157
|
+
`Q ${chamferX.toFixed(2)} ${height.toFixed(2)} ${p3X.toFixed(2)} ${p3Y.toFixed(2)}`,
|
|
158
|
+
`L ${p2X.toFixed(2)} ${p2Y.toFixed(2)}`,
|
|
159
|
+
`Q 0 ${(height - chamferY).toFixed(2)} 0 ${p1Y.toFixed(2)}`,
|
|
160
|
+
`L 0 ${startY}`,
|
|
161
|
+
'Z',
|
|
162
|
+
].join(' ');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Generates an SVG path for the top ribbon step badge.
|
|
167
|
+
* Features 5 distinct rounded curves matching the design:
|
|
168
|
+
* 1. Top-Left rounded corner
|
|
169
|
+
* 2. Top-Right rounded corner
|
|
170
|
+
* 3. Bottom-Right rounded corner
|
|
171
|
+
* 4. Bottom-Center pointed tip curve
|
|
172
|
+
* 5. Bottom-Left rounded corner
|
|
173
|
+
*/
|
|
174
|
+
export function createHorizCardBadgePath({
|
|
175
|
+
width = 84,
|
|
176
|
+
height = 58,
|
|
177
|
+
topRadius = 12,
|
|
178
|
+
tipRadius = 8,
|
|
179
|
+
sideHeight = 46,
|
|
180
|
+
bottomRadius = 8,
|
|
181
|
+
}: ICreateHorizCardBadgePathParams = {}): string {
|
|
182
|
+
const halfW = width / 2;
|
|
183
|
+
const dx = halfW;
|
|
184
|
+
const dy = height - sideHeight;
|
|
185
|
+
const len = Math.hypot(dx, dy);
|
|
186
|
+
const ux = len > 0 ? dx / len : 0;
|
|
187
|
+
const uy = len > 0 ? dy / len : 0;
|
|
188
|
+
|
|
189
|
+
return [
|
|
190
|
+
// 1. Top-Left curve
|
|
191
|
+
`M ${topRadius} 0`,
|
|
192
|
+
// Top horizontal edge
|
|
193
|
+
`H ${width - topRadius}`,
|
|
194
|
+
// 2. Top-Right curve
|
|
195
|
+
`A ${topRadius} ${topRadius} 0 0 1 ${width} ${topRadius}`,
|
|
196
|
+
// Right vertical edge
|
|
197
|
+
`V ${sideHeight - bottomRadius}`,
|
|
198
|
+
// 3. Bottom-Right curve
|
|
199
|
+
`Q ${width} ${sideHeight} ${(width - bottomRadius * ux).toFixed(2)} ${(sideHeight + bottomRadius * uy).toFixed(2)}`,
|
|
200
|
+
// Bottom-right diagonal edge
|
|
201
|
+
`L ${(halfW + tipRadius * ux).toFixed(2)} ${(height - tipRadius * uy).toFixed(2)}`,
|
|
202
|
+
// 4. Bottom-Center tip curve
|
|
203
|
+
`Q ${halfW} ${height} ${(halfW - tipRadius * ux).toFixed(2)} ${(height - tipRadius * uy).toFixed(2)}`,
|
|
204
|
+
// Bottom-left diagonal edge
|
|
205
|
+
`L ${(bottomRadius * ux).toFixed(2)} ${(sideHeight + bottomRadius * uy).toFixed(2)}`,
|
|
206
|
+
// 5. Bottom-Left curve
|
|
207
|
+
`Q 0 ${sideHeight} 0 ${sideHeight - bottomRadius}`,
|
|
208
|
+
// Left vertical edge
|
|
209
|
+
`V ${topRadius}`,
|
|
210
|
+
`A ${topRadius} ${topRadius} 0 0 1 ${topRadius} 0`,
|
|
211
|
+
'Z',
|
|
212
|
+
].join(' ');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Converts a hex color string to an RGBA string with the given alpha transparency.
|
|
217
|
+
*/
|
|
218
|
+
export function hexToRgba({ hex, alpha = 0.15 }: IHexToRgbaParams): string {
|
|
219
|
+
let sanitized = hex.trim().replace(/^#/, '');
|
|
220
|
+
|
|
221
|
+
if (sanitized.length === 3) {
|
|
222
|
+
sanitized = sanitized
|
|
223
|
+
.split('')
|
|
224
|
+
.map((char) => {
|
|
225
|
+
return char + char;
|
|
226
|
+
})
|
|
227
|
+
.join('');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (sanitized.length !== 6) {
|
|
231
|
+
return hex;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const r = Number.parseInt(sanitized.slice(0, 2), 16);
|
|
235
|
+
const g = Number.parseInt(sanitized.slice(2, 4), 16);
|
|
236
|
+
const b = Number.parseInt(sanitized.slice(4, 6), 16);
|
|
237
|
+
|
|
238
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
|
239
|
+
return hex;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
243
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ComputedRef, InjectionKey } from 'vue';
|
|
2
|
+
|
|
3
|
+
import type { IQuadHubItem, IResolvedQuadHubItem } from './resolveQuadHubItems';
|
|
4
|
+
|
|
5
|
+
export interface IQuadHubRegistration {
|
|
6
|
+
readonly index: number;
|
|
7
|
+
readonly item: ComputedRef<IResolvedQuadHubItem>;
|
|
8
|
+
readonly setCustomBadge?: (text: string | number) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IQuadHubRootContext {
|
|
12
|
+
readonly animation?: ComputedRef<boolean>;
|
|
13
|
+
readonly registerCallout: (props?: IQuadHubItem) => IQuadHubRegistration;
|
|
14
|
+
readonly items: ComputedRef<readonly IResolvedQuadHubItem[]>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface IQuadHubCalloutContext {
|
|
18
|
+
readonly index: number;
|
|
19
|
+
readonly item: ComputedRef<IResolvedQuadHubItem>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const QUAD_HUB_ROOT_KEY: InjectionKey<IQuadHubRootContext> =
|
|
23
|
+
Symbol('QUAD_HUB_ROOT_KEY');
|
|
24
|
+
|
|
25
|
+
export const QUAD_HUB_CALLOUT_KEY: InjectionKey<IQuadHubCalloutContext> =
|
|
26
|
+
Symbol('QUAD_HUB_CALLOUT_KEY');
|