@hyvor/design 2.1.9 → 2.1.11
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/dist/cloud/HyvorBar/BarSupport.svelte +1 -1
- package/dist/components/ColorPicker/ColorPicker.svelte +68 -9
- package/dist/components/ColorPicker/ColorPicker.svelte.d.ts +3 -0
- package/dist/components/Confetti/confetti.d.ts +7 -0
- package/dist/components/Confetti/confetti.js +102 -0
- package/dist/components/NavLink/NavLinkGroup.svelte +1 -0
- package/dist/index.css +4 -0
- package/dist/marketing/AllFeaturesAccordion/AllFeaturesAccordion.svelte +251 -0
- package/dist/marketing/AllFeaturesAccordion/AllFeaturesAccordion.svelte.d.ts +23 -0
- package/dist/marketing/AllFeaturesAccordion/AllFeaturesAccordionFeature.svelte +70 -0
- package/dist/marketing/AllFeaturesAccordion/AllFeaturesAccordionFeature.svelte.d.ts +10 -0
- package/dist/marketing/FAQ/FAQ.svelte +227 -0
- package/dist/marketing/FAQ/FAQ.svelte.d.ts +13 -0
- package/dist/marketing/FeatureSplit/FeatureSplit.svelte +325 -0
- package/dist/marketing/FeatureSplit/FeatureSplit.svelte.d.ts +24 -0
- package/dist/marketing/Footer/Footer.svelte +395 -98
- package/dist/marketing/Footer/Footer.svelte.d.ts +18 -2
- package/dist/marketing/Footer/FooterLinkList.svelte +10 -3
- package/dist/marketing/FullTrialSignup/FullTrialSignup.svelte +164 -0
- package/dist/marketing/FullTrialSignup/FullTrialSignup.svelte.d.ts +18 -0
- package/dist/marketing/Header/Header.svelte +72 -28
- package/dist/marketing/Header/Header.svelte.d.ts +5 -2
- package/dist/marketing/Header/HeaderLanguageToggle.svelte +119 -0
- package/dist/marketing/Header/HeaderLanguageToggle.svelte.d.ts +14 -0
- package/dist/marketing/Header/HeaderNavLink.svelte +149 -0
- package/dist/marketing/Header/HeaderNavLink.svelte.d.ts +18 -0
- package/dist/marketing/Header/language.d.ts +16 -0
- package/dist/marketing/Header/language.js +28 -0
- package/dist/marketing/Hero/Hero.svelte +349 -0
- package/dist/marketing/Hero/Hero.svelte.d.ts +16 -0
- package/dist/marketing/LogoStrip/LogoStrip.svelte +199 -0
- package/dist/marketing/LogoStrip/LogoStrip.svelte.d.ts +19 -0
- package/dist/marketing/Seal/CcpaSeal.svelte +29 -0
- package/dist/marketing/Seal/CcpaSeal.svelte.d.ts +6 -0
- package/dist/marketing/Seal/GdprSeal.svelte +60 -0
- package/dist/marketing/Seal/GdprSeal.svelte.d.ts +6 -0
- package/dist/marketing/Seal/IsoSeal.svelte +44 -0
- package/dist/marketing/Seal/IsoSeal.svelte.d.ts +6 -0
- package/dist/marketing/Seal/Seal.svelte +62 -0
- package/dist/marketing/Seal/Seal.svelte.d.ts +8 -0
- package/dist/marketing/Seal/SsoSeal.svelte +31 -0
- package/dist/marketing/Seal/SsoSeal.svelte.d.ts +6 -0
- package/dist/marketing/Seal/ccpa.svg +131 -0
- package/dist/marketing/SpotlightSplit/SpotlightSplit.svelte +256 -0
- package/dist/marketing/SpotlightSplit/SpotlightSplit.svelte.d.ts +19 -0
- package/dist/marketing/Testimonials/Testimonials.svelte +441 -0
- package/dist/marketing/Testimonials/Testimonials.svelte.d.ts +24 -0
- package/dist/marketing/index.d.ts +20 -2
- package/dist/marketing/index.js +26 -2
- package/dist/marketing/social.d.ts +1 -1
- package/dist/marketing/social.js +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import ColorPicker from 'svelte-awesome-color-picker';
|
|
3
3
|
import { clickOutside } from '../index.js';
|
|
4
4
|
import { createEventDispatcher } from 'svelte';
|
|
5
|
+
import type { DropdownAlign, DropdownPosition } from '../Dropdown/dropdown.types.js';
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
color?: string;
|
|
@@ -9,6 +10,8 @@
|
|
|
9
10
|
show?: boolean;
|
|
10
11
|
'aria-label'?: string;
|
|
11
12
|
alpha?: boolean;
|
|
13
|
+
align?: DropdownAlign;
|
|
14
|
+
position?: DropdownPosition;
|
|
12
15
|
oninput?: (color: string) => void;
|
|
13
16
|
onchange?: (color: string) => void;
|
|
14
17
|
}
|
|
@@ -19,6 +22,8 @@
|
|
|
19
22
|
show = $bindable(false),
|
|
20
23
|
'aria-label': ariaLabel = '',
|
|
21
24
|
alpha = false,
|
|
25
|
+
align = 'start',
|
|
26
|
+
position = 'bottom',
|
|
22
27
|
oninput,
|
|
23
28
|
onchange
|
|
24
29
|
}: Props = $props();
|
|
@@ -29,25 +34,79 @@
|
|
|
29
34
|
}>();
|
|
30
35
|
|
|
31
36
|
let buttonElement: HTMLButtonElement;
|
|
37
|
+
let wrapElement: HTMLElement | undefined = $state();
|
|
32
38
|
let pickerTop = $state(0);
|
|
33
39
|
let pickerLeft = $state(0);
|
|
34
40
|
|
|
41
|
+
const GAP = 5;
|
|
42
|
+
const SPACE_AROUND = 15;
|
|
43
|
+
|
|
35
44
|
function updatePosition() {
|
|
36
|
-
if (buttonElement)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
if (!buttonElement || !wrapElement) return;
|
|
46
|
+
|
|
47
|
+
const triggerRect = buttonElement.getBoundingClientRect();
|
|
48
|
+
const contentRect = wrapElement.getBoundingClientRect();
|
|
49
|
+
const documentWidth = document.documentElement.clientWidth;
|
|
50
|
+
const width = contentRect.width;
|
|
51
|
+
const height = contentRect.height;
|
|
52
|
+
|
|
53
|
+
let top = triggerRect.bottom + GAP;
|
|
54
|
+
let left = triggerRect.left;
|
|
55
|
+
|
|
56
|
+
if (position === 'bottom') {
|
|
57
|
+
top = triggerRect.bottom + GAP;
|
|
58
|
+
} else if (position === 'top') {
|
|
59
|
+
top = triggerRect.top - height - GAP;
|
|
60
|
+
} else if (position === 'left') {
|
|
61
|
+
left = triggerRect.left - width - GAP;
|
|
62
|
+
} else if (position === 'right') {
|
|
63
|
+
left = triggerRect.right + GAP;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (position === 'bottom' || position === 'top') {
|
|
67
|
+
if (align === 'start') {
|
|
68
|
+
left = Math.min(triggerRect.left, documentWidth - width - SPACE_AROUND);
|
|
69
|
+
} else if (align === 'center') {
|
|
70
|
+
left = triggerRect.left + triggerRect.width / 2 - width / 2;
|
|
71
|
+
} else if (align === 'end') {
|
|
72
|
+
left = triggerRect.right - width;
|
|
73
|
+
}
|
|
74
|
+
left = Math.max(left, SPACE_AROUND);
|
|
75
|
+
} else {
|
|
76
|
+
if (align === 'start') {
|
|
77
|
+
top = triggerRect.top;
|
|
78
|
+
} else if (align === 'center') {
|
|
79
|
+
top = triggerRect.top + triggerRect.height / 2 - height / 2;
|
|
80
|
+
} else if (align === 'end') {
|
|
81
|
+
top = triggerRect.bottom - height;
|
|
82
|
+
}
|
|
83
|
+
top = Math.max(top, SPACE_AROUND);
|
|
40
84
|
}
|
|
85
|
+
|
|
86
|
+
pickerTop = top;
|
|
87
|
+
pickerLeft = left;
|
|
41
88
|
}
|
|
42
89
|
|
|
43
90
|
$effect(() => {
|
|
44
|
-
|
|
91
|
+
// rerun when positioning inputs change
|
|
92
|
+
position;
|
|
93
|
+
align;
|
|
94
|
+
show;
|
|
95
|
+
|
|
96
|
+
if (show && wrapElement) {
|
|
45
97
|
updatePosition();
|
|
46
98
|
window.addEventListener('scroll', updatePosition, true);
|
|
47
99
|
window.addEventListener('resize', updatePosition);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
100
|
+
|
|
101
|
+
// the color picker widget can change size after it mounts
|
|
102
|
+
const observer = new MutationObserver(updatePosition);
|
|
103
|
+
observer.observe(wrapElement, { subtree: true, childList: true });
|
|
104
|
+
|
|
105
|
+
return () => {
|
|
106
|
+
observer.disconnect();
|
|
107
|
+
window.removeEventListener('scroll', updatePosition, true);
|
|
108
|
+
window.removeEventListener('resize', updatePosition);
|
|
109
|
+
};
|
|
51
110
|
}
|
|
52
111
|
|
|
53
112
|
return () => {
|
|
@@ -87,6 +146,7 @@
|
|
|
87
146
|
{#if show}
|
|
88
147
|
<div
|
|
89
148
|
class="color-picker-wrap"
|
|
149
|
+
bind:this={wrapElement}
|
|
90
150
|
style:top="{pickerTop}px"
|
|
91
151
|
style:left="{pickerLeft}px"
|
|
92
152
|
use:clickOutside={{
|
|
@@ -114,7 +174,6 @@
|
|
|
114
174
|
}
|
|
115
175
|
div {
|
|
116
176
|
position: fixed;
|
|
117
|
-
width: 0;
|
|
118
177
|
z-index: 1000;
|
|
119
178
|
}
|
|
120
179
|
</style>
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import ColorPicker from 'svelte-awesome-color-picker';
|
|
2
|
+
import type { DropdownAlign, DropdownPosition } from '../Dropdown/dropdown.types.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
color?: string;
|
|
4
5
|
size?: number;
|
|
5
6
|
show?: boolean;
|
|
6
7
|
'aria-label'?: string;
|
|
7
8
|
alpha?: boolean;
|
|
9
|
+
align?: DropdownAlign;
|
|
10
|
+
position?: DropdownPosition;
|
|
8
11
|
oninput?: (color: string) => void;
|
|
9
12
|
onchange?: (color: string) => void;
|
|
10
13
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export function confetti(options = {}) {
|
|
2
|
+
const { particleCount = 220, duration = 12000, zIndex = 20000000 } = options;
|
|
3
|
+
const gravity = 0.18;
|
|
4
|
+
const drag = 0.96;
|
|
5
|
+
const canvas = document.createElement('canvas');
|
|
6
|
+
canvas.style.position = 'fixed';
|
|
7
|
+
canvas.style.top = '0';
|
|
8
|
+
canvas.style.left = '0';
|
|
9
|
+
canvas.style.width = '100vw';
|
|
10
|
+
canvas.style.height = '100vh';
|
|
11
|
+
canvas.style.pointerEvents = 'none';
|
|
12
|
+
canvas.style.zIndex = String(zIndex);
|
|
13
|
+
document.body.appendChild(canvas);
|
|
14
|
+
const ctx = canvas.getContext('2d');
|
|
15
|
+
canvas.width = window.innerWidth;
|
|
16
|
+
canvas.height = window.innerHeight;
|
|
17
|
+
const colors = ['#ffba6b', '#8cdaff', '#b5ffb0', '#ff9090', '#ffbfff', '#d894ff', '#fff79b'];
|
|
18
|
+
const particles = [];
|
|
19
|
+
let animationFrameId;
|
|
20
|
+
let timeoutId = null;
|
|
21
|
+
let running = true;
|
|
22
|
+
const originX = canvas.width / 2;
|
|
23
|
+
const originY = canvas.height / 2;
|
|
24
|
+
const createParticle = () => {
|
|
25
|
+
const angle = Math.random() * Math.PI * 2;
|
|
26
|
+
const speed = Math.random() * 16 + 10;
|
|
27
|
+
return {
|
|
28
|
+
x: originX + (Math.random() - 0.5) * 40,
|
|
29
|
+
y: originY + (Math.random() - 0.5) * 40,
|
|
30
|
+
size: Math.random() * 9 + 5,
|
|
31
|
+
color: colors[Math.floor(Math.random() * colors.length)],
|
|
32
|
+
velocityX: Math.cos(angle) * speed,
|
|
33
|
+
velocityY: Math.sin(angle) * speed,
|
|
34
|
+
rotation: Math.random() * 360,
|
|
35
|
+
rotationSpeed: Math.random() * 12 - 6,
|
|
36
|
+
wobble: Math.random() * Math.PI * 2,
|
|
37
|
+
wobbleSpeed: Math.random() * 0.1 + 0.05
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const drawParticle = (particle) => {
|
|
41
|
+
ctx.save();
|
|
42
|
+
ctx.translate(particle.x, particle.y);
|
|
43
|
+
ctx.rotate((particle.rotation * Math.PI) / 180);
|
|
44
|
+
ctx.fillStyle = particle.color;
|
|
45
|
+
ctx.beginPath();
|
|
46
|
+
ctx.roundRect(-particle.size / 2, -particle.size / 2, particle.size, particle.size, 1);
|
|
47
|
+
ctx.fill();
|
|
48
|
+
ctx.restore();
|
|
49
|
+
};
|
|
50
|
+
const updateParticles = () => {
|
|
51
|
+
for (const particle of particles) {
|
|
52
|
+
particle.velocityX *= drag;
|
|
53
|
+
particle.velocityY = particle.velocityY * drag + gravity;
|
|
54
|
+
particle.wobble += particle.wobbleSpeed;
|
|
55
|
+
particle.x += particle.velocityX + Math.sin(particle.wobble) * 0.8;
|
|
56
|
+
particle.y += particle.velocityY;
|
|
57
|
+
particle.rotation += particle.rotationSpeed;
|
|
58
|
+
}
|
|
59
|
+
const remaining = particles.filter((particle) => particle.y - particle.size <= canvas.height);
|
|
60
|
+
particles.length = 0;
|
|
61
|
+
particles.push(...remaining);
|
|
62
|
+
};
|
|
63
|
+
const render = () => {
|
|
64
|
+
if (!running)
|
|
65
|
+
return;
|
|
66
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
67
|
+
for (const particle of particles)
|
|
68
|
+
drawParticle(particle);
|
|
69
|
+
updateParticles();
|
|
70
|
+
if (particles.length === 0) {
|
|
71
|
+
stopConfetti();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
animationFrameId = requestAnimationFrame(render);
|
|
75
|
+
};
|
|
76
|
+
let stopped = false;
|
|
77
|
+
const stopConfetti = () => {
|
|
78
|
+
if (stopped)
|
|
79
|
+
return;
|
|
80
|
+
stopped = true;
|
|
81
|
+
running = false;
|
|
82
|
+
cancelAnimationFrame(animationFrameId);
|
|
83
|
+
if (timeoutId !== null)
|
|
84
|
+
clearTimeout(timeoutId);
|
|
85
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
86
|
+
canvas.remove();
|
|
87
|
+
};
|
|
88
|
+
const startConfetti = () => {
|
|
89
|
+
particles.length = 0;
|
|
90
|
+
for (let i = 0; i < particleCount; i++)
|
|
91
|
+
particles.push(createParticle());
|
|
92
|
+
running = true;
|
|
93
|
+
render();
|
|
94
|
+
if (duration > 0) {
|
|
95
|
+
timeoutId = window.setTimeout(stopConfetti, duration);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
startConfetti();
|
|
99
|
+
return {
|
|
100
|
+
stop: stopConfetti
|
|
101
|
+
};
|
|
102
|
+
}
|
package/dist/index.css
CHANGED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Component } from 'svelte';
|
|
3
|
+
import Feature from './AllFeaturesAccordionFeature.svelte';
|
|
4
|
+
import IconChevronDown from '@hyvor/icons/IconChevronDown';
|
|
5
|
+
|
|
6
|
+
interface FeatureItem {
|
|
7
|
+
icon: Component;
|
|
8
|
+
title: string;
|
|
9
|
+
description: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Category {
|
|
13
|
+
label: string;
|
|
14
|
+
icon: Component;
|
|
15
|
+
color?: string;
|
|
16
|
+
features: FeatureItem[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
title?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
categories: Category[];
|
|
23
|
+
defaultOpenIndex?: number;
|
|
24
|
+
accentColor?: string;
|
|
25
|
+
accentColorDark?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
title = "And There's More...",
|
|
30
|
+
description = '',
|
|
31
|
+
categories,
|
|
32
|
+
defaultOpenIndex = 0,
|
|
33
|
+
accentColor,
|
|
34
|
+
accentColorDark
|
|
35
|
+
}: Props = $props();
|
|
36
|
+
|
|
37
|
+
let openIndex = $state(defaultOpenIndex);
|
|
38
|
+
|
|
39
|
+
function toggle(i: number) {
|
|
40
|
+
openIndex = openIndex === i ? -1 : i;
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<section
|
|
45
|
+
class="hds-all-features-accordion hds-container"
|
|
46
|
+
style:--afa-accent={accentColor}
|
|
47
|
+
style:--afa-accent-dark={accentColorDark}
|
|
48
|
+
>
|
|
49
|
+
<div class="section-header">
|
|
50
|
+
<h2>{title}</h2>
|
|
51
|
+
{#if description}
|
|
52
|
+
<p>{description}</p>
|
|
53
|
+
{/if}
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="accordion">
|
|
57
|
+
{#each categories as cat, i}
|
|
58
|
+
{@const CatIcon = cat.icon}
|
|
59
|
+
{@const catColor = cat.color || 'var(--accent)'}
|
|
60
|
+
<div class="accordion-item" class:open={openIndex === i}>
|
|
61
|
+
<h3 class="accordion-heading">
|
|
62
|
+
<button
|
|
63
|
+
class="accordion-trigger"
|
|
64
|
+
id="hds-accordion-trigger-{i}"
|
|
65
|
+
onclick={() => toggle(i)}
|
|
66
|
+
aria-expanded={openIndex === i}
|
|
67
|
+
aria-controls="hds-accordion-panel-{i}"
|
|
68
|
+
style="--cat-color: {catColor}"
|
|
69
|
+
>
|
|
70
|
+
<span class="cat-icon" aria-hidden="true">
|
|
71
|
+
<CatIcon size={18} />
|
|
72
|
+
</span>
|
|
73
|
+
<span class="cat-label">{cat.label}</span>
|
|
74
|
+
<span class="cat-count">{cat.features.length} features</span>
|
|
75
|
+
<span class="chevron" class:rotated={openIndex === i} aria-hidden="true">
|
|
76
|
+
<IconChevronDown size={14} />
|
|
77
|
+
</span>
|
|
78
|
+
</button>
|
|
79
|
+
</h3>
|
|
80
|
+
|
|
81
|
+
<div
|
|
82
|
+
class="accordion-body"
|
|
83
|
+
class:open={openIndex === i}
|
|
84
|
+
role="region"
|
|
85
|
+
id="hds-accordion-panel-{i}"
|
|
86
|
+
aria-labelledby="hds-accordion-trigger-{i}"
|
|
87
|
+
>
|
|
88
|
+
<div class="accordion-body-inner">
|
|
89
|
+
<div class="accordion-content">
|
|
90
|
+
<ul class="features-grid">
|
|
91
|
+
{#each cat.features as feat}
|
|
92
|
+
<Feature
|
|
93
|
+
icon={feat.icon}
|
|
94
|
+
title={feat.title}
|
|
95
|
+
description={feat.description}
|
|
96
|
+
color={catColor}
|
|
97
|
+
/>
|
|
98
|
+
{/each}
|
|
99
|
+
</ul>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
{/each}
|
|
105
|
+
</div>
|
|
106
|
+
</section>
|
|
107
|
+
|
|
108
|
+
<style>
|
|
109
|
+
.hds-all-features-accordion {
|
|
110
|
+
padding: 80px 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.section-header {
|
|
114
|
+
text-align: center;
|
|
115
|
+
margin-bottom: 48px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.section-header h2 {
|
|
119
|
+
font-size: 32px;
|
|
120
|
+
font-weight: 700;
|
|
121
|
+
margin: 0 0 6px;
|
|
122
|
+
font-family: var(--font-serif);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.section-header p {
|
|
126
|
+
font-size: 1rem;
|
|
127
|
+
color: var(--text-light);
|
|
128
|
+
margin: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.accordion {
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: column;
|
|
134
|
+
gap: 4px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.accordion-item {
|
|
138
|
+
border-radius: 16px;
|
|
139
|
+
transition: background 0.2s ease;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.accordion-item.open {
|
|
143
|
+
background: color-mix(in srgb, var(--afa-accent, var(--accent)) 6%, transparent);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
:global(:root.dark) .accordion-item.open {
|
|
147
|
+
background: color-mix(
|
|
148
|
+
in srgb,
|
|
149
|
+
var(--afa-accent-dark, var(--afa-accent, var(--accent))) 6%,
|
|
150
|
+
transparent
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.accordion-heading {
|
|
155
|
+
margin: 0;
|
|
156
|
+
font-size: inherit;
|
|
157
|
+
font-weight: inherit;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.accordion-trigger {
|
|
161
|
+
width: 100%;
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
gap: 14px;
|
|
165
|
+
padding: 18px 20px;
|
|
166
|
+
background: none;
|
|
167
|
+
border: none;
|
|
168
|
+
border-radius: inherit;
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
text-align: left;
|
|
171
|
+
transition: background 0.15s;
|
|
172
|
+
color: var(--text);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.accordion-item:not(.open) .accordion-trigger:hover {
|
|
176
|
+
background: var(--hover);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.cat-icon {
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
justify-content: center;
|
|
183
|
+
width: 34px;
|
|
184
|
+
height: 34px;
|
|
185
|
+
border-radius: 10px;
|
|
186
|
+
color: var(--cat-color);
|
|
187
|
+
background: color-mix(in srgb, var(--cat-color) 16%, transparent);
|
|
188
|
+
flex-shrink: 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.cat-label {
|
|
192
|
+
font-size: 16px;
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
flex: 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.cat-count {
|
|
198
|
+
font-size: 13px;
|
|
199
|
+
color: var(--text-light);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.chevron {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
color: var(--text-light);
|
|
206
|
+
transition: transform 0.2s;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.chevron.rotated {
|
|
210
|
+
transform: rotate(180deg);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* CSS grid row trick: content stays in DOM for SEO, height animates via grid */
|
|
214
|
+
.accordion-body {
|
|
215
|
+
display: grid;
|
|
216
|
+
grid-template-rows: 0fr;
|
|
217
|
+
transition: grid-template-rows 0.22s ease;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.accordion-body.open {
|
|
221
|
+
grid-template-rows: 1fr;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.accordion-body-inner {
|
|
225
|
+
overflow: hidden;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.accordion-content {
|
|
229
|
+
padding: 8px 20px 28px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.features-grid {
|
|
233
|
+
display: grid;
|
|
234
|
+
grid-template-columns: repeat(3, 1fr);
|
|
235
|
+
gap: 28px 20px;
|
|
236
|
+
margin: 0;
|
|
237
|
+
padding: 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* Override AllFeaturesAccordionFeature's width since we're using grid now */
|
|
241
|
+
/* TODO */
|
|
242
|
+
.features-grid :global(.feature) {
|
|
243
|
+
width: auto;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@media (max-width: 768px) {
|
|
247
|
+
.features-grid {
|
|
248
|
+
grid-template-columns: 1fr;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
interface FeatureItem {
|
|
3
|
+
icon: Component;
|
|
4
|
+
title: string;
|
|
5
|
+
description: string;
|
|
6
|
+
}
|
|
7
|
+
interface Category {
|
|
8
|
+
label: string;
|
|
9
|
+
icon: Component;
|
|
10
|
+
color?: string;
|
|
11
|
+
features: FeatureItem[];
|
|
12
|
+
}
|
|
13
|
+
interface Props {
|
|
14
|
+
title?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
categories: Category[];
|
|
17
|
+
defaultOpenIndex?: number;
|
|
18
|
+
accentColor?: string;
|
|
19
|
+
accentColorDark?: string;
|
|
20
|
+
}
|
|
21
|
+
declare const AllFeaturesAccordion: Component<Props, {}, "">;
|
|
22
|
+
type AllFeaturesAccordion = ReturnType<typeof AllFeaturesAccordion>;
|
|
23
|
+
export default AllFeaturesAccordion;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Component } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
icon: Component;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
color?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { icon, title, description, color = 'var(--accent)' }: Props = $props();
|
|
12
|
+
|
|
13
|
+
const SvelteComponent = $derived(icon);
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<li class="feature">
|
|
17
|
+
<div class="icon-bg" style:color aria-hidden="true">
|
|
18
|
+
<SvelteComponent size={30} />
|
|
19
|
+
</div>
|
|
20
|
+
<div class="content">
|
|
21
|
+
<h4 class="title">{title}</h4>
|
|
22
|
+
<p class="description">{description}</p>
|
|
23
|
+
</div>
|
|
24
|
+
</li>
|
|
25
|
+
|
|
26
|
+
<style>
|
|
27
|
+
.feature {
|
|
28
|
+
position: relative;
|
|
29
|
+
width: calc(33.33% - 15px);
|
|
30
|
+
list-style: none;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.content {
|
|
35
|
+
position: relative;
|
|
36
|
+
z-index: 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.icon-bg {
|
|
40
|
+
position: absolute;
|
|
41
|
+
top: 0;
|
|
42
|
+
right: 4px;
|
|
43
|
+
z-index: 0;
|
|
44
|
+
opacity: 0.14;
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.title {
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
font-size: 20px;
|
|
51
|
+
margin: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.description {
|
|
55
|
+
font-size: 16px;
|
|
56
|
+
margin: 10px 0 0;
|
|
57
|
+
color: var(--text-light);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@media (max-width: 992px) {
|
|
61
|
+
.feature {
|
|
62
|
+
width: 100%;
|
|
63
|
+
text-align: center;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.icon-bg {
|
|
67
|
+
display: none;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
icon: Component;
|
|
4
|
+
title: string;
|
|
5
|
+
description: string;
|
|
6
|
+
color?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const AllFeaturesAccordionFeature: Component<Props, {}, "">;
|
|
9
|
+
type AllFeaturesAccordionFeature = ReturnType<typeof AllFeaturesAccordionFeature>;
|
|
10
|
+
export default AllFeaturesAccordionFeature;
|