@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.
Files changed (93) hide show
  1. package/README.md +163 -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/comparison-table/ComparisonTable.vue +244 -0
  27. package/components/comparison-table/ComparisonTableCell.vue +88 -0
  28. package/components/comparison-table/ComparisonTableCol.vue +97 -0
  29. package/components/comparison-table/ComparisonTableCols.vue +74 -0
  30. package/components/comparison-table/ComparisonTableRow.vue +92 -0
  31. package/components/comparison-table/ComparisonTableRows.vue +21 -0
  32. package/components/hex-triad/HexTriad.vue +438 -0
  33. package/components/hex-triad/HexTriadBadge.vue +110 -0
  34. package/components/hex-triad/HexTriadCallout.vue +200 -0
  35. package/components/hex-triad/HexTriadCenterBadge.vue +51 -0
  36. package/components/hex-triad/HexTriadContent.vue +20 -0
  37. package/components/hex-triad/HexTriadHeading.vue +28 -0
  38. package/components/hex-triad/HexTriadLayer.vue +28 -0
  39. package/components/horiz-card/HorizCard.vue +318 -0
  40. package/components/horiz-card/HorizCardBadge.vue +75 -0
  41. package/components/horiz-card/HorizCardContent.vue +46 -0
  42. package/components/horiz-card/HorizCardHeading.vue +47 -0
  43. package/components/horiz-card/HorizCardIcon.vue +118 -0
  44. package/components/quad-hub/QuadHub.vue +642 -0
  45. package/components/quad-hub/QuadHubCallout.vue +267 -0
  46. package/components/quad-hub/QuadHubCenter.vue +53 -0
  47. package/components/quad-hub/QuadHubContent.vue +39 -0
  48. package/components/quad-hub/QuadHubHeading.vue +44 -0
  49. package/components/quad-hub/QuadHubIcon.vue +112 -0
  50. package/components/shifting-heading/TransitionHeading.vue +2 -2
  51. package/components/vert-card/VertCard.vue +389 -0
  52. package/components/vert-card/VertCardBadge.vue +72 -0
  53. package/components/vert-card/VertCardContent.vue +45 -0
  54. package/components/vert-card/VertCardHeading.vue +68 -0
  55. package/components/vert-card/VertCardIcon.vue +126 -0
  56. package/components/vert-card/VertCardTitle.vue +76 -0
  57. package/layouts/core/arc-toc.vue +76 -113
  58. package/layouts/core/bg-center.vue +32 -4
  59. package/layouts/shifting-heading/shifting-intro.vue +35 -7
  60. package/package.json +3 -2
  61. package/utils/arcArrowProcess/context.ts +28 -0
  62. package/utils/arcArrowProcess/createArcArrowProcessGeometry.ts +137 -0
  63. package/utils/arcArrowProcess/index.ts +3 -0
  64. package/utils/arcArrowProcess/resolveArcArrowProcessItems.ts +189 -0
  65. package/utils/arcCompare/context.ts +36 -0
  66. package/utils/arcCompare/createArcCompareGeometry.ts +254 -0
  67. package/utils/arcCompare/index.ts +3 -0
  68. package/utils/arcCompare/resolveArcCompareItems.ts +123 -0
  69. package/utils/arrowTriad/context.ts +38 -0
  70. package/utils/arrowTriad/createArrowTriadGeometry.ts +63 -0
  71. package/utils/arrowTriad/index.ts +3 -0
  72. package/utils/arrowTriad/resolveArrowTriadItems.ts +121 -0
  73. package/utils/chevronCard/context.ts +12 -0
  74. package/utils/chevronCard/createChevronCardGeometry.ts +48 -0
  75. package/utils/chevronCard/index.ts +2 -0
  76. package/utils/comparisonTable/constants.ts +72 -0
  77. package/utils/comparisonTable/context.ts +24 -0
  78. package/utils/comparisonTable/index.ts +2 -0
  79. package/utils/hexTriad/context.ts +25 -0
  80. package/utils/hexTriad/createHexTriadGeometry.ts +182 -0
  81. package/utils/hexTriad/index.ts +3 -0
  82. package/utils/hexTriad/resolveHexTriadItems.ts +63 -0
  83. package/utils/horizCard/context.ts +13 -0
  84. package/utils/horizCard/createHorizCardGeometry.ts +243 -0
  85. package/utils/horizCard/index.ts +2 -0
  86. package/utils/quadHub/context.ts +26 -0
  87. package/utils/quadHub/createQuadHubGeometry.ts +331 -0
  88. package/utils/quadHub/index.ts +3 -0
  89. package/utils/quadHub/resolveQuadHubItems.ts +214 -0
  90. package/utils/useDiagramAutoScale.ts +300 -0
  91. package/utils/vertCard/context.ts +18 -0
  92. package/utils/vertCard/createVertCardGeometry.ts +242 -0
  93. package/utils/vertCard/index.ts +2 -0
@@ -0,0 +1,300 @@
1
+ import {
2
+ type ComputedRef,
3
+ computed,
4
+ onMounted,
5
+ onUnmounted,
6
+ type Ref,
7
+ ref,
8
+ } from 'vue';
9
+
10
+ export interface IUseDiagramAutoScaleOptions {
11
+ readonly rootRef: Ref<HTMLElement | null>;
12
+ readonly baseWidth: number;
13
+ readonly baseHeight: number;
14
+ readonly scale?: Ref<number | undefined> | (() => number | undefined);
15
+ readonly autoScale?: Ref<boolean | undefined> | (() => boolean | undefined);
16
+ readonly maxScale?: Ref<number | undefined> | (() => number | undefined);
17
+ readonly height?:
18
+ | Ref<number | string | undefined>
19
+ | (() => number | string | undefined);
20
+ readonly pagyClearance?: number;
21
+ }
22
+
23
+ export interface IUseDiagramAutoScaleResult {
24
+ readonly resolvedScale: ComputedRef<number>;
25
+ readonly autoScaleValue: Ref<number>;
26
+ readonly containerHeight: Ref<number>;
27
+ readonly updateScale: () => void;
28
+ }
29
+
30
+ function resolveValue<T>(val: Ref<T> | (() => T) | undefined, fallback: T): T {
31
+ if (typeof val === 'function') {
32
+ const res = (val as () => T)();
33
+
34
+ return res !== undefined ? res : fallback;
35
+ }
36
+
37
+ if (val !== undefined && val.value !== undefined) {
38
+ return val.value;
39
+ }
40
+
41
+ return fallback;
42
+ }
43
+
44
+ /**
45
+ * Automatically computes a scale factor (via CSS transform: scale)
46
+ * so that diagram components (ArrowTriad, QuadHub, ArcArrowProcess, etc.)
47
+ * fit comfortably inside the available slide canvas without clipping or overlaying pagination.
48
+ */
49
+ export function useDiagramAutoScale(
50
+ options: IUseDiagramAutoScaleOptions,
51
+ ): IUseDiagramAutoScaleResult {
52
+ const { rootRef, baseWidth, baseHeight, pagyClearance = 32 } = options;
53
+
54
+ const autoScaleValue = ref<number>(1);
55
+ const containerHeight = ref<number>(0);
56
+
57
+ const updateScale = () => {
58
+ if (typeof window === 'undefined' || !rootRef.value) {
59
+ return;
60
+ }
61
+
62
+ const el = rootRef.value;
63
+ const parent = el.parentElement;
64
+
65
+ if (!parent) {
66
+ return;
67
+ }
68
+
69
+ // 1. Locate slide container if inside Slidev
70
+ const pageEl = el.closest<HTMLElement>(
71
+ '.slidev-page, [data-slidev-page], .slidev-slide-content',
72
+ );
73
+ const layoutEl = el.closest<HTMLElement>('.slidev-layout');
74
+ const slideEl = pageEl || layoutEl;
75
+
76
+ // Immediate parent padding
77
+ const parentStyle = window.getComputedStyle(parent);
78
+ const padLeft = Number.parseFloat(parentStyle.paddingLeft) || 0;
79
+ const padRight = Number.parseFloat(parentStyle.paddingRight) || 0;
80
+ const padTop = Number.parseFloat(parentStyle.paddingTop) || 0;
81
+ const padBottom = Number.parseFloat(parentStyle.paddingBottom) || 0;
82
+
83
+ // Available width inside parent or slide layout
84
+ let availW = 0;
85
+
86
+ if (parent.clientWidth > 0) {
87
+ availW = parent.clientWidth - padLeft - padRight;
88
+ } else if (slideEl && slideEl.clientWidth > 0) {
89
+ const slideStyle = window.getComputedStyle(slideEl);
90
+ const sPadLeft = Number.parseFloat(slideStyle.paddingLeft) || 0;
91
+ const sPadRight = Number.parseFloat(slideStyle.paddingRight) || 0;
92
+ availW = slideEl.clientWidth - sPadLeft - sPadRight;
93
+ } else if (el.clientWidth > 0) {
94
+ availW = el.clientWidth;
95
+ }
96
+
97
+ // Available height inside parent or slide layout
98
+ let availH = 0;
99
+ const explicitHeight = resolveValue(options.height, undefined);
100
+
101
+ if (typeof explicitHeight === 'number') {
102
+ availH = explicitHeight;
103
+ } else if (
104
+ typeof explicitHeight === 'string' &&
105
+ explicitHeight.endsWith('px')
106
+ ) {
107
+ availH = Number.parseFloat(explicitHeight);
108
+ }
109
+
110
+ if (!availH) {
111
+ // Calculate sibling heights inside parent (e.g. title, headers)
112
+ let siblingsH = 0;
113
+
114
+ for (let i = 0; i < parent.children.length; i++) {
115
+ const child = parent.children[i];
116
+
117
+ if (child && child !== el && child instanceof HTMLElement) {
118
+ siblingsH += child.offsetHeight;
119
+ const cs = window.getComputedStyle(child);
120
+ siblingsH +=
121
+ (Number.parseFloat(cs.marginTop) || 0) +
122
+ (Number.parseFloat(cs.marginBottom) || 0);
123
+ }
124
+ }
125
+
126
+ // Check if pagination element is present and visible
127
+ const pagyEl =
128
+ pageEl?.querySelector<HTMLElement>(
129
+ '.alpha-academic-pagy, .slidev-page-nav, [aria-label="Slide pagination"]',
130
+ ) ||
131
+ slideEl?.querySelector<HTMLElement>(
132
+ '.alpha-academic-pagy, .slidev-page-nav, [aria-label="Slide pagination"]',
133
+ ) ||
134
+ (typeof document !== 'undefined'
135
+ ? document.querySelector<HTMLElement>(
136
+ '.alpha-academic-pagy, .slidev-page-nav',
137
+ )
138
+ : null);
139
+ const isPagyVisible =
140
+ pagyEl && window.getComputedStyle(pagyEl).display !== 'none';
141
+ const clearance = isPagyVisible ? pagyClearance : 16;
142
+
143
+ if (parent.clientHeight > 0 && parent !== slideEl) {
144
+ availH = Math.max(
145
+ 0,
146
+ parent.clientHeight - padTop - padBottom - siblingsH - clearance,
147
+ );
148
+ }
149
+
150
+ if (availH <= 0 && slideEl && slideEl.clientHeight > 0) {
151
+ const slideStyle = window.getComputedStyle(slideEl);
152
+ const sPadTop = Number.parseFloat(slideStyle.paddingTop) || 0;
153
+ const sPadBottom = Number.parseFloat(slideStyle.paddingBottom) || 0;
154
+
155
+ let slideSiblingsH = siblingsH;
156
+
157
+ if (parent !== slideEl) {
158
+ for (let i = 0; i < slideEl.children.length; i++) {
159
+ const child = slideEl.children[i];
160
+
161
+ if (
162
+ child &&
163
+ child !== parent &&
164
+ !child.contains(parent) &&
165
+ child instanceof HTMLElement
166
+ ) {
167
+ slideSiblingsH += child.offsetHeight;
168
+ const cs = window.getComputedStyle(child);
169
+ slideSiblingsH +=
170
+ (Number.parseFloat(cs.marginTop) || 0) +
171
+ (Number.parseFloat(cs.marginBottom) || 0);
172
+ }
173
+ }
174
+ }
175
+
176
+ availH = Math.max(
177
+ 0,
178
+ slideEl.clientHeight -
179
+ sPadTop -
180
+ sPadBottom -
181
+ slideSiblingsH -
182
+ clearance,
183
+ );
184
+ }
185
+ }
186
+
187
+ if (availH > 0 && Math.abs(availH - containerHeight.value) > 1) {
188
+ containerHeight.value = Math.round(availH);
189
+ }
190
+
191
+ if (availW <= 0) {
192
+ availW = baseWidth;
193
+ }
194
+
195
+ let targetScale = 1;
196
+
197
+ if (availW > 0 && availH > 0) {
198
+ const scaleX = availW / baseWidth;
199
+ const scaleY = availH / baseHeight;
200
+ targetScale = Math.min(scaleX, scaleY);
201
+ } else if (availW > 0) {
202
+ targetScale = availW / baseWidth;
203
+ }
204
+
205
+ const maxScaleProp = resolveValue(options.maxScale, undefined);
206
+
207
+ if (maxScaleProp !== undefined) {
208
+ targetScale = Math.min(targetScale, maxScaleProp);
209
+ }
210
+
211
+ targetScale = Math.round(targetScale * 1000) / 1000;
212
+
213
+ if (Math.abs(targetScale - autoScaleValue.value) > 0.005) {
214
+ autoScaleValue.value = targetScale;
215
+ }
216
+ };
217
+
218
+ let observer: ResizeObserver | null = null;
219
+
220
+ onMounted(() => {
221
+ if (typeof window === 'undefined' || !rootRef.value) {
222
+ return;
223
+ }
224
+
225
+ updateScale();
226
+ requestAnimationFrame(() => {
227
+ updateScale();
228
+ });
229
+
230
+ if (typeof ResizeObserver !== 'undefined') {
231
+ observer = new ResizeObserver(() => {
232
+ updateScale();
233
+ });
234
+
235
+ observer.observe(rootRef.value);
236
+
237
+ if (rootRef.value.parentElement) {
238
+ observer.observe(rootRef.value.parentElement);
239
+ }
240
+
241
+ const pageEl = rootRef.value.closest<HTMLElement>(
242
+ '.slidev-page, [data-slidev-page], .slidev-slide-content',
243
+ );
244
+ const layoutEl = rootRef.value.closest<HTMLElement>('.slidev-layout');
245
+ const slideEl = pageEl || layoutEl;
246
+
247
+ if (slideEl && slideEl !== rootRef.value.parentElement) {
248
+ observer.observe(slideEl);
249
+ }
250
+
251
+ if (
252
+ pageEl &&
253
+ pageEl !== slideEl &&
254
+ pageEl !== rootRef.value.parentElement
255
+ ) {
256
+ observer.observe(pageEl);
257
+ }
258
+ } else {
259
+ window.addEventListener('resize', updateScale);
260
+ }
261
+ });
262
+
263
+ onUnmounted(() => {
264
+ if (observer) {
265
+ observer.disconnect();
266
+ observer = null;
267
+ } else if (typeof window !== 'undefined') {
268
+ window.removeEventListener('resize', updateScale);
269
+ }
270
+ });
271
+
272
+ const resolvedScale = computed((): number => {
273
+ const scaleProp = resolveValue(options.scale, undefined);
274
+
275
+ if (scaleProp !== undefined) {
276
+ return scaleProp;
277
+ }
278
+
279
+ const autoScaleProp = resolveValue(options.autoScale, true);
280
+
281
+ if (autoScaleProp === false) {
282
+ return 1;
283
+ }
284
+
285
+ const maxScaleProp = resolveValue(options.maxScale, undefined);
286
+
287
+ if (maxScaleProp !== undefined) {
288
+ return Math.min(autoScaleValue.value, maxScaleProp);
289
+ }
290
+
291
+ return autoScaleValue.value;
292
+ });
293
+
294
+ return {
295
+ resolvedScale,
296
+ autoScaleValue,
297
+ containerHeight,
298
+ updateScale,
299
+ };
300
+ }
@@ -0,0 +1,18 @@
1
+ import type { ComputedRef, InjectionKey } from 'vue';
2
+
3
+ export type TVertCardBadgeVariant = 'outside' | 'inside' | 'none' | 'hidden';
4
+
5
+ export interface IVertCardContext {
6
+ readonly color: ComputedRef<string>;
7
+ readonly cardBg: ComputedRef<string>;
8
+ readonly lightColor: ComputedRef<string>;
9
+ readonly titleColor: ComputedRef<string>;
10
+ readonly stripeColor: ComputedRef<string>;
11
+ readonly hasDivider?: ComputedRef<boolean>;
12
+ readonly step: ComputedRef<string | number>;
13
+ readonly textColor: ComputedRef<string | undefined>;
14
+ readonly badgeVariant: ComputedRef<TVertCardBadgeVariant>;
15
+ }
16
+
17
+ export const VERT_CARD_KEY: InjectionKey<IVertCardContext> =
18
+ Symbol('VERT_CARD');
@@ -0,0 +1,242 @@
1
+ export interface ICreateVertCardPathParams {
2
+ readonly width: number;
3
+ readonly height: number;
4
+ readonly borderRadius?: number;
5
+ }
6
+
7
+ /**
8
+ * Generates an SVG path for the main VertCard body.
9
+ * Features rounded corners on all four sides.
10
+ */
11
+ export function createVertCardPath({
12
+ width,
13
+ height,
14
+ borderRadius = 24,
15
+ }: ICreateVertCardPathParams): string {
16
+ const safeR = Math.max(0, Math.min(borderRadius, width / 2, height / 2));
17
+
18
+ return [
19
+ `M ${safeR} 0`,
20
+ `H ${width - safeR}`,
21
+ `A ${safeR} ${safeR} 0 0 1 ${width} ${safeR}`,
22
+ `V ${height - safeR}`,
23
+ `A ${safeR} ${safeR} 0 0 1 ${width - safeR} ${height}`,
24
+ `H ${safeR}`,
25
+ `A ${safeR} ${safeR} 0 0 1 0 ${height - safeR}`,
26
+ `V ${safeR}`,
27
+ `A ${safeR} ${safeR} 0 0 1 ${safeR} 0`,
28
+ 'Z',
29
+ ].join(' ');
30
+ }
31
+
32
+ export interface ICreateVertCardBottomBlobPathParams {
33
+ readonly width: number;
34
+ readonly height: number;
35
+ readonly blobWidth?: number;
36
+ readonly blobHeight?: number;
37
+ readonly borderRadius?: number;
38
+ }
39
+
40
+ /**
41
+ * Generates an SVG path for the organic accent blob/wave at the bottom-left corner.
42
+ * Curves down smoothly from the left edge into the bottom horizontal edge,
43
+ * adhering to the bottom-left rounded corner of the card.
44
+ */
45
+ export function createVertCardBottomBlobPath({
46
+ width,
47
+ height,
48
+ borderRadius = 24,
49
+ blobWidth = Math.round(width * 0.45),
50
+ blobHeight = Math.round(height * 0.24),
51
+ }: ICreateVertCardBottomBlobPathParams): string {
52
+ const safeR = Math.max(0, Math.min(borderRadius, width / 2, height / 2));
53
+ const startY = height - blobHeight;
54
+ const endX = blobWidth;
55
+
56
+ const cp1X = Math.round(blobWidth * 0.08);
57
+ const cp1Y = Math.round(startY + blobHeight * 0.38);
58
+ const cp2X = Math.round(blobWidth * 0.46);
59
+ const cp2Y = Math.round(height - blobHeight * 0.06);
60
+
61
+ return [
62
+ `M 0 ${startY}`,
63
+ `C ${cp1X} ${cp1Y}, ${cp2X} ${cp2Y}, ${endX} ${height}`,
64
+ `H ${safeR}`,
65
+ `A ${safeR} ${safeR} 0 0 1 0 ${height - safeR}`,
66
+ `V ${startY}`,
67
+ 'Z',
68
+ ].join(' ');
69
+ }
70
+
71
+ export interface ICreateVertCardTopRightBlobPathParams {
72
+ readonly width: number;
73
+ readonly height: number;
74
+ readonly blobWidth?: number;
75
+ readonly blobHeight?: number;
76
+ readonly borderRadius?: number;
77
+ }
78
+
79
+ /**
80
+ * Generates an SVG path for the organic accent blob/wave at the top-right corner.
81
+ * Symmetrical counterpart (180° rotation) to the bottom-left organic blob.
82
+ */
83
+ export function createVertCardTopRightBlobPath({
84
+ width,
85
+ height,
86
+ borderRadius = 24,
87
+ blobWidth = Math.round(width * 0.45),
88
+ blobHeight = Math.round(height * 0.24),
89
+ }: ICreateVertCardTopRightBlobPathParams): string {
90
+ const safeR = Math.max(0, Math.min(borderRadius, width / 2, height / 2));
91
+ const startX = width - blobWidth;
92
+ const endY = blobHeight;
93
+
94
+ const cp1X = Math.round(width - blobWidth * 0.46);
95
+ const cp1Y = Math.round(blobHeight * 0.06);
96
+ const cp2X = Math.round(width - blobWidth * 0.08);
97
+ const cp2Y = Math.round(blobHeight * 0.62);
98
+
99
+ return [
100
+ `M ${startX} 0`,
101
+ `C ${cp1X} ${cp1Y}, ${cp2X} ${cp2Y}, ${width} ${endY}`,
102
+ `V ${safeR}`,
103
+ `A ${safeR} ${safeR} 0 0 0 ${width - safeR} 0`,
104
+ `H ${startX}`,
105
+ 'Z',
106
+ ].join(' ');
107
+ }
108
+
109
+ export interface ICreateVertCardTopRightOuterStripePathParams {
110
+ readonly width: number;
111
+ readonly outerOffset?: number;
112
+ }
113
+
114
+ /**
115
+ * Generates an SVG path for the top-right diagonal outer accent band.
116
+ */
117
+ export function createVertCardTopRightOuterStripePath({
118
+ width,
119
+ outerOffset = 72,
120
+ }: ICreateVertCardTopRightOuterStripePathParams): string {
121
+ return [
122
+ `M ${width - outerOffset} 0`,
123
+ `L ${width} ${outerOffset}`,
124
+ `L ${width} 0`,
125
+ 'Z',
126
+ ].join(' ');
127
+ }
128
+
129
+ export interface ICreateVertCardTopRightInnerStripePathParams {
130
+ readonly width: number;
131
+ readonly outerOffset?: number;
132
+ readonly stripeWidth?: number;
133
+ }
134
+
135
+ /**
136
+ * Generates an SVG path for the top-right diagonal inner accent band.
137
+ */
138
+ export function createVertCardTopRightInnerStripePath({
139
+ width,
140
+ outerOffset = 72,
141
+ stripeWidth = 24,
142
+ }: ICreateVertCardTopRightInnerStripePathParams): string {
143
+ const innerOffset = outerOffset + stripeWidth;
144
+
145
+ return [
146
+ `M ${width - innerOffset} 0`,
147
+ `L ${width} ${innerOffset}`,
148
+ `L ${width} ${outerOffset}`,
149
+ `L ${width - outerOffset} 0`,
150
+ 'Z',
151
+ ].join(' ');
152
+ }
153
+
154
+ export interface ICreateVertCardBadgePathParams {
155
+ readonly width?: number;
156
+ readonly height?: number;
157
+ readonly tipRadius?: number;
158
+ readonly sideWidth?: number;
159
+ readonly leftRadius?: number;
160
+ readonly cornerRadius?: number;
161
+ }
162
+
163
+ /**
164
+ * Generates an SVG path for the left ribbon step badge pointing to the RIGHT.
165
+ * Features rounded corners on the left edge, smooth transition fillets,
166
+ * and a rounded arrow tip pointing into the card.
167
+ */
168
+ export function createVertCardBadgePath({
169
+ width = 62,
170
+ height = 48,
171
+ leftRadius = 8,
172
+ tipRadius = 6,
173
+ sideWidth = 42,
174
+ cornerRadius = 6,
175
+ }: ICreateVertCardBadgePathParams = {}): string {
176
+ const halfH = height / 2;
177
+ const dx = width - sideWidth;
178
+ const dy = halfH;
179
+ const len = Math.hypot(dx, dy);
180
+ const ux = len > 0 ? dx / len : 0;
181
+ const uy = len > 0 ? dy / len : 0;
182
+
183
+ return [
184
+ `M ${leftRadius} 0`,
185
+ `H ${(sideWidth - cornerRadius).toFixed(2)}`,
186
+ `Q ${sideWidth} 0 ${(sideWidth + cornerRadius * ux).toFixed(2)} ${(cornerRadius * uy).toFixed(2)}`,
187
+ `L ${(width - tipRadius * ux).toFixed(2)} ${(halfH - tipRadius * uy).toFixed(2)}`,
188
+ `Q ${width} ${halfH} ${(width - tipRadius * ux).toFixed(2)} ${(halfH + tipRadius * uy).toFixed(2)}`,
189
+ `L ${(sideWidth + cornerRadius * ux).toFixed(2)} ${(height - cornerRadius * uy).toFixed(2)}`,
190
+ `Q ${sideWidth} ${height} ${(sideWidth - cornerRadius).toFixed(2)} ${height}`,
191
+ `H ${leftRadius}`,
192
+ `A ${leftRadius} ${leftRadius} 0 0 1 0 ${height - leftRadius}`,
193
+ `V ${leftRadius}`,
194
+ `A ${leftRadius} ${leftRadius} 0 0 1 ${leftRadius} 0`,
195
+ 'Z',
196
+ ].join(' ');
197
+ }
198
+
199
+ export interface IHexToRgbaParams {
200
+ readonly hex: string;
201
+ readonly alpha?: number;
202
+ }
203
+
204
+ /**
205
+ * Converts a hex color string to an RGBA string with the given alpha transparency.
206
+ */
207
+ export function hexToRgba({ hex, alpha = 0.16 }: IHexToRgbaParams): string {
208
+ const trimmed = hex.trim();
209
+
210
+ if (
211
+ trimmed.startsWith('var(') ||
212
+ trimmed.startsWith('rgb') ||
213
+ trimmed.startsWith('hsl')
214
+ ) {
215
+ return `color-mix(in srgb, ${trimmed} ${Math.round(alpha * 100)}%, transparent)`;
216
+ }
217
+
218
+ let sanitized = trimmed.replace(/^#/, '');
219
+
220
+ if (sanitized.length === 3) {
221
+ sanitized = sanitized
222
+ .split('')
223
+ .map((char) => {
224
+ return char + char;
225
+ })
226
+ .join('');
227
+ }
228
+
229
+ if (sanitized.length !== 6) {
230
+ return hex;
231
+ }
232
+
233
+ const r = Number.parseInt(sanitized.slice(0, 2), 16);
234
+ const g = Number.parseInt(sanitized.slice(2, 4), 16);
235
+ const b = Number.parseInt(sanitized.slice(4, 6), 16);
236
+
237
+ if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
238
+ return hex;
239
+ }
240
+
241
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
242
+ }
@@ -0,0 +1,2 @@
1
+ export * from './context';
2
+ export * from './createVertCardGeometry';