mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
style: auto-format codebase with Prettier
Applied formatting to 1487+ files using pnpm format:write - TypeScript/JavaScript files - Svelte components - Astro pages - JSON configs - Markdown docs 13 files still need manual review (Astro JSX comments)
This commit is contained in:
parent
0241f5554c
commit
d36b321d9d
3952 changed files with 661498 additions and 739751 deletions
|
|
@ -1,39 +1,39 @@
|
|||
{
|
||||
"name": "@manacore/shared-landing-ui",
|
||||
"version": "0.1.0",
|
||||
"description": "Shared Astro landing page components for Manacore monorepo",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./atoms/*": "./src/atoms/*",
|
||||
"./sections/*": "./src/sections/*",
|
||||
"./layouts/*": "./src/layouts/*",
|
||||
"./utils": "./src/utils/index.ts",
|
||||
"./themes": "./src/themes/index.css",
|
||||
"./themes/memoro": "./src/themes/memoro.css",
|
||||
"./themes/manacore": "./src/themes/manacore.css",
|
||||
"./themes/maerchenzauber": "./src/themes/maerchenzauber.css",
|
||||
"./themes/manadeck": "./src/themes/manadeck.css"
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"type-check": "astro check"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": ">=5.0.0",
|
||||
"astro-icon": ">=1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.0",
|
||||
"astro": "^5.16.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"astro",
|
||||
"landing",
|
||||
"components",
|
||||
"manacore"
|
||||
]
|
||||
"name": "@manacore/shared-landing-ui",
|
||||
"version": "0.1.0",
|
||||
"description": "Shared Astro landing page components for Manacore monorepo",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./atoms/*": "./src/atoms/*",
|
||||
"./sections/*": "./src/sections/*",
|
||||
"./layouts/*": "./src/layouts/*",
|
||||
"./utils": "./src/utils/index.ts",
|
||||
"./themes": "./src/themes/index.css",
|
||||
"./themes/memoro": "./src/themes/memoro.css",
|
||||
"./themes/manacore": "./src/themes/manacore.css",
|
||||
"./themes/maerchenzauber": "./src/themes/maerchenzauber.css",
|
||||
"./themes/manadeck": "./src/themes/manadeck.css"
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"type-check": "astro check"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": ">=5.0.0",
|
||||
"astro-icon": ">=1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.0",
|
||||
"astro": "^5.16.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"astro",
|
||||
"landing",
|
||||
"components",
|
||||
"manacore"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,34 +3,32 @@
|
|||
* Shared Badge component for landing pages
|
||||
*/
|
||||
interface Props {
|
||||
variant?: 'default' | 'primary' | 'success' | 'warning' | 'error';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
class?: string;
|
||||
variant?: 'default' | 'primary' | 'success' | 'warning' | 'error';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
variant = 'default',
|
||||
size = 'md',
|
||||
class: className = ''
|
||||
} = Astro.props;
|
||||
const { variant = 'default', size = 'md', class: className = '' } = Astro.props;
|
||||
|
||||
const variants = {
|
||||
default: 'bg-[var(--color-background-card)] text-[var(--color-text-secondary)] border-[var(--color-border)]',
|
||||
primary: 'bg-[var(--color-primary)]/10 text-[var(--color-primary)] border-[var(--color-primary)]/20',
|
||||
success: 'bg-green-100 text-green-700 border-green-200',
|
||||
warning: 'bg-yellow-100 text-yellow-700 border-yellow-200',
|
||||
error: 'bg-red-100 text-red-700 border-red-200'
|
||||
default:
|
||||
'bg-[var(--color-background-card)] text-[var(--color-text-secondary)] border-[var(--color-border)]',
|
||||
primary:
|
||||
'bg-[var(--color-primary)]/10 text-[var(--color-primary)] border-[var(--color-primary)]/20',
|
||||
success: 'bg-green-100 text-green-700 border-green-200',
|
||||
warning: 'bg-yellow-100 text-yellow-700 border-yellow-200',
|
||||
error: 'bg-red-100 text-red-700 border-red-200',
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: 'text-xs px-2 py-0.5',
|
||||
md: 'text-sm px-2.5 py-1',
|
||||
lg: 'text-base px-3 py-1.5'
|
||||
sm: 'text-xs px-2 py-0.5',
|
||||
md: 'text-sm px-2.5 py-1',
|
||||
lg: 'text-base px-3 py-1.5',
|
||||
};
|
||||
|
||||
const styles = `inline-flex items-center rounded-full font-medium border ${variants[variant]} ${sizes[size]} ${className}`;
|
||||
---
|
||||
|
||||
<span class={styles}>
|
||||
<slot />
|
||||
<slot />
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -6,70 +6,70 @@
|
|||
* --color-background-card, --color-border
|
||||
*/
|
||||
interface Props {
|
||||
href?: string;
|
||||
variant?: "primary" | "secondary" | "outline" | "ghost";
|
||||
size?: "sm" | "md" | "lg";
|
||||
class?: string;
|
||||
target?: string;
|
||||
rel?: string;
|
||||
download?: boolean | string;
|
||||
id?: string;
|
||||
onclick?: string;
|
||||
'aria-label'?: string;
|
||||
fullWidth?: boolean;
|
||||
[key: string]: any;
|
||||
href?: string;
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
class?: string;
|
||||
target?: string;
|
||||
rel?: string;
|
||||
download?: boolean | string;
|
||||
id?: string;
|
||||
onclick?: string;
|
||||
'aria-label'?: string;
|
||||
fullWidth?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const {
|
||||
href,
|
||||
variant = "primary",
|
||||
size = "md",
|
||||
class: className = "",
|
||||
target,
|
||||
rel,
|
||||
download,
|
||||
id,
|
||||
onclick,
|
||||
'aria-label': ariaLabel,
|
||||
fullWidth = false,
|
||||
...rest
|
||||
href,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
class: className = '',
|
||||
target,
|
||||
rel,
|
||||
download,
|
||||
id,
|
||||
onclick,
|
||||
'aria-label': ariaLabel,
|
||||
fullWidth = false,
|
||||
...rest
|
||||
} = Astro.props;
|
||||
|
||||
const variants = {
|
||||
primary:
|
||||
"bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-hover)] border-2 border-[var(--color-primary)] hover:border-[var(--color-primary-hover)]",
|
||||
secondary:
|
||||
"bg-transparent text-[var(--color-text-primary)] hover:bg-[var(--color-background-card)] border-2 border-[var(--color-border)] hover:border-[var(--color-primary)]",
|
||||
outline:
|
||||
"bg-transparent text-[var(--color-text-primary)] hover:bg-[var(--color-background-card-hover)] border-2 border-[var(--color-border)]",
|
||||
ghost:
|
||||
"bg-transparent text-[var(--color-text-primary)] hover:bg-[var(--color-background-card-hover)] border-transparent",
|
||||
primary:
|
||||
'bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-hover)] border-2 border-[var(--color-primary)] hover:border-[var(--color-primary-hover)]',
|
||||
secondary:
|
||||
'bg-transparent text-[var(--color-text-primary)] hover:bg-[var(--color-background-card)] border-2 border-[var(--color-border)] hover:border-[var(--color-primary)]',
|
||||
outline:
|
||||
'bg-transparent text-[var(--color-text-primary)] hover:bg-[var(--color-background-card-hover)] border-2 border-[var(--color-border)]',
|
||||
ghost:
|
||||
'bg-transparent text-[var(--color-text-primary)] hover:bg-[var(--color-background-card-hover)] border-transparent',
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: "text-sm px-3 py-1.5",
|
||||
md: "text-base px-4 py-2",
|
||||
lg: "text-lg px-6 py-3",
|
||||
sm: 'text-sm px-3 py-1.5',
|
||||
md: 'text-base px-4 py-2',
|
||||
lg: 'text-lg px-6 py-3',
|
||||
};
|
||||
|
||||
const baseStyles =
|
||||
"inline-flex items-center justify-center rounded-lg transition-all duration-200 font-medium focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:ring-offset-2 whitespace-nowrap";
|
||||
const widthStyle = fullWidth ? "w-full" : "";
|
||||
'inline-flex items-center justify-center rounded-lg transition-all duration-200 font-medium focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:ring-offset-2 whitespace-nowrap';
|
||||
const widthStyle = fullWidth ? 'w-full' : '';
|
||||
const styles = `${baseStyles} ${variants[variant]} ${sizes[size]} ${widthStyle} ${className}`;
|
||||
|
||||
const Element = href ? "a" : "button";
|
||||
const Element = href ? 'a' : 'button';
|
||||
---
|
||||
|
||||
<Element
|
||||
href={href}
|
||||
target={target}
|
||||
rel={rel}
|
||||
download={download}
|
||||
class={styles}
|
||||
id={id}
|
||||
onclick={onclick}
|
||||
aria-label={ariaLabel}
|
||||
{...rest}
|
||||
href={href}
|
||||
target={target}
|
||||
rel={rel}
|
||||
download={download}
|
||||
class={styles}
|
||||
id={id}
|
||||
onclick={onclick}
|
||||
aria-label={ariaLabel}
|
||||
{...rest}
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</Element>
|
||||
|
|
|
|||
|
|
@ -5,46 +5,50 @@
|
|||
* --color-background-card, --color-border, --color-primary
|
||||
*/
|
||||
export interface Props {
|
||||
variant?: 'default' | 'hover' | 'glow' | 'bordered';
|
||||
class?: string;
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
as?: 'div' | 'article' | 'section' | 'a';
|
||||
href?: string;
|
||||
variant?: 'default' | 'hover' | 'glow' | 'bordered';
|
||||
class?: string;
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
as?: 'div' | 'article' | 'section' | 'a';
|
||||
href?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
variant = 'default',
|
||||
class: className = '',
|
||||
padding = 'md',
|
||||
as: Element = 'div',
|
||||
href
|
||||
variant = 'default',
|
||||
class: className = '',
|
||||
padding = 'md',
|
||||
as: Element = 'div',
|
||||
href,
|
||||
} = Astro.props;
|
||||
|
||||
const paddingStyles = {
|
||||
none: '',
|
||||
sm: 'p-3 sm:p-4',
|
||||
md: 'p-4 sm:p-6',
|
||||
lg: 'p-6 sm:p-8',
|
||||
xl: 'p-8 sm:p-10'
|
||||
none: '',
|
||||
sm: 'p-3 sm:p-4',
|
||||
md: 'p-4 sm:p-6',
|
||||
lg: 'p-6 sm:p-8',
|
||||
xl: 'p-8 sm:p-10',
|
||||
};
|
||||
|
||||
const variantStyles = {
|
||||
default: 'bg-[var(--color-background-card)]',
|
||||
hover: 'bg-[var(--color-background-card)] hover:bg-[var(--color-background-card-hover)] hover:shadow-xl hover:scale-[1.02] transition-all duration-300',
|
||||
glow: 'bg-[var(--color-background-card)] hover:shadow-[0_0_30px_var(--color-primary-glow,rgba(59,130,246,0.3))] transition-all duration-300',
|
||||
bordered: 'bg-[var(--color-background-card)] border border-[var(--color-border)] hover:border-[var(--color-primary)] transition-colors duration-200'
|
||||
default: 'bg-[var(--color-background-card)]',
|
||||
hover:
|
||||
'bg-[var(--color-background-card)] hover:bg-[var(--color-background-card-hover)] hover:shadow-xl hover:scale-[1.02] transition-all duration-300',
|
||||
glow: 'bg-[var(--color-background-card)] hover:shadow-[0_0_30px_var(--color-primary-glow,rgba(59,130,246,0.3))] transition-all duration-300',
|
||||
bordered:
|
||||
'bg-[var(--color-background-card)] border border-[var(--color-border)] hover:border-[var(--color-primary)] transition-colors duration-200',
|
||||
};
|
||||
|
||||
const baseStyles = 'rounded-xl shadow-lg';
|
||||
const finalClassName = `${baseStyles} ${variantStyles[variant]} ${paddingStyles[padding]} ${className}`;
|
||||
---
|
||||
|
||||
{Element === 'a' ? (
|
||||
<a href={href} class={finalClassName}>
|
||||
<slot />
|
||||
</a>
|
||||
) : (
|
||||
<Element class={finalClassName}>
|
||||
<slot />
|
||||
</Element>
|
||||
)}
|
||||
{
|
||||
Element === 'a' ? (
|
||||
<a href={href} class={finalClassName}>
|
||||
<slot />
|
||||
</a>
|
||||
) : (
|
||||
<Element class={finalClassName}>
|
||||
<slot />
|
||||
</Element>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,28 +3,24 @@
|
|||
* Shared Container component for consistent max-width and padding
|
||||
*/
|
||||
interface Props {
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
||||
class?: string;
|
||||
as?: 'div' | 'section' | 'article' | 'main';
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
||||
class?: string;
|
||||
as?: 'div' | 'section' | 'article' | 'main';
|
||||
}
|
||||
|
||||
const {
|
||||
size = 'lg',
|
||||
class: className = '',
|
||||
as: Element = 'div'
|
||||
} = Astro.props;
|
||||
const { size = 'lg', class: className = '', as: Element = 'div' } = Astro.props;
|
||||
|
||||
const sizes = {
|
||||
sm: 'max-w-3xl',
|
||||
md: 'max-w-5xl',
|
||||
lg: 'max-w-7xl',
|
||||
xl: 'max-w-[1400px]',
|
||||
full: 'max-w-full'
|
||||
sm: 'max-w-3xl',
|
||||
md: 'max-w-5xl',
|
||||
lg: 'max-w-7xl',
|
||||
xl: 'max-w-[1400px]',
|
||||
full: 'max-w-full',
|
||||
};
|
||||
|
||||
const styles = `mx-auto px-4 sm:px-6 lg:px-8 ${sizes[size]} ${className}`;
|
||||
---
|
||||
|
||||
<Element class={styles}>
|
||||
<slot />
|
||||
<slot />
|
||||
</Element>
|
||||
|
|
|
|||
|
|
@ -3,47 +3,53 @@
|
|||
* Shared Section Header component for consistent section titles
|
||||
*/
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
class?: string;
|
||||
titleClass?: string;
|
||||
subtitleClass?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
class?: string;
|
||||
titleClass?: string;
|
||||
subtitleClass?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
subtitle,
|
||||
align = 'center',
|
||||
class: className = '',
|
||||
titleClass = '',
|
||||
subtitleClass = ''
|
||||
title,
|
||||
subtitle,
|
||||
align = 'center',
|
||||
class: className = '',
|
||||
titleClass = '',
|
||||
subtitleClass = '',
|
||||
} = Astro.props;
|
||||
|
||||
const alignStyles = {
|
||||
left: 'text-left',
|
||||
center: 'text-center mx-auto',
|
||||
right: 'text-right'
|
||||
left: 'text-left',
|
||||
center: 'text-center mx-auto',
|
||||
right: 'text-right',
|
||||
};
|
||||
|
||||
const containerStyles = `mb-8 md:mb-12 ${alignStyles[align]} ${className}`;
|
||||
---
|
||||
|
||||
<div class={containerStyles}>
|
||||
<h2 class:list={[
|
||||
"text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-[var(--color-text-primary)] mb-3 md:mb-4 leading-tight",
|
||||
titleClass
|
||||
]}>
|
||||
<slot name="title">{title}</slot>
|
||||
</h2>
|
||||
{subtitle && (
|
||||
<p class:list={[
|
||||
"text-base sm:text-lg text-[var(--color-text-secondary)] max-w-3xl leading-relaxed",
|
||||
align === 'center' && 'mx-auto',
|
||||
subtitleClass
|
||||
]}>
|
||||
<slot name="subtitle">{subtitle}</slot>
|
||||
</p>
|
||||
)}
|
||||
<slot />
|
||||
<h2
|
||||
class:list={[
|
||||
'text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-[var(--color-text-primary)] mb-3 md:mb-4 leading-tight',
|
||||
titleClass,
|
||||
]}
|
||||
>
|
||||
<slot name="title">{title}</slot>
|
||||
</h2>
|
||||
{
|
||||
subtitle && (
|
||||
<p
|
||||
class:list={[
|
||||
'text-base sm:text-lg text-[var(--color-text-secondary)] max-w-3xl leading-relaxed',
|
||||
align === 'center' && 'mx-auto',
|
||||
subtitleClass,
|
||||
]}
|
||||
>
|
||||
<slot name="subtitle">{subtitle}</slot>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<slot />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,163 +7,167 @@ import Container from '../atoms/Container.astro';
|
|||
import Button from '../atoms/Button.astro';
|
||||
|
||||
interface FooterLink {
|
||||
label: string;
|
||||
href: string;
|
||||
icon?: string;
|
||||
external?: boolean;
|
||||
label: string;
|
||||
href: string;
|
||||
icon?: string;
|
||||
external?: boolean;
|
||||
}
|
||||
|
||||
interface FooterSection {
|
||||
title: string;
|
||||
links: FooterLink[];
|
||||
title: string;
|
||||
links: FooterLink[];
|
||||
}
|
||||
|
||||
interface SocialLink {
|
||||
platform: 'linkedin' | 'twitter' | 'facebook' | 'instagram' | 'youtube' | 'github';
|
||||
href: string;
|
||||
platform: 'linkedin' | 'twitter' | 'facebook' | 'instagram' | 'youtube' | 'github';
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
brand: {
|
||||
name: string;
|
||||
tagline?: string;
|
||||
logo?: string;
|
||||
};
|
||||
sections?: FooterSection[];
|
||||
socialLinks?: SocialLink[];
|
||||
copyright?: string;
|
||||
ctaButton?: {
|
||||
text: string;
|
||||
href: string;
|
||||
variant?: 'primary' | 'secondary';
|
||||
};
|
||||
secondaryButton?: {
|
||||
text: string;
|
||||
href: string;
|
||||
};
|
||||
class?: string;
|
||||
lang?: string;
|
||||
brand: {
|
||||
name: string;
|
||||
tagline?: string;
|
||||
logo?: string;
|
||||
};
|
||||
sections?: FooterSection[];
|
||||
socialLinks?: SocialLink[];
|
||||
copyright?: string;
|
||||
ctaButton?: {
|
||||
text: string;
|
||||
href: string;
|
||||
variant?: 'primary' | 'secondary';
|
||||
};
|
||||
secondaryButton?: {
|
||||
text: string;
|
||||
href: string;
|
||||
};
|
||||
class?: string;
|
||||
lang?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
brand,
|
||||
sections = [],
|
||||
socialLinks = [],
|
||||
copyright,
|
||||
ctaButton,
|
||||
secondaryButton,
|
||||
class: className = '',
|
||||
lang = 'de'
|
||||
brand,
|
||||
sections = [],
|
||||
socialLinks = [],
|
||||
copyright,
|
||||
ctaButton,
|
||||
secondaryButton,
|
||||
class: className = '',
|
||||
lang = 'de',
|
||||
} = Astro.props;
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const socialIcons = {
|
||||
linkedin: `<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/>`,
|
||||
twitter: `<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>`,
|
||||
facebook: `<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>`,
|
||||
instagram: `<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>`,
|
||||
youtube: `<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>`,
|
||||
github: `<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>`
|
||||
linkedin: `<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/>`,
|
||||
twitter: `<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>`,
|
||||
facebook: `<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>`,
|
||||
instagram: `<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>`,
|
||||
youtube: `<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>`,
|
||||
github: `<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>`,
|
||||
};
|
||||
|
||||
const socialHoverColors = {
|
||||
linkedin: 'hover:text-[#0077b5]',
|
||||
twitter: 'hover:text-white',
|
||||
facebook: 'hover:text-[#1877f2]',
|
||||
instagram: 'hover:text-[#E4405F]',
|
||||
youtube: 'hover:text-[#FF0000]',
|
||||
github: 'hover:text-white'
|
||||
linkedin: 'hover:text-[#0077b5]',
|
||||
twitter: 'hover:text-white',
|
||||
facebook: 'hover:text-[#1877f2]',
|
||||
instagram: 'hover:text-[#E4405F]',
|
||||
youtube: 'hover:text-[#FF0000]',
|
||||
github: 'hover:text-white',
|
||||
};
|
||||
---
|
||||
|
||||
<footer class:list={["bg-[var(--color-background-page)] py-12 md:py-16", className]}>
|
||||
<Container>
|
||||
{/* Main Footer Grid */}
|
||||
{sections.length > 0 && (
|
||||
<div class:list={[
|
||||
"grid gap-8 mb-12",
|
||||
sections.length === 2 && "grid-cols-2 md:grid-cols-4",
|
||||
sections.length === 3 && "grid-cols-2 md:grid-cols-3 lg:grid-cols-4",
|
||||
sections.length >= 4 && "grid-cols-2 md:grid-cols-4 lg:grid-cols-5"
|
||||
]}>
|
||||
{sections.map((section) => (
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-xs sm:text-sm font-medium text-[var(--color-text-muted)] uppercase tracking-wider mb-4">
|
||||
{section.title}
|
||||
</h3>
|
||||
<ul class="space-y-2">
|
||||
{section.links.map((link) => (
|
||||
<li>
|
||||
<a
|
||||
href={link.href}
|
||||
class="flex items-center gap-2 px-3 py-2 bg-white/5 hover:bg-white/10 rounded-lg text-sm transition-all text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]"
|
||||
target={link.external ? "_blank" : undefined}
|
||||
rel={link.external ? "noopener noreferrer" : undefined}
|
||||
>
|
||||
{link.icon && <span class="w-4 h-4">{link.icon}</span>}
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<footer class:list={['bg-[var(--color-background-page)] py-12 md:py-16', className]}>
|
||||
<Container>
|
||||
{/* Main Footer Grid */}
|
||||
{
|
||||
sections.length > 0 && (
|
||||
<div
|
||||
class:list={[
|
||||
'grid gap-8 mb-12',
|
||||
sections.length === 2 && 'grid-cols-2 md:grid-cols-4',
|
||||
sections.length === 3 && 'grid-cols-2 md:grid-cols-3 lg:grid-cols-4',
|
||||
sections.length >= 4 && 'grid-cols-2 md:grid-cols-4 lg:grid-cols-5',
|
||||
]}
|
||||
>
|
||||
{sections.map((section) => (
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-xs sm:text-sm font-medium text-[var(--color-text-muted)] uppercase tracking-wider mb-4">
|
||||
{section.title}
|
||||
</h3>
|
||||
<ul class="space-y-2">
|
||||
{section.links.map((link) => (
|
||||
<li>
|
||||
<a
|
||||
href={link.href}
|
||||
class="flex items-center gap-2 px-3 py-2 bg-white/5 hover:bg-white/10 rounded-lg text-sm transition-all text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]"
|
||||
target={link.external ? '_blank' : undefined}
|
||||
rel={link.external ? 'noopener noreferrer' : undefined}
|
||||
>
|
||||
{link.icon && <span class="w-4 h-4">{link.icon}</span>}
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{/* Social Links */}
|
||||
{socialLinks.length > 0 && (
|
||||
<div class="border-t border-[var(--color-border)] pt-8 mb-8">
|
||||
<div class="flex justify-center space-x-6">
|
||||
{socialLinks.map((social) => (
|
||||
<a
|
||||
href={social.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class:list={[
|
||||
"text-[var(--color-text-muted)] transition-colors",
|
||||
socialHoverColors[social.platform]
|
||||
]}
|
||||
>
|
||||
<span class="sr-only">{social.platform}</span>
|
||||
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<Fragment set:html={socialIcons[social.platform]} />
|
||||
</svg>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Social Links */}
|
||||
{
|
||||
socialLinks.length > 0 && (
|
||||
<div class="border-t border-[var(--color-border)] pt-8 mb-8">
|
||||
<div class="flex justify-center space-x-6">
|
||||
{socialLinks.map((social) => (
|
||||
<a
|
||||
href={social.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class:list={[
|
||||
'text-[var(--color-text-muted)] transition-colors',
|
||||
socialHoverColors[social.platform],
|
||||
]}
|
||||
>
|
||||
<span class="sr-only">{social.platform}</span>
|
||||
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<Fragment set:html={socialIcons[social.platform]} />
|
||||
</svg>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{/* CTA Buttons */}
|
||||
{(ctaButton || secondaryButton) && (
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center mb-8">
|
||||
{secondaryButton && (
|
||||
<Button href={secondaryButton.href} variant="secondary" size="md">
|
||||
{secondaryButton.text}
|
||||
</Button>
|
||||
)}
|
||||
{ctaButton && (
|
||||
<Button href={ctaButton.href} variant={ctaButton.variant ?? 'primary'} size="md">
|
||||
{ctaButton.text}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* CTA Buttons */}
|
||||
{
|
||||
(ctaButton || secondaryButton) && (
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center mb-8">
|
||||
{secondaryButton && (
|
||||
<Button href={secondaryButton.href} variant="secondary" size="md">
|
||||
{secondaryButton.text}
|
||||
</Button>
|
||||
)}
|
||||
{ctaButton && (
|
||||
<Button href={ctaButton.href} variant={ctaButton.variant ?? 'primary'} size="md">
|
||||
{ctaButton.text}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{/* Copyright */}
|
||||
<div class="text-center">
|
||||
<p class="text-[var(--color-text-muted)] text-xs sm:text-sm">
|
||||
{copyright || `© ${currentYear} ${brand.name}. All rights reserved.`}
|
||||
</p>
|
||||
{brand.tagline && (
|
||||
<p class="text-[var(--color-text-muted)] text-xs mt-1">
|
||||
{brand.tagline}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* Copyright */}
|
||||
<div class="text-center">
|
||||
<p class="text-[var(--color-text-muted)] text-xs sm:text-sm">
|
||||
{copyright || `© ${currentYear} ${brand.name}. All rights reserved.`}
|
||||
</p>
|
||||
{brand.tagline && <p class="text-[var(--color-text-muted)] text-xs mt-1">{brand.tagline}</p>}
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</Container>
|
||||
<slot />
|
||||
</Container>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -6,69 +6,79 @@ import Container from '../atoms/Container.astro';
|
|||
import Button from '../atoms/Button.astro';
|
||||
|
||||
interface CTA {
|
||||
text: string;
|
||||
href: string;
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
||||
text: string;
|
||||
href: string;
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
primaryCta?: CTA;
|
||||
secondaryCta?: CTA;
|
||||
variant?: 'default' | 'highlighted' | 'minimal';
|
||||
class?: string;
|
||||
id?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
primaryCta?: CTA;
|
||||
secondaryCta?: CTA;
|
||||
variant?: 'default' | 'highlighted' | 'minimal';
|
||||
class?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
subtitle,
|
||||
primaryCta,
|
||||
secondaryCta,
|
||||
variant = 'default',
|
||||
class: className = '',
|
||||
id
|
||||
title,
|
||||
subtitle,
|
||||
primaryCta,
|
||||
secondaryCta,
|
||||
variant = 'default',
|
||||
class: className = '',
|
||||
id,
|
||||
} = Astro.props;
|
||||
|
||||
const variantStyles = {
|
||||
default: 'bg-[var(--color-background-page)]',
|
||||
highlighted: 'bg-[var(--color-primary)]/10 border-y border-[var(--color-primary)]/20',
|
||||
minimal: ''
|
||||
default: 'bg-[var(--color-background-page)]',
|
||||
highlighted: 'bg-[var(--color-primary)]/10 border-y border-[var(--color-primary)]/20',
|
||||
minimal: '',
|
||||
};
|
||||
---
|
||||
|
||||
<section id={id} class:list={["py-16 md:py-24", variantStyles[variant], className]}>
|
||||
<Container size="md">
|
||||
<div class="text-center space-y-6">
|
||||
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-[var(--color-text-primary)] leading-tight">
|
||||
<slot name="title">{title}</slot>
|
||||
</h2>
|
||||
<section id={id} class:list={['py-16 md:py-24', variantStyles[variant], className]}>
|
||||
<Container size="md">
|
||||
<div class="text-center space-y-6">
|
||||
<h2
|
||||
class="text-2xl sm:text-3xl md:text-4xl font-bold text-[var(--color-text-primary)] leading-tight"
|
||||
>
|
||||
<slot name="title">{title}</slot>
|
||||
</h2>
|
||||
|
||||
{subtitle && (
|
||||
<p class="text-base sm:text-lg text-[var(--color-text-secondary)] max-w-2xl mx-auto leading-relaxed">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
{
|
||||
subtitle && (
|
||||
<p class="text-base sm:text-lg text-[var(--color-text-secondary)] max-w-2xl mx-auto leading-relaxed">
|
||||
{subtitle}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
{(primaryCta || secondaryCta) && (
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center pt-4">
|
||||
{primaryCta && (
|
||||
<Button href={primaryCta.href} variant={primaryCta.variant ?? 'primary'} size="lg">
|
||||
<slot name="primaryCtaIcon" />
|
||||
{primaryCta.text}
|
||||
</Button>
|
||||
)}
|
||||
{secondaryCta && (
|
||||
<Button href={secondaryCta.href} variant={secondaryCta.variant ?? 'secondary'} size="lg">
|
||||
<slot name="secondaryCtaIcon" />
|
||||
{secondaryCta.text}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
(primaryCta || secondaryCta) && (
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center pt-4">
|
||||
{primaryCta && (
|
||||
<Button href={primaryCta.href} variant={primaryCta.variant ?? 'primary'} size="lg">
|
||||
<slot name="primaryCtaIcon" />
|
||||
{primaryCta.text}
|
||||
</Button>
|
||||
)}
|
||||
{secondaryCta && (
|
||||
<Button
|
||||
href={secondaryCta.href}
|
||||
variant={secondaryCta.variant ?? 'secondary'}
|
||||
size="lg"
|
||||
>
|
||||
<slot name="secondaryCtaIcon" />
|
||||
{secondaryCta.text}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</Container>
|
||||
<slot />
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -7,109 +7,119 @@ import Container from '../atoms/Container.astro';
|
|||
import SectionHeader from '../atoms/SectionHeader.astro';
|
||||
|
||||
interface FAQItem {
|
||||
question: string;
|
||||
answer: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
faqs: FAQItem[];
|
||||
class?: string;
|
||||
id?: string;
|
||||
singleExpand?: boolean;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
faqs: FAQItem[];
|
||||
class?: string;
|
||||
id?: string;
|
||||
singleExpand?: boolean;
|
||||
}
|
||||
|
||||
const {
|
||||
title = 'Frequently Asked Questions',
|
||||
subtitle,
|
||||
faqs,
|
||||
class: className = '',
|
||||
id = 'faq',
|
||||
singleExpand = true
|
||||
title = 'Frequently Asked Questions',
|
||||
subtitle,
|
||||
faqs,
|
||||
class: className = '',
|
||||
id = 'faq',
|
||||
singleExpand = true,
|
||||
} = Astro.props;
|
||||
|
||||
const sectionId = `faq-section-${Math.random().toString(36).substring(7)}`;
|
||||
---
|
||||
|
||||
<section id={id} class:list={["py-16 md:py-24", className]}>
|
||||
<Container size="md">
|
||||
<SectionHeader title={title} subtitle={subtitle} />
|
||||
<section id={id} class:list={['py-16 md:py-24', className]}>
|
||||
<Container size="md">
|
||||
<SectionHeader title={title} subtitle={subtitle} />
|
||||
|
||||
<div class="max-w-3xl mx-auto space-y-4" data-faq-container={singleExpand ? sectionId : undefined}>
|
||||
{faqs.map((faq, index) => (
|
||||
<details class="group" data-faq-item={singleExpand ? sectionId : undefined}>
|
||||
<summary class="cursor-pointer list-none">
|
||||
<div class="bg-[var(--color-background-card)] rounded-xl p-5 sm:p-6 border border-[var(--color-border)] hover:bg-[var(--color-background-card-hover)] hover:border-[var(--color-border-hover,var(--color-border))] transition-all duration-200">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<h3 class="font-semibold text-base sm:text-lg text-[var(--color-text-primary)] pr-4 group-open:text-[var(--color-primary)] transition-colors text-left">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<div class="flex-shrink-0 w-5 h-5 text-[var(--color-primary)] transition-transform duration-300 group-open:rotate-180">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-hidden max-h-0 group-open:max-h-[1000px] transition-all duration-300 ease-in-out">
|
||||
<div class="pt-4 text-[var(--color-text-secondary)] leading-relaxed text-sm sm:text-base prose prose-sm max-w-none">
|
||||
<Fragment set:html={faq.answer} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
class="max-w-3xl mx-auto space-y-4"
|
||||
data-faq-container={singleExpand ? sectionId : undefined}
|
||||
>
|
||||
{
|
||||
faqs.map((faq, index) => (
|
||||
<details class="group" data-faq-item={singleExpand ? sectionId : undefined}>
|
||||
<summary class="cursor-pointer list-none">
|
||||
<div class="bg-[var(--color-background-card)] rounded-xl p-5 sm:p-6 border border-[var(--color-border)] hover:bg-[var(--color-background-card-hover)] hover:border-[var(--color-border-hover,var(--color-border))] transition-all duration-200">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<h3 class="font-semibold text-base sm:text-lg text-[var(--color-text-primary)] pr-4 group-open:text-[var(--color-primary)] transition-colors text-left">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<div class="flex-shrink-0 w-5 h-5 text-[var(--color-primary)] transition-transform duration-300 group-open:rotate-180">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-hidden max-h-0 group-open:max-h-[1000px] transition-all duration-300 ease-in-out">
|
||||
<div class="pt-4 text-[var(--color-text-secondary)] leading-relaxed text-sm sm:text-base prose prose-sm max-w-none">
|
||||
<Fragment set:html={faq.answer} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
</details>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<slot name="contact" />
|
||||
<slot />
|
||||
</Container>
|
||||
<slot name="contact" />
|
||||
<slot />
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
details summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
details summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
details[open] summary ~ * {
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
details[open] summary ~ * {
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script define:vars={{ sectionId, singleExpand }}>
|
||||
if (singleExpand) {
|
||||
function setupFAQ() {
|
||||
const container = document.querySelector(`[data-faq-container="${sectionId}"]`);
|
||||
if (!container) return;
|
||||
if (singleExpand) {
|
||||
function setupFAQ() {
|
||||
const container = document.querySelector(`[data-faq-container="${sectionId}"]`);
|
||||
if (!container) return;
|
||||
|
||||
const items = container.querySelectorAll(`[data-faq-item="${sectionId}"]`);
|
||||
const items = container.querySelectorAll(`[data-faq-item="${sectionId}"]`);
|
||||
|
||||
items.forEach((item) => {
|
||||
item.addEventListener('toggle', (e) => {
|
||||
if (e.target.open) {
|
||||
items.forEach((otherItem) => {
|
||||
if (otherItem !== e.target && otherItem.open) {
|
||||
otherItem.open = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
items.forEach((item) => {
|
||||
item.addEventListener('toggle', (e) => {
|
||||
if (e.target.open) {
|
||||
items.forEach((otherItem) => {
|
||||
if (otherItem !== e.target && otherItem.open) {
|
||||
otherItem.open = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setupFAQ();
|
||||
document.addEventListener('astro:page-load', setupFAQ);
|
||||
}
|
||||
setupFAQ();
|
||||
document.addEventListener('astro:page-load', setupFAQ);
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -8,88 +8,88 @@ import Card from '../atoms/Card.astro';
|
|||
import SectionHeader from '../atoms/SectionHeader.astro';
|
||||
|
||||
interface Feature {
|
||||
icon: string;
|
||||
title: string;
|
||||
description: string;
|
||||
href?: string;
|
||||
icon: string;
|
||||
title: string;
|
||||
description: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
features: Feature[];
|
||||
columns?: 2 | 3 | 4;
|
||||
variant?: 'cards' | 'simple' | 'icons-left';
|
||||
class?: string;
|
||||
id?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
features: Feature[];
|
||||
columns?: 2 | 3 | 4;
|
||||
variant?: 'cards' | 'simple' | 'icons-left';
|
||||
class?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
subtitle,
|
||||
features,
|
||||
columns = 3,
|
||||
variant = 'cards',
|
||||
class: className = '',
|
||||
id
|
||||
title,
|
||||
subtitle,
|
||||
features,
|
||||
columns = 3,
|
||||
variant = 'cards',
|
||||
class: className = '',
|
||||
id,
|
||||
} = Astro.props;
|
||||
|
||||
const gridCols = {
|
||||
2: 'md:grid-cols-2',
|
||||
3: 'md:grid-cols-2 lg:grid-cols-3',
|
||||
4: 'md:grid-cols-2 lg:grid-cols-4'
|
||||
2: 'md:grid-cols-2',
|
||||
3: 'md:grid-cols-2 lg:grid-cols-3',
|
||||
4: 'md:grid-cols-2 lg:grid-cols-4',
|
||||
};
|
||||
---
|
||||
|
||||
<section id={id} class:list={["py-16 md:py-24", className]}>
|
||||
<Container>
|
||||
<SectionHeader title={title} subtitle={subtitle} />
|
||||
<section id={id} class:list={['py-16 md:py-24', className]}>
|
||||
<Container>
|
||||
<SectionHeader title={title} subtitle={subtitle} />
|
||||
|
||||
<div class:list={["grid gap-6 md:gap-8", gridCols[columns]]}>
|
||||
{features.map((feature) => (
|
||||
variant === 'cards' ? (
|
||||
<Card variant="hover" padding="lg" as={feature.href ? 'a' : 'div'} href={feature.href}>
|
||||
<div class="flex flex-col items-center text-center h-full">
|
||||
<div class="text-4xl sm:text-5xl mb-4 transform group-hover:scale-110 transition-transform duration-300">
|
||||
{feature.icon}
|
||||
</div>
|
||||
<h3 class="font-semibold text-lg sm:text-xl text-[var(--color-text-primary)] mb-3">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p class="text-[var(--color-text-secondary)] text-sm sm:text-base leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
) : variant === 'icons-left' ? (
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-shrink-0 text-3xl">
|
||||
{feature.icon}
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg text-[var(--color-text-primary)] mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p class="text-[var(--color-text-secondary)] text-sm leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div class="text-center">
|
||||
<div class="text-4xl mb-4">{feature.icon}</div>
|
||||
<h3 class="font-semibold text-lg text-[var(--color-text-primary)] mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p class="text-[var(--color-text-secondary)] text-sm leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
<div class:list={['grid gap-6 md:gap-8', gridCols[columns]]}>
|
||||
{
|
||||
features.map((feature) =>
|
||||
variant === 'cards' ? (
|
||||
<Card variant="hover" padding="lg" as={feature.href ? 'a' : 'div'} href={feature.href}>
|
||||
<div class="flex flex-col items-center text-center h-full">
|
||||
<div class="text-4xl sm:text-5xl mb-4 transform group-hover:scale-110 transition-transform duration-300">
|
||||
{feature.icon}
|
||||
</div>
|
||||
<h3 class="font-semibold text-lg sm:text-xl text-[var(--color-text-primary)] mb-3">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p class="text-[var(--color-text-secondary)] text-sm sm:text-base leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
) : variant === 'icons-left' ? (
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-shrink-0 text-3xl">{feature.icon}</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg text-[var(--color-text-primary)] mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p class="text-[var(--color-text-secondary)] text-sm leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div class="text-center">
|
||||
<div class="text-4xl mb-4">{feature.icon}</div>
|
||||
<h3 class="font-semibold text-lg text-[var(--color-text-primary)] mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p class="text-[var(--color-text-secondary)] text-sm leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<slot name="highlight" />
|
||||
<slot />
|
||||
</Container>
|
||||
<slot name="highlight" />
|
||||
<slot />
|
||||
</Container>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -9,132 +9,136 @@ import Badge from '../atoms/Badge.astro';
|
|||
import SectionHeader from '../atoms/SectionHeader.astro';
|
||||
|
||||
interface PricingFeature {
|
||||
text: string;
|
||||
included: boolean;
|
||||
text: string;
|
||||
included: boolean;
|
||||
}
|
||||
|
||||
interface PricingPlan {
|
||||
name: string;
|
||||
description?: string;
|
||||
price: string;
|
||||
period?: string;
|
||||
features: PricingFeature[] | string[];
|
||||
cta: {
|
||||
text: string;
|
||||
href: string;
|
||||
};
|
||||
highlighted?: boolean;
|
||||
badge?: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
price: string;
|
||||
period?: string;
|
||||
features: PricingFeature[] | string[];
|
||||
cta: {
|
||||
text: string;
|
||||
href: string;
|
||||
};
|
||||
highlighted?: boolean;
|
||||
badge?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
plans: PricingPlan[];
|
||||
class?: string;
|
||||
id?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
plans: PricingPlan[];
|
||||
class?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
subtitle,
|
||||
plans,
|
||||
class: className = '',
|
||||
id = 'pricing'
|
||||
} = Astro.props;
|
||||
const { title, subtitle, plans, class: className = '', id = 'pricing' } = Astro.props;
|
||||
|
||||
// Normalize features to always have { text, included } format
|
||||
function normalizeFeatures(features: PricingFeature[] | string[]): PricingFeature[] {
|
||||
return features.map(f => typeof f === 'string' ? { text: f, included: true } : f);
|
||||
return features.map((f) => (typeof f === 'string' ? { text: f, included: true } : f));
|
||||
}
|
||||
---
|
||||
|
||||
<section id={id} class:list={["py-16 md:py-24", className]}>
|
||||
<Container>
|
||||
<SectionHeader title={title} subtitle={subtitle} />
|
||||
<section id={id} class:list={['py-16 md:py-24', className]}>
|
||||
<Container>
|
||||
<SectionHeader title={title} subtitle={subtitle} />
|
||||
|
||||
<div class:list={[
|
||||
"grid gap-6 md:gap-8",
|
||||
plans.length === 2 && "md:grid-cols-2 max-w-4xl mx-auto",
|
||||
plans.length === 3 && "md:grid-cols-3",
|
||||
plans.length >= 4 && "md:grid-cols-2 lg:grid-cols-4"
|
||||
]}>
|
||||
{plans.map((plan) => (
|
||||
<Card
|
||||
variant={plan.highlighted ? 'glow' : 'bordered'}
|
||||
padding="lg"
|
||||
class:list={[
|
||||
"flex flex-col relative",
|
||||
plan.highlighted && "ring-2 ring-[var(--color-primary)] scale-105"
|
||||
]}
|
||||
>
|
||||
{plan.badge && (
|
||||
<div class="absolute -top-3 left-1/2 -translate-x-1/2">
|
||||
<Badge variant="primary">{plan.badge}</Badge>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
class:list={[
|
||||
'grid gap-6 md:gap-8',
|
||||
plans.length === 2 && 'md:grid-cols-2 max-w-4xl mx-auto',
|
||||
plans.length === 3 && 'md:grid-cols-3',
|
||||
plans.length >= 4 && 'md:grid-cols-2 lg:grid-cols-4',
|
||||
]}
|
||||
>
|
||||
{
|
||||
plans.map((plan) => (
|
||||
<Card
|
||||
variant={plan.highlighted ? 'glow' : 'bordered'}
|
||||
padding="lg"
|
||||
class:list={[
|
||||
'flex flex-col relative',
|
||||
plan.highlighted && 'ring-2 ring-[var(--color-primary)] scale-105',
|
||||
]}
|
||||
>
|
||||
{plan.badge && (
|
||||
<div class="absolute -top-3 left-1/2 -translate-x-1/2">
|
||||
<Badge variant="primary">{plan.badge}</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="text-center mb-6">
|
||||
<h3 class="text-xl font-bold text-[var(--color-text-primary)] mb-2">
|
||||
{plan.name}
|
||||
</h3>
|
||||
{plan.description && (
|
||||
<p class="text-sm text-[var(--color-text-secondary)]">
|
||||
{plan.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div class="text-center mb-6">
|
||||
<h3 class="text-xl font-bold text-[var(--color-text-primary)] mb-2">{plan.name}</h3>
|
||||
{plan.description && (
|
||||
<p class="text-sm text-[var(--color-text-secondary)]">{plan.description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-6">
|
||||
<span class="text-4xl font-bold text-[var(--color-text-primary)]">
|
||||
{plan.price}
|
||||
</span>
|
||||
{plan.period && (
|
||||
<span class="text-[var(--color-text-secondary)]">
|
||||
/{plan.period}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div class="text-center mb-6">
|
||||
<span class="text-4xl font-bold text-[var(--color-text-primary)]">{plan.price}</span>
|
||||
{plan.period && (
|
||||
<span class="text-[var(--color-text-secondary)]">/{plan.period}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ul class="space-y-3 mb-8 flex-1">
|
||||
{normalizeFeatures(plan.features).map((feature) => (
|
||||
<li class="flex items-start gap-3">
|
||||
<svg
|
||||
class:list={[
|
||||
"w-5 h-5 flex-shrink-0 mt-0.5",
|
||||
feature.included ? "text-green-500" : "text-[var(--color-text-muted)]"
|
||||
]}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
{feature.included ? (
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
) : (
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
)}
|
||||
</svg>
|
||||
<span class:list={[
|
||||
"text-sm",
|
||||
feature.included ? "text-[var(--color-text-primary)]" : "text-[var(--color-text-muted)] line-through"
|
||||
]}>
|
||||
{feature.text}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul class="space-y-3 mb-8 flex-1">
|
||||
{normalizeFeatures(plan.features).map((feature) => (
|
||||
<li class="flex items-start gap-3">
|
||||
<svg
|
||||
class:list={[
|
||||
'w-5 h-5 flex-shrink-0 mt-0.5',
|
||||
feature.included ? 'text-green-500' : 'text-[var(--color-text-muted)]',
|
||||
]}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
{feature.included ? (
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
) : (
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
<span
|
||||
class:list={[
|
||||
'text-sm',
|
||||
feature.included
|
||||
? 'text-[var(--color-text-primary)]'
|
||||
: 'text-[var(--color-text-muted)] line-through',
|
||||
]}
|
||||
>
|
||||
{feature.text}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<Button
|
||||
href={plan.cta.href}
|
||||
variant={plan.highlighted ? 'primary' : 'secondary'}
|
||||
fullWidth
|
||||
>
|
||||
{plan.cta.text}
|
||||
</Button>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
href={plan.cta.href}
|
||||
variant={plan.highlighted ? 'primary' : 'secondary'}
|
||||
fullWidth
|
||||
>
|
||||
{plan.cta.text}
|
||||
</Button>
|
||||
</Card>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</Container>
|
||||
<slot />
|
||||
</Container>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -7,17 +7,17 @@
|
|||
* for the shared components to work correctly.
|
||||
*/
|
||||
export const requiredCssVariables = [
|
||||
'--color-primary',
|
||||
'--color-primary-hover',
|
||||
'--color-primary-glow',
|
||||
'--color-text-primary',
|
||||
'--color-text-secondary',
|
||||
'--color-text-muted',
|
||||
'--color-background-page',
|
||||
'--color-background-card',
|
||||
'--color-background-card-hover',
|
||||
'--color-border',
|
||||
'--color-border-hover',
|
||||
'--color-primary',
|
||||
'--color-primary-hover',
|
||||
'--color-primary-glow',
|
||||
'--color-text-primary',
|
||||
'--color-text-secondary',
|
||||
'--color-text-muted',
|
||||
'--color-background-page',
|
||||
'--color-background-card',
|
||||
'--color-background-card-hover',
|
||||
'--color-border',
|
||||
'--color-border-hover',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
|
|
@ -62,45 +62,45 @@ export const exampleDarkTheme = `
|
|||
* Type definitions for component props (for TypeScript users)
|
||||
*/
|
||||
export interface ButtonProps {
|
||||
href?: string;
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
fullWidth?: boolean;
|
||||
href?: string;
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
export interface CardProps {
|
||||
variant?: 'default' | 'hover' | 'glow' | 'bordered';
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
variant?: 'default' | 'hover' | 'glow' | 'bordered';
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
}
|
||||
|
||||
export interface Feature {
|
||||
icon: string;
|
||||
title: string;
|
||||
description: string;
|
||||
href?: string;
|
||||
icon: string;
|
||||
title: string;
|
||||
description: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export interface Testimonial {
|
||||
name: string;
|
||||
role?: string;
|
||||
company?: string;
|
||||
text: string;
|
||||
image?: string;
|
||||
rating?: number;
|
||||
name: string;
|
||||
role?: string;
|
||||
company?: string;
|
||||
text: string;
|
||||
image?: string;
|
||||
rating?: number;
|
||||
}
|
||||
|
||||
export interface FAQItem {
|
||||
question: string;
|
||||
answer: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
export interface PricingPlan {
|
||||
name: string;
|
||||
description?: string;
|
||||
price: string;
|
||||
period?: string;
|
||||
features: Array<{ text: string; included: boolean } | string>;
|
||||
cta: { text: string; href: string };
|
||||
highlighted?: boolean;
|
||||
badge?: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
price: string;
|
||||
period?: string;
|
||||
features: Array<{ text: string; included: boolean } | string>;
|
||||
cta: { text: string; href: string };
|
||||
highlighted?: boolean;
|
||||
badge?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"moduleResolution": "bundler",
|
||||
"target": "ES2022",
|
||||
"module": "ES2022"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"moduleResolution": "bundler",
|
||||
"target": "ES2022",
|
||||
"module": "ES2022"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue