reusable nav menu

This commit is contained in:
Jesse Winton
2025-04-16 14:56:26 -04:00
parent 3fc48c05ea
commit 5b90b7c785
14 changed files with 394 additions and 253 deletions

View File

@@ -71,7 +71,7 @@
"markdown-it": "^14.1.0",
"meilisearch": "^0.37.0",
"melt": "^0.29.2",
"motion": "^10.18.0",
"motion": "^12.7.3",
"node-html-parser": "^6.1.13",
"openapi-types": "^12.1.3",
"oslllo-svg-fixer": "^3.0.0",

117
pnpm-lock.yaml generated
View File

@@ -139,8 +139,8 @@ importers:
specifier: ^0.29.2
version: 0.29.2(@floating-ui/dom@1.6.13)(svelte@5.25.6)
motion:
specifier: ^10.18.0
version: 10.18.0
specifier: ^12.7.3
version: 12.7.3
node-html-parser:
specifier: ^6.1.13
version: 6.1.13
@@ -831,24 +831,6 @@ packages:
peerDependencies:
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0-next.118
'@motionone/animation@10.18.0':
resolution: {integrity: sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==}
'@motionone/dom@10.18.0':
resolution: {integrity: sha512-bKLP7E0eyO4B2UaHBBN55tnppwRnaE3KFfh3Ps9HhnAkar3Cb69kUCJY9as8LrccVYKgHA+JY5dOQqJLOPhF5A==}
'@motionone/easing@10.18.0':
resolution: {integrity: sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==}
'@motionone/generators@10.18.0':
resolution: {integrity: sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg==}
'@motionone/types@10.17.1':
resolution: {integrity: sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==}
'@motionone/utils@10.18.0':
resolution: {integrity: sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==}
'@napi-rs/nice-android-arm-eabi@1.0.1':
resolution: {integrity: sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==}
engines: {node: '>= 10'}
@@ -2263,6 +2245,20 @@ packages:
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
engines: {node: '>= 6'}
framer-motion@12.7.3:
resolution: {integrity: sha512-dNT4l5gEnUo2ytXLUBUf6AI21dZ77TMclDKE3ElaIHZ8m90nJ/NCcExW51zdSIaS0RhAS5iXcF7bEIxZe8XG2g==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
fs-extra@11.2.0:
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
engines: {node: '>=14.14'}
@@ -2383,9 +2379,6 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
hey-listen@1.0.8:
resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
highlight.js@11.11.1:
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
engines: {node: '>=12.0.0'}
@@ -2833,8 +2826,25 @@ packages:
engines: {node: '>=10'}
hasBin: true
motion@10.18.0:
resolution: {integrity: sha512-MVAZZmwM/cp77BrNe1TxTMldxRPjwBNHheU5aPToqT4rJdZxLiADk58H+a0al5jKLxkB0OdgNq6DiVn11cjvIQ==}
motion-dom@12.7.3:
resolution: {integrity: sha512-IjMt1YJHrvyvruFvmpmd6bGXXGCvmygrnvSb3aZ8KhOzF4H3PulU+cMBzH+U8TBJHjC/mnmJFRIA1Cu4vBfcBA==}
motion-utils@12.7.2:
resolution: {integrity: sha512-XhZwqctxyJs89oX00zn3OGCuIIpVevbTa+u82usWBC6pSHUd2AoNWiYa7Du8tJxJy9TFbZ82pcn5t7NOm1PHAw==}
motion@12.7.3:
resolution: {integrity: sha512-EGhzIg7vj+USH9SLNLjHRzglldWEletUZTEtBVKW7IJF+1Ig3RI5LnJmHQBNutuOIyeUbcF36MrNFT00etlc3g==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
move-file@2.1.0:
resolution: {integrity: sha512-i9qLW6gqboJ5Ht8bauZi7KlTnQ3QFpBCvMvFfEcHADKgHGeJ9BZMO7SFCTwHPV9Qa0du9DYY1Yx3oqlGt30nXA==}
@@ -4503,41 +4513,6 @@ snapshots:
nanoid: 5.1.3
svelte: 5.25.6
'@motionone/animation@10.18.0':
dependencies:
'@motionone/easing': 10.18.0
'@motionone/types': 10.17.1
'@motionone/utils': 10.18.0
tslib: 2.8.1
'@motionone/dom@10.18.0':
dependencies:
'@motionone/animation': 10.18.0
'@motionone/generators': 10.18.0
'@motionone/types': 10.17.1
'@motionone/utils': 10.18.0
hey-listen: 1.0.8
tslib: 2.8.1
'@motionone/easing@10.18.0':
dependencies:
'@motionone/utils': 10.18.0
tslib: 2.8.1
'@motionone/generators@10.18.0':
dependencies:
'@motionone/types': 10.17.1
'@motionone/utils': 10.18.0
tslib: 2.8.1
'@motionone/types@10.17.1': {}
'@motionone/utils@10.18.0':
dependencies:
'@motionone/types': 10.17.1
hey-listen: 1.0.8
tslib: 2.8.1
'@napi-rs/nice-android-arm-eabi@1.0.1':
optional: true
@@ -5949,6 +5924,12 @@ snapshots:
es-set-tostringtag: 2.1.0
mime-types: 2.1.35
framer-motion@12.7.3:
dependencies:
motion-dom: 12.7.3
motion-utils: 12.7.2
tslib: 2.8.1
fs-extra@11.2.0:
dependencies:
graceful-fs: 4.2.11
@@ -6093,8 +6074,6 @@ snapshots:
he@1.2.0: {}
hey-listen@1.0.8: {}
highlight.js@11.11.1: {}
html-escaper@3.0.3: {}
@@ -6535,12 +6514,16 @@ snapshots:
mkdirp@1.0.4: {}
motion@10.18.0:
motion-dom@12.7.3:
dependencies:
'@motionone/animation': 10.18.0
'@motionone/dom': 10.18.0
'@motionone/types': 10.17.1
'@motionone/utils': 10.18.0
motion-utils: 12.7.2
motion-utils@12.7.2: {}
motion@12.7.3:
dependencies:
framer-motion: 12.7.3
tslib: 2.8.1
move-file@2.1.0:
dependencies:

View File

@@ -1,109 +0,0 @@
<script lang="ts">
import { NavigationMenu } from 'bits-ui';
import { Icon } from '$lib/components/ui';
import { classNames } from '$lib/utils/classnames';
const products: { name: string; href: string; description: string }[] = [
{
name: 'Auth',
href: '/products/auth',
description: 'Secure login with multi-factor auth.'
},
{
name: 'Databases',
href: '/docs/products/databases',
description: 'Scalable and robust databases.'
},
{
name: 'Storage',
href: '/products/storage',
description: 'Advanced compression and encryption.'
},
{
name: 'Functions',
href: '/products/functions',
description: 'Deploy & scale serverless functions.'
},
{
name: 'Messaging',
href: '/products/messaging',
description: 'Set up a full-functioning messaging service.'
},
{
name: 'Realtime',
href: '/docs/apis/realtime',
description: 'Subscribe and react to any event.'
}
];
type ListItemProps = {
className?: string;
name: string;
href: string;
content: string;
};
</script>
{#snippet ListItem({ className, name, content, href }: ListItemProps)}
<li>
<NavigationMenu.Link
class={classNames(
'hover:bg-muted hover:text-accent-foreground focus:bg-muted focus:text-accent-foreground block space-y-1 rounded-md p-3 leading-none no-underline outline-hidden transition-colors select-none',
className
)}
{href}
>
<div class="text-sm leading-none font-medium">{name}</div>
<p class="text-muted-foreground line-clamp-2 text-sm leading-snug">
{content}
</p>
</NavigationMenu.Link>
</li>
{/snippet}
<NavigationMenu.Root>
<NavigationMenu.List class="flex items-center gap-8">
<NavigationMenu.Item>
<NavigationMenu.Trigger class="group flex items-center gap-3"
>Products <Icon
name="chevron-down"
class="relative size-4 transition-transform duration-200 group-data-[state=open]:-rotate-180"
aria-hidden="true"
/></NavigationMenu.Trigger
>
<NavigationMenu.Content
class="data-[motion=from-end]:animate-enter-from-right data-[motion=from-start]:animate-enter-from-left data-[motion=to-end]:animate-exit-to-right data-[motion=to-start]:animate-exit-to-left w-full sm:w-auto"
>
<ul
class="grid gap-3 p-3 sm:w-[400px] sm:p-6 md:w-[600px] md:grid-cols-2 lg:w-[800px]"
>
{#each products as component (component.name)}
{@render ListItem({
href: component.href,
name: component.name,
content: component.description
})}
{/each}
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link href="/docs">Docs</NavigationMenu.Link>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link href="/pricing">Pricing</NavigationMenu.Link>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link href="/contact/enterprise">Enterprise</NavigationMenu.Link>
</NavigationMenu.Item>
</NavigationMenu.List>
<div class="absolute top-full left-0 flex w-full justify-center perspective-[2000px]">
<NavigationMenu.Viewport
class={classNames(
'bg-greyscale-850 border-smooth relative w-full origin-[top_center] rounded-md border opacity-100 shadow-lg backdrop-blur-2xl transition-[width,_height] duration-200 before:rounded-md after:rounded-md',
'data-[state=open]:animate-scale-in data-[state=closed]:hidden',
'h-(--bits-navigation-menu-viewport-height) sm:w-(--bits-navigation-menu-viewport-width)'
)}
/>
</div>
</NavigationMenu.Root>

View File

@@ -0,0 +1,54 @@
<script lang="ts">
import { classNames } from '$lib/utils/classnames';
import type { SvelteHTMLElements } from 'svelte/elements';
import { navState } from './menu-state.svelte';
type Props = {
class?: string;
} & SvelteHTMLElements['button'];
const {
class: className,
'aria-label': ariaLabel = 'Open navigation menu',
onclick,
...rest
}: Props = $props();
const toggleMobileNav = () => {
navState.isOpen = !navState.isOpen;
};
</script>
<button
aria-label={ariaLabel}
class={classNames(
'focus:ring-accent flex size-7 cursor-pointer appearance-none items-center justify-center rounded-xs',
className
)}
onclick={(e) => {
toggleMobileNav();
onclick?.(e);
}}
{...rest}
>
<span class="h-4.5 w-7">
<span
class={classNames(
'dark:bg-primary relative block h-px w-6 translate-y-1 bg-gray-800 transition-all duration-200 ease-in-out',
'before:bg-primary before:absolute before:bottom-1 before:left-0 before:block before:h-px before:w-7',
'before:ease-in-out before:[transition:bottom_200ms_200ms,transform_200ms]',
'after:bg-primary after:absolute after:top-1 after:left-0 after:block after:h-px after:w-7',
'after:ease-in-out after:[transition:top_200ms_200ms,transform_200ms]',
{
'!bg-transparent': navState.isOpen,
'before:bottom-0 before:rotate-45 before:[transition:bottom_200ms,transform_200ms_200ms]':
navState.isOpen,
'after:top-0 after:-rotate-45 after:[transition:top_200ms,transform_200ms_200ms]':
navState.isOpen
}
)}
></span>
</span>
</button>
<svelte:window onresize={() => (navState.isOpen = false)} />

View File

@@ -0,0 +1,5 @@
export let navState = $state<{
isOpen: boolean;
}>({
isOpen: false
});

View File

@@ -0,0 +1,52 @@
<script lang="ts" module>
export { NavItem, Trigger };
</script>
<script lang="ts">
import { NavigationMenu } from 'bits-ui';
import { classNames } from '$lib/utils/classnames';
import type { Snippet } from 'svelte';
type TriggerProps = NavigationMenu.TriggerProps;
type ListItemProps = {
label: string;
content: string;
} & NavigationMenu.LinkProps;
type Props = { children: Snippet } & NavigationMenu.RootProps;
const { class: className, children, ...rest }: Props = $props();
</script>
<NavigationMenu.Content
class="data-[motion=from-end]:animate-enter-from-right data-[motion=from-start]:animate-enter-from-left data-[motion=to-end]:animate-exit-to-right data-[motion=to-start]:animate-exit-to-left w-full sm:w-auto"
>
<ul class="grid gap-3 p-3 sm:w-[400px] sm:p-6 md:w-[600px] md:grid-cols-2 lg:w-[800px]">
{@render children()}
</ul>
</NavigationMenu.Content>
{#snippet NavItem({ class: className, label, content, href, ...rest }: ListItemProps)}
<li>
<NavigationMenu.Link
class={classNames(
'hover:bg-muted hover:text-accent-foreground focus:bg-muted focus:text-accent-foreground block space-y-1 rounded-md p-3 leading-none no-underline outline-hidden transition-colors select-none',
className
)}
{href}
{...rest}
>
<div class="text-sm leading-none font-medium">{name}</div>
<p class="text-muted-foreground line-clamp-2 text-sm leading-snug">
{content}
</p>
</NavigationMenu.Link>
</li>
{/snippet}
{#snippet Trigger({ class: className, children, ...rest }: TriggerProps)}
<NavigationMenu.Trigger class={classNames('group flex items-center gap-3', className)} {...rest}
>{@render children?.()}</NavigationMenu.Trigger
>
{/snippet}

View File

@@ -0,0 +1,48 @@
<script lang="ts">
import MenuWrapper, { NavItem } from './menu-wrapper.svelte';
const products: { name: string; href: string; description: string }[] = [
{
name: 'Auth',
href: '/products/auth',
description: 'Secure login with multi-factor auth.'
},
{
name: 'Databases',
href: '/docs/products/databases',
description: 'Scalable and robust databases.'
},
{
name: 'Storage',
href: '/products/storage',
description: 'Advanced compression and encryption.'
},
{
name: 'Functions',
href: '/products/functions',
description: 'Deploy & scale serverless functions.'
},
{
name: 'Messaging',
href: '/products/messaging',
description: 'Set up a full-functioning messaging service.'
},
{
name: 'Realtime',
href: '/docs/apis/realtime',
description: 'Subscribe and react to any event.'
}
];
</script>
<MenuWrapper>
<ul class="grid gap-3 p-3 sm:w-[400px] sm:p-6 md:w-[600px] md:grid-cols-2 lg:w-[800px]">
{#each products as product (product.name)}
{@render NavItem({
href: product.href,
label: product.name,
content: product.description
})}
{/each}
</ul>
</MenuWrapper>

View File

@@ -0,0 +1,58 @@
<script lang="ts">
import { Collapsible } from 'bits-ui';
import { Icon } from '$lib/components/ui';
import { classNames } from '$lib/utils/classnames';
const products: { name: string; href: string; description: string }[] = [
{
name: 'Auth',
href: '/products/auth',
description: 'Secure login with multi-factor auth.'
},
{
name: 'Databases',
href: '/docs/products/databases',
description: 'Scalable and robust databases.'
},
{
name: 'Storage',
href: '/products/storage',
description: 'Advanced compression and encryption.'
},
{
name: 'Functions',
href: '/products/functions',
description: 'Deploy & scale serverless functions.'
},
{
name: 'Messaging',
href: '/products/messaging',
description: 'Set up a full-functioning messaging service.'
},
{
name: 'Realtime',
href: '/docs/apis/realtime',
description: 'Subscribe and react to any event.'
}
];
type CollapsibleItemProps = {
className?: string;
label: string;
content: string;
};
</script>
{#snippet ListItem({ className, label }: CollapsibleItemProps)}
<Collapsible.Root>
<Collapsible.Trigger>{label}</Collapsible.Trigger>
<Collapsible.Content
class={classNames(
'hover:bg-muted hover:text-accent-foreground focus:bg-muted focus:text-accent-foreground block space-y-1 rounded-md p-3 leading-none no-underline outline-hidden transition-colors select-none',
className
)}
>
<div class="text-sm leading-none font-medium">{name}</div>
</Collapsible.Content>
</Collapsible.Root>
{/snippet}

View File

@@ -0,0 +1,67 @@
<script lang="ts">
import { NavigationMenu } from 'bits-ui';
import { classNames } from '$lib/utils/classnames';
import type { Component, SvelteComponent } from 'svelte';
import ProductMenu from './menus/product-menu.svelte';
import { Icon } from '$lib/components/ui';
type NavItem = {
label: string;
href?: string;
menu?: Component;
};
export const navItems: Array<NavItem> = [
{ label: 'Products', menu: ProductMenu },
{
label: 'Docs',
href: '/docs'
},
{
label: 'Pricing',
href: '/pricing'
},
{
label: 'Enterprise',
href: '/enterprise'
}
];
type Props = NavigationMenu.RootProps;
const { class: className, ...rest }: Props = $props();
</script>
<NavigationMenu.Root class={className} {...rest}>
<NavigationMenu.List class="flex items-center gap-8">
{#each navItems as item}
{#if item.menu}
{@const Menu = item.menu}
<NavigationMenu.Item>
<NavigationMenu.Trigger class="group flex items-center gap-3"
>{item.label}
<Icon
name="chevron-down"
class="relative size-4 transition-transform duration-200 group-data-[state=open]:-rotate-180"
aria-hidden="true"
/></NavigationMenu.Trigger
>
<Menu />
</NavigationMenu.Item>
{:else}
<NavigationMenu.Item>
<NavigationMenu.Link href={item.href ?? ''}>{item.label}</NavigationMenu.Link>
</NavigationMenu.Item>
{/if}
{/each}
</NavigationMenu.List>
<div class="absolute top-full left-0 flex w-full justify-center perspective-[2000px]">
<NavigationMenu.Viewport
class={classNames(
'bg-greyscale-850 border-smooth relative w-full origin-[top_center] rounded-b-md border-x border-b opacity-100 shadow-lg backdrop-blur-2xl transition-[width,_height] duration-200 before:rounded-md after:rounded-md',
'data-[state=open]:animate-scale-in data-[state=closed]:hidden',
'h-(--bits-navigation-menu-viewport-height) sm:w-(--bits-navigation-menu-viewport-width)'
)}
/>
</div>
</NavigationMenu.Root>

View File

@@ -1,5 +1,7 @@
<script>
import MainNavigation from './main-navigation.svelte';
<script lang="ts">
import { Button } from '../ui';
import HamburgerMenu from './navigation/hamburger-menu.svelte';
import PrimaryNav from './navigation/primary-nav.svelte';
</script>
<header
@@ -15,8 +17,10 @@
width="130"
/></a
>
<MainNavigation />
<button class="web-button">Start building for free</button>
<PrimaryNav class="hidden md:block" />
<Button class="hidden! md:flex!">Start building for free</Button>
<HamburgerMenu class="block md:hidden" />
</div>
</header>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import MainFooter from '$lib/components/layout/main-footer.svelte';
import MainHeader from '$lib/components/layout/main-header.svelte';
import MainFooter from '$lib/components/layout/site-footer.svelte';
import MainHeader from '$lib/components/layout/site-header.svelte';
import SubFooter from '$lib/components/layout/sub-footer.svelte';
import type { Snippet } from 'svelte';

View File

@@ -57,53 +57,53 @@
Create: 5
} as const;
$effect(() => {
let timeout: NodeJS.Timeout;
switch (step) {
case FunctionsState.Stale:
console.log('stale');
timeout = setTimeout(() => {
step = FunctionsState.Generate;
}, 500);
return () => clearTimeout(timeout);
case FunctionsState.Generate:
console.log('generate');
timeout = setTimeout(() => {
step = FunctionsState.Send;
}, 2000);
return () => clearTimeout(timeout);
case FunctionsState.Send:
console.log('send');
timeout = setTimeout(() => {
step = FunctionsState.Update;
}, 1000);
return () => clearTimeout(timeout);
case FunctionsState.Update:
console.log('update');
timeout = setTimeout(() => {
step = FunctionsState.Delete;
}, 200);
return () => clearTimeout(timeout);
case FunctionsState.Delete:
console.log('delete');
timeout = setTimeout(() => {
step = FunctionsState.Create;
}, 100);
return () => clearTimeout(timeout);
case FunctionsState.Create:
console.log('create');
timeout = setTimeout(() => {
step = FunctionsState.Stale;
}, 800);
return () => clearTimeout(timeout);
default:
console.log('stale');
timeout = setTimeout(() => {
step = FunctionsState.Generate;
}, 3000);
return () => clearTimeout(timeout);
}
});
// $effect(() => {
// let timeout: NodeJS.Timeout;
// switch (step) {
// case FunctionsState.Stale:
// console.log('stale');
// timeout = setTimeout(() => {
// step = FunctionsState.Generate;
// }, 500);
// return () => clearTimeout(timeout);
// case FunctionsState.Generate:
// console.log('generate');
// timeout = setTimeout(() => {
// step = FunctionsState.Send;
// }, 2000);
// return () => clearTimeout(timeout);
// case FunctionsState.Send:
// console.log('send');
// timeout = setTimeout(() => {
// step = FunctionsState.Update;
// }, 1000);
// return () => clearTimeout(timeout);
// case FunctionsState.Update:
// console.log('update');
// timeout = setTimeout(() => {
// step = FunctionsState.Delete;
// }, 200);
// return () => clearTimeout(timeout);
// case FunctionsState.Delete:
// console.log('delete');
// timeout = setTimeout(() => {
// step = FunctionsState.Create;
// }, 100);
// return () => clearTimeout(timeout);
// case FunctionsState.Create:
// console.log('create');
// timeout = setTimeout(() => {
// step = FunctionsState.Stale;
// }, 800);
// return () => clearTimeout(timeout);
// default:
// console.log('stale');
// timeout = setTimeout(() => {
// step = FunctionsState.Generate;
// }, 3000);
// return () => clearTimeout(timeout);
// }
// });
</script>
<div

View File

@@ -7,7 +7,8 @@
<div class="relative flex min-h-[80vh] flex-col items-center md:flex-row">
<div
class={classNames(
'hero animate-lighting absolute top-0 left-0 z-0 h-screen w-full -translate-x-[25%] translate-y-8 rotate-25 overflow-hidden blur-3xl',
'animate-lighting absolute top-0 left-0 -z-10 h-screen w-[200vw] -translate-x-[25%] translate-y-8 rotate-25 overflow-hidden blur-3xl md:w-full',
'bg-[image:radial-gradient(ellipse_390px_50px_at_10%_30%,_rgba(254,_149,_103,_0.2)_0%,_rgba(254,_149,_103,_0)_70%),_radial-gradient(ellipse_1100px_170px_at_15%_40%,rgba(253,_54,_110,_0.08)_0%,_rgba(253,_54,_110,_0)_70%),_radial-gradient(ellipse_1200px_180px_at_30%_30%,_rgba(253,_54,_110,_0.08)_0%,_rgba(253,_54,_110,_0)_70%)]',
'bg-position-[0%_0%]'
)}
style:--speed="500ms"
@@ -30,25 +31,3 @@
</div>
<Dashboard />
</div>
<style>
.hero {
--first-gradient: radial-gradient(
ellipse 390px 50px at 10% 30%,
rgba(254, 149, 103, 0.4) 0%,
rgba(254, 149, 103, 0) 70%
);
--second-gradient: radial-gradient(
ellipse 1100px 170px at 15% 40%,
rgba(253, 54, 110, 0.16) 0%,
rgba(253, 54, 110, 0) 70%
);
--third-gradient: radial-gradient(
ellipse 1200px 180px at 30% 30%,
rgba(253, 54, 110, 0.16) 0%,
rgba(253, 54, 110, 0) 70%
);
background-image: var(--first-gradient), var(--second-gradient), var(--third-gradient);
}
</style>