iftrace: rebuild site with fulldev ui astro

This commit is contained in:
root 2025-12-30 14:55:45 +00:00
parent 8d3610b5d8
commit e70d1ead43
395 changed files with 23806 additions and 15151 deletions

18
ifttt-src/.gitignore vendored Normal file
View file

@ -0,0 +1,18 @@
node_modules/
.astro/
# build output (written to ../ifttt)
dist/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# env
.env
.env.*
# OS/editor
.DS_Store

View file

@ -0,0 +1,14 @@
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "astro/config";
export default defineConfig({
site: "https://infrafabric.io",
output: "static",
outDir: "../ifttt",
build: {
assets: "assets/_astro",
},
vite: {
plugins: [tailwindcss()],
},
});

6264
ifttt-src/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

25
ifttt-src/package.json Normal file
View file

@ -0,0 +1,25 @@
{
"name": "iftrace-site",
"private": true,
"type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.14",
"astro": "^5.16.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-static": "^0.546.0",
"sharp": "^0.34.5",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.14",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@types/node": "^24.8.1",
"typescript": "^5.9.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View file

@ -0,0 +1,92 @@
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1024"
height="1024"
viewBox="0 0 512 512"
role="img"
aria-labelledby="title desc"
>
<title id="title">if. mark</title>
<desc id="desc">Stylized “if.” mark with subtle gradient and shadow.</desc>
<defs>
<linearGradient id="ifBlue" x1="70" y1="0" x2="440" y2="0" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0b253b" />
<stop offset="0.44" stop-color="#164d7a" />
<stop offset="0.56" stop-color="#1b5a8e" />
<stop offset="1" stop-color="#0b253b" />
</linearGradient>
<filter id="ifShadow" x="-40%" y="-40%" width="180%" height="180%">
<feOffset in="SourceAlpha" dx="0" dy="14" result="off" />
<feGaussianBlur in="off" stdDeviation="12" result="blur" />
<feColorMatrix
in="blur"
type="matrix"
values="0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0.28 0"
result="shadow"
/>
<feMerge>
<feMergeNode in="shadow" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<!--
Mark group:
- i (stem + dot)
- f (stem + top bar + crossbar)
- period dot
-->
<g filter="url(#ifShadow)" transform="translate(75 15)" fill="url(#ifBlue)">
<!-- i -->
<circle cx="35" cy="125" r="32" />
<rect x="0" y="170" width="70" height="220" rx="8" />
<!-- f -->
<rect x="120" y="140" width="86" height="250" rx="10" />
<rect x="120" y="110" width="150" height="90" rx="45" />
<rect x="120" y="240" width="140" height="80" rx="28" />
<!-- . -->
<circle cx="330" cy="358" r="32" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,72 @@
[
{
"text": "Footnotes aren't decorations. They're load-bearing walls.",
"source": "IF.Trace paper (v2.3)",
"href": "https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.html"
},
{
"text": "If there's no IF.Trace trace, it didn't happen—or shouldn't be trusted.",
"source": "IF.Trace doctrine",
"href": "https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.html"
},
{
"text": "No trace, no trust. Simple as that.",
"source": "IF.Trace doctrine",
"href": "https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.html"
},
{
"text": "Trust isn't claimed. It's proven.",
"source": "IF.Trace paper (v2.3)",
"href": "https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.html"
},
{
"text": "We dont claim trust. We publish receipts.",
"source": "IF.Trace stance",
"href": "https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.html"
},
{
"text": "Your auditor shouldnt need your credentials.",
"source": "IF.Trace public receipts",
"href": "https://infrafabric.io/static/hosted/ifttt/"
},
{
"text": "Third parties verify. We just publish the math.",
"source": "IF.Trace posture",
"href": "https://infrafabric.io/static/hosted/ifttt/"
},
{
"text": "Receipts must be readable and verifiable without credentials.",
"source": "IF.Trace public receipt surface",
"href": "https://infrafabric.io/static/hosted/ifttt/"
},
{
"text": "Design constraint: black/white clarity. No “maybe”.",
"source": "IF.Trace ops (full stack)",
"href": "https://infrafabric.io/static/hosted/ifttt/api/"
},
{
"text": "We do not claim “quantum-secure”. We claim “quantum-ready”.",
"source": "IF.Trace tech stack",
"href": "https://infrafabric.io/static/hosted/ifttt/api/"
},
{
"text": "The stenographer doesn't make the therapy cold. The stenographer makes it accountable.",
"source": "IF.emotion on IF.Trace",
"href": "https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.html"
},
{
"text": "That's the moat.",
"source": "IF.Trace paper (v2.3)",
"href": "https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.html"
},
{
"text": "The moat is not the AI. The moat is the proof.",
"source": "IF.Trace paper (v2.3)",
"href": "https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.html"
},
{
"text": "If its not verifiable, label it as a gap. Do not endorse it.",
"source": "IF.Trace stance",
"href": "https://infrafabric.io/static/hosted/ifttt/"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 KiB

View file

@ -0,0 +1,66 @@
# IF.Trace Website — External Review Packet
- Generated: `2025-12-30`
- Owner: Danny Stocker (`ds@infrafabric.io`)
This packet is for external reviewers to evaluate the IF.Trace website (copy, structure, and claims) without needing to crawl or guess context.
## What this is
- A public-facing explanation of IF.Trace: “confidential documents → open verification”.
- A set of pages that describe who this is for, how verification works, and what the proof does/does not claim.
- A request for critical feedback on clarity, honesty, and missing pieces.
## What this is not
- Not a claim that IF.Trace guarantees compliance, intent, or correctness of interpretation.
- Not a request for marketing language or hype.
- Not a request to “agree” or be supportive — please be direct.
## Response metadata (required)
Please include:
- `llm_name`:
- `probable_model`:
- `cutoff_date`:
- `response_date_utc`:
- `web_access_used`: yes/no (list any URLs you relied on)
## Live entry points
- https://infrafabric.io/
- https://infrafabric.io/verticals/
- https://infrafabric.io/pricing/
- https://infrafabric.io/api/
- https://infrafabric.io/whitepaper/
- https://infrafabric.io/about/
- https://infrafabric.io/governance/
- https://infrafabric.io/fr/
## Core demo links (used throughout)
- https://infrafabric.io/static/trace/6qRgcR01kw_qNo63Dbs_ob9n
- https://infrafabric.io/static/pack/6qRgcR01kw_qNo63Dbs_ob9n
- https://infrafabric.io/static/pack/6qRgcR01kw_qNo63Dbs_ob9n.md
- https://infrafabric.io/static/hosted/review/trace-bundles/b6547c03/index.html
- https://infrafabric.io/static/hosted/iftrace.html
- https://infrafabric.io/static/hosted/iftrace.py
- https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.html
## Questions for reviewers
1. In your own words: what does IF.Trace do?
2. What feels unclear, hand-wavy, or like “compliance theater”?
3. Where do we over-claim (even accidentally)?
4. What is missing to make a third party comfortable verifying a claim?
5. Which page is strongest? Which page is weakest?
6. Does the site make it obvious what is verified vs not verified?
7. What would you remove to make it more honest?
8. What would you add to make it more useful for real reviewers (audit/legal/security/research)?
## Bias notice
This packet intentionally avoids conversion stats, testimonials, and “success stories”.
Please focus on falsifiability, clarity, and what a skeptical third party would challenge.

View file

@ -0,0 +1,64 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import {
Section,
SectionContent,
SectionMedia,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
title?: string
description?: string
item?: {
image?: {
src: string
alt: string
}
title?: string
description?: string
}
image?: {
src: string
alt: string
}
}
const { class: className, id, title, description, image, item } = Astro.props
---
<Section class={className} id={id} style={{ "--container": "672px" }}>
<SectionContent>
<SectionProse>
{title && <h1>{title}</h1>}
{description && <p>{description}</p>}
</SectionProse>
<Item class="p-0">
<ItemMedia>
<Avatar class="size-10">
<AvatarImage src={item?.image?.src} alt={item?.image?.alt} />
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
<SectionMedia>
<Image sizes="(min-width: 672px) 672px, 100vw" priority {...image} />
</SectionMedia>
<SectionProse>
<slot />
</SectionProse>
</Section>

View file

@ -0,0 +1,64 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import {
Section,
SectionContent,
SectionMedia,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
title?: string
description?: string
item?: {
image?: {
src: string
alt: string
}
title?: string
description?: string
}
image?: {
src: string
alt: string
}
}
const { class: className, id, title, description, image, item } = Astro.props
---
<Section class={className} id={id} style={{ "--container": "672px" }}>
<SectionContent class="items-center">
<SectionProse class="text-center text-balance">
{title && <h1>{title}</h1>}
{description && <p>{description}</p>}
</SectionProse>
<Item class="p-0">
<ItemMedia>
<Avatar class="size-10">
<AvatarImage src={image?.src} alt={image?.alt} />
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
<SectionMedia>
<Image sizes="(min-width: 672px) 672px, 100vw" priority {...image} />
</SectionMedia>
<SectionProse>
<slot />
</SectionProse>
</Section>

View file

@ -0,0 +1,103 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
href?: string
title?: string
description?: string
item?: {
image?: {
src: string
alt: string
}
title?: string
description?: string
}
image?: {
src: string
alt: string
}
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid>
{
items?.map(({ title, description, image, href, item }) => (
<Tile href={href}>
<TileMedia class="aspect-video">
<Image sizes="600px" {...image} />
</TileMedia>
<TileContent>
<TileTitle class="line-clamp-2">{title}</TileTitle>
<TileDescription class="line-clamp-2">
{description}
</TileDescription>
</TileContent>
<Item class="p-0">
<ItemMedia>
<Avatar class="size-10">
<AvatarImage src={item?.image?.src} alt={item?.image?.alt} />
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,104 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
href?: string
title?: string
description?: string
item?: {
image?: {
src: string
alt: string
}
title?: string
description?: string
}
image?: {
src: string
alt: string
}
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit class="@5xl:grid-cols-[2fr_3fr]">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid size="lg">
{
items?.map(({ title, description, image, href, item }) => (
<Tile href={href}>
<TileMedia>
<Image sizes="820px" {...image} />
</TileMedia>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
<Item class="p-0">
<ItemMedia>
<Avatar class="size-10">
<AvatarImage src={item?.image?.src} alt={item?.image?.alt} />
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
</Tile>
))
}
</SectionGrid>
</SectionSplit>
</Section>

View file

@ -0,0 +1,101 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import {
Section,
SectionActions,
SectionGrid,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
href?: string
title?: string
description?: string
item?: {
image?: {
src: string
alt: string
}
title?: string
description?: string
}
image?: {
src: string
alt: string
}
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSpread class="@5xl:items-end">
<SectionProse>
<slot />
</SectionProse>
<SectionActions class="-mx-3 -my-2">
{
links?.map(({ icon, text, ...link }) => (
<Button variant="link" {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionSpread>
<SectionGrid>
{
items?.map(({ title, description, image, href, item }) => (
<Tile href={href}>
<TileMedia>
<Image sizes="600px" {...image} />
</TileMedia>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
<Item class="p-0">
<ItemMedia>
<Avatar class="size-10">
<AvatarImage src={item?.image?.src} alt={item?.image?.alt} />
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,103 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
href?: string
title?: string
description?: string
item?: {
image?: {
src: string
alt: string
}
title?: string
description?: string
}
image?: {
src: string
alt: string
}
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid>
{
items?.map(({ title, description, image, href, item }) => (
<Tile href={href} variant="floating">
<TileMedia class="-mx-6 -mt-6 aspect-video">
<Image sizes="600px" {...image} />
</TileMedia>
<TileContent>
<TileTitle class="line-clamp-2">{title}</TileTitle>
<TileDescription class="line-clamp-2">
{description}
</TileDescription>
</TileContent>
<Item class="p-0">
<ItemMedia>
<Avatar class="size-10">
<AvatarImage src={item?.image?.src} alt={item?.image?.alt} />
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,25 @@
---
import {
Banner,
BannerContent,
BannerDescription,
BannerTitle,
} from "@/components/ui/banner"
import { Icon } from "@/components/ui/icon"
interface Props {
icon?: string
title?: string
description?: string
}
const { icon, title, description } = Astro.props
---
<Banner>
<BannerContent>
<Icon class="size-5" name={icon} />
<BannerTitle>{title}</BannerTitle>
<BannerDescription>{description}</BannerDescription>
</BannerContent>
</Banner>

View file

@ -0,0 +1,25 @@
---
import {
Banner,
BannerContent,
BannerDescription,
BannerTitle,
} from "@/components/ui/banner"
import { Icon } from "@/components/ui/icon"
interface Props {
icon?: string
title?: string
description?: string
}
const { icon, title, description } = Astro.props
---
<Banner>
<BannerContent class="justify-center pr-6">
<Icon class="size-5" name={icon} />
<BannerTitle>{title}</BannerTitle>
<BannerDescription>{description}</BannerDescription>
</BannerContent>
</Banner>

View file

@ -0,0 +1,66 @@
---
import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Section, SectionContent } from "@/components/ui/section"
interface Props {
class?: string
id?: string
[key: string]: any
}
const { class: className, id, ...props } = Astro.props
const allBlocks = import.meta.glob("src/components/blocks/**/*.astro", {
eager: true,
})
const sortedBlocks = Object.entries(allBlocks).sort(([a], [b]) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" })
)
---
<Section class={cn("@container flex flex-col px-4 py-8", className)} id={id}>
<SectionContent
class="grid grid-cols-1 gap-6 @3xl:grid-cols-2 @5xl:grid-cols-3"
>
{
sortedBlocks.map(([path, block]: [string, any]) => {
const id = path.split("/").pop()?.split(".")[0]
const BlockComponent = block.default
return BlockComponent ? (
<div class="no-scrollbar flex flex-col gap-2">
<div class="flex flex-row items-center justify-between">
<Badge variant="secondary" href={`/blocks/${id}/`}>
{id}
</Badge>
</div>
<div
id={id}
class="relative overflow-hidden rounded-lg border-2 border-dashed"
>
<div class="bg-background no-scrollbar relative aspect-video overflow-x-hidden">
<div class="@container h-full w-[200%] origin-top-left scale-50">
<BlockComponent {...props}>
<Fragment set:html={props?.html} />
</BlockComponent>
</div>
</div>
</div>
</div>
) : null
})
}
</SectionContent>
</Section>
<style>
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
</style>

View file

@ -0,0 +1,95 @@
---
import fs from "fs"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionContent,
SectionGrid,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
import Block from "@/components/block.astro"
interface Props {
class?: string
id?: string
title?: string
description?: string
items?: any[]
}
const { class: className, id, title, description, items } = Astro.props
---
<Section class={cn("", className)} id={id}>
<SectionSpread>
<SectionContent>
<SectionProse>
{title && <h1>{title}</h1>}
{description && <p>{description}</p>}
</SectionProse>
</SectionContent>
</SectionSpread>
<SectionGrid class="grid-cols-1 sm:grid-cols-1 @6xl:grid-cols-2">
{
items?.map(({ block, href, ...props }) => {
if (!block) return null
const fileContent = fs.readFileSync(
`src/components/blocks/${block}.astro`,
"utf8"
)
return (
<div class="group relative aspect-video overflow-hidden rounded-lg border">
<div class="bg-background @container h-full w-[400%] origin-top-left scale-25 @xl:w-[200%] @xl:scale-50">
<Block block={block} {...props} />
</div>
<div class="bg-background absolute bottom-0 left-0 hidden w-full items-center justify-between border-t p-4 text-sm shadow-lg group-hover:flex">
<Button variant="outline" size="sm" href={href}>
<Icon name="eye" class="[&:is(.copied_&)]:hidden" />
{block}
</Button>
<div class="flex gap-2">
<Button
class="copy"
variant="outline"
size="sm"
data-copy={fileContent}
>
<Icon name="copy" class="[&:is(.copied_&)]:hidden" />
<Icon name="check" class="hidden [&:is(.copied_&)]:block" />
Copy code
</Button>
<Button
class="copy"
variant="outline"
size="sm"
data-copy={`npx shadcn@latest add @fulldev/${block}`}
>
<Icon name="terminal" class="[&:is(.copied_&)]:hidden" />
<Icon name="check" class="hidden [&:is(.copied_&)]:block" />
npx shadcn add
</Button>
</div>
</div>
</div>
)
})
}
</SectionGrid>
</Section>
<script>
const buttons = document.querySelectorAll("button.copy")
buttons.forEach((button) => {
button.addEventListener("click", () => {
const copy = button.getAttribute("data-copy") || ""
navigator.clipboard.writeText(copy)
button.classList.add("copied")
})
})
sessionStorage.removeItem("closedBanners")
</script>

View file

@ -0,0 +1,93 @@
---
import fs from "fs"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Marquee, MarqueeContent } from "@/components/ui/marquee"
import {
Section,
SectionContent,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
import Block from "@/components/block.astro"
interface Props {
class?: string
id?: string
title?: string
description?: string
items?: any[]
}
const { class: className, id, items } = Astro.props
---
<Section class={cn("", className)} id={id}>
<SectionSpread>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
</SectionContent>
</SectionSpread>
<Marquee class="-mx-(--section-px)">
<MarqueeContent pauseOnHover>
{
items?.map(({ block, href, ...props }) => {
if (!block) return null
const fileContent = fs.readFileSync(
`src/components/blocks/${block}.astro`,
"utf8"
)
return (
<div class="group relative aspect-video w-[320px] overflow-hidden rounded-lg border @5xl:w-[640px]">
<div class="bg-background @container h-full w-[400%] origin-top-left scale-25 @5xl:w-[200%] @5xl:scale-50">
<Block block={block} {...props} />
</div>
<div class="bg-background absolute bottom-0 left-0 hidden w-full items-center justify-between border-t p-4 text-sm shadow-lg group-hover:flex">
<h3 class="text-sm font-medium">
<a href={href}>{block}</a>
</h3>
<div class="flex gap-2">
<Button
class="copy"
variant="outline"
size="sm"
data-copy={fileContent}
>
<Icon name="copy" class="[&:is(.copied_&)]:hidden" />
<Icon name="check" class="hidden [&:is(.copied_&)]:block" />
Copy code
</Button>
<Button
class="copy"
variant="outline"
size="sm"
data-copy={`npx shadcn add @fulldev/${block}`}
>
<Icon name="terminal" class="[&:is(.copied_&)]:hidden" />
<Icon name="check" class="hidden [&:is(.copied_&)]:block" />
npx shadcn add
</Button>
</div>
</div>
</div>
)
})
}
</MarqueeContent>
</Marquee>
</Section>
<script>
const buttons = document.querySelectorAll("button.copy")
buttons.forEach((button) => {
button.addEventListener("click", () => {
const copy = button.getAttribute("data-copy") || ""
navigator.clipboard.writeText(copy)
button.classList.add("copied")
})
})
</script>

View file

@ -0,0 +1,110 @@
---
import fs from "fs"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Marquee, MarqueeContent } from "@/components/ui/marquee"
import {
Section,
SectionContent,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
import Block from "@/components/block.astro"
interface Props {
class?: string
id?: string
title?: string
description?: string
items?: any[]
}
const { class: className, id, items } = Astro.props
---
<Section class={cn("", className)} id={id}>
<SectionSpread>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
</SectionContent>
</SectionSpread>
<div class="flex flex-col gap-6">
{
(["left", "right"] as const).map((direction) => (
<Marquee class="-mx-(--section-px) mask-x-from-95%">
<MarqueeContent
class="animation-duration-80000"
direction={direction}
pauseOnHover
>
{items?.map(({ block, href, ...props }) => {
if (!block) return null
const fileContent = fs.readFileSync(
`src/components/blocks/${block}.astro`,
"utf8"
)
return (
<div class="group relative aspect-video w-[320px] overflow-hidden rounded-lg border @5xl:w-[640px]">
<div class="bg-background @container h-full w-[400%] origin-top-left scale-25 @5xl:w-[200%] @5xl:scale-50">
<Block block={block} {...props} />
</div>
<div class="bg-background absolute bottom-0 left-0 hidden w-full items-center justify-between border-t p-4 text-sm shadow-lg group-hover:flex">
<h3 class="text-sm font-medium">
<a href={href}>{block}</a>
</h3>
<div class="flex gap-2">
<Button
class="copy"
variant="outline"
size="sm"
data-copy={fileContent}
>
<Icon name="copy" class="[&:is(.copied_&)]:hidden" />
<Icon
name="check"
class="hidden [&:is(.copied_&)]:block"
/>
Copy code
</Button>
<Button
class="copy"
variant="outline"
size="sm"
data-copy={`npx shadcn@latest add @fulldev/${block}`}
>
<Icon
name="terminal"
class="[&:is(.copied_&)]:hidden"
/>
<Icon
name="check"
class="hidden [&:is(.copied_&)]:block"
/>
npx shadcn add
</Button>
</div>
</div>
</div>
)
})}
</MarqueeContent>
</Marquee>
))
}
</div>
</Section>
<script>
const buttons = document.querySelectorAll("button.copy")
buttons.forEach((button) => {
button.addEventListener("click", () => {
const copy = button.getAttribute("data-copy") || ""
navigator.clipboard.writeText(copy)
button.classList.add("copied")
})
})
</script>

View file

@ -0,0 +1,55 @@
---
import { AutoForm } from "@/components/ui/auto-form"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import {
Section,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
form?: any
items?: {
icon?: string
title?: string
description?: string
href?: string
}[]
}
const { class: className, id, form, items } = Astro.props
---
<Section class={className} id={id} style="--section-width: 672px;">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
</SectionContent>
<SectionGrid>
{
items?.map(({ icon, title, description, href }) => (
<Item href={href} variant="outline">
<ItemMedia variant="icon">
<Icon href={href} name={icon} />
</ItemMedia>
<ItemContent>
<ItemTitle>{title}</ItemTitle>
<ItemDescription>{description}</ItemDescription>
</ItemContent>
</Item>
))
}
</SectionGrid>
<AutoForm {...form} />
</Section>

View file

@ -0,0 +1,60 @@
---
import { AutoForm } from "@/components/ui/auto-form"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import {
Section,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
form?: any
items?: {
icon?: string
title?: string
description?: string
href?: string
}[]
}
const { class: className, id, form, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit
class="items-start @5xl:grid-cols-[3fr_2fr] @5xl:*:sticky @5xl:*:top-[calc(var(--section-py)+var(--header-height,0px))]"
>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionGrid class="flex max-w-md flex-col">
{
items?.map(({ icon, title, description, href }) => (
<Item href={href} variant="muted">
<ItemMedia variant="icon">
<Icon href={href} name={icon} />
</ItemMedia>
<ItemContent>
<ItemTitle>{title}</ItemTitle>
<ItemDescription>{description}</ItemDescription>
</ItemContent>
</Item>
))
}
</SectionGrid>
</SectionContent>
<AutoForm {...form} />
</SectionSplit>
</Section>

View file

@ -0,0 +1,69 @@
---
import { AutoForm } from "@/components/ui/auto-form"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import {
Section,
SectionContent,
SectionGrid,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
form?: any
items?: {
icon?: string
title?: string
description?: string
href?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, form, items, image } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit class="gap-x-32">
<SectionContent class="gap-16">
<SectionProse>
<slot />
</SectionProse>
<SectionGrid class="max-w-2xl grid-cols-1!">
{
items?.map(({ icon, title, description, href }) => (
<Item href={href} variant="outline">
<ItemMedia variant="icon">
<Icon href={href} name={icon} />
</ItemMedia>
<ItemContent>
<ItemTitle>{title}</ItemTitle>
<ItemDescription>{description}</ItemDescription>
</ItemContent>
</Item>
))
}
</SectionGrid>
<AutoForm {...form} />
</SectionContent>
<SectionMedia
class="-mr-(--section-px) p-1 @5xl:z-90 @5xl:-mt-[calc(var(--section-py)+var(--header-height,0px))]"
>
<Image {...image} />
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,62 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import { List, ListItem } from "@/components/ui/list"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, list, links, image } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia>
<Image sizes="(min-width: 1536px) 1536px, 100vw" {...image} />
</SectionMedia>
</Section>

View file

@ -0,0 +1,68 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import { List, ListItem } from "@/components/ui/list"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, list, links, image } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit class="@5xl:items-center">
<SectionContent class="max-w-xl">
<SectionProse>
<slot />
</SectionProse>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia>
<Image
sizes="(min-width: 1536px) 768px, (min-width: 1024px) 50vw, 100vw"
{...image}
/>
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,67 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import { List, ListItem } from "@/components/ui/list"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, list, links, image } = Astro.props
---
<Section class={className} id={id}>
<SectionSpread class="@5xl:items-end">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
</SectionContent>
<SectionContent>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
</SectionSpread>
<SectionMedia>
<Image sizes="(min-width: 1536px) 1536px, 100vw" {...image} />
</SectionMedia>
</Section>

View file

@ -0,0 +1,68 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import { List, ListItem } from "@/components/ui/list"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, list, links, image } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit class="@5xl:grid-cols-[1fr_2fr] @5xl:items-center">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia>
<Image
sizes="(min-width: 1536px) 1024px, (min-width: 1024px) 66vw, 100vw"
{...image}
/>
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,76 @@
---
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import { List, ListItem } from "@/components/ui/list"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, list, links, image } = Astro.props
---
<Section
class={cn(
"border-primary/20 my-0 -mt-px rounded-none shadow-none",
className
)}
id={id}
variant="floating"
>
<SectionSplit class="@5xl:items-center">
<SectionContent class="max-w-xl">
<SectionProse>
<slot />
</SectionProse>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "outline" : "ghost"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia>
<Image
sizes="(min-width: 1536px) 768px, (min-width: 1024px) 50vw, 100vw"
{...image}
/>
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,76 @@
---
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import { List, ListItem } from "@/components/ui/list"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, list, links, image } = Astro.props
---
<Section
class={cn(
"border-primary/20 my-0 -mt-px rounded-none shadow-none",
className
)}
id={id}
variant="floating"
>
<SectionSplit class="@5xl:items-center">
<SectionContent class="max-w-xl">
<SectionProse>
<slot />
</SectionProse>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "outline" : "ghost"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia class="@5xl:order-first">
<Image
sizes="(min-width: 1536px) 768px, (min-width: 1024px) 50vw, 100vw"
{...image}
/>
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,78 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
}
const { class: className, id, links, item } = Astro.props
---
<Section class={className} id={id} variant="floating">
<SectionContent class="items-center">
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
<SectionProse class="text-center" size="lg">
<slot />
</SectionProse>
<SectionActions class="justify-center">
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
</Section>

View file

@ -0,0 +1,88 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
}
const { class: className, id, links, image, item } = Astro.props
---
<Section class={className} id={id} variant="floating">
<SectionSplit class="@5xl:items-center">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-4">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-10 ring-2">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent>
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
<SectionMedia
class="-mx-(--section-px) rounded-none @5xl:-my-(--section-py) @5xl:-ml-0"
>
<Image sizes="(1536px) 768px, (1024px) 50vw, 100vw" {...image} />
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,82 @@
---
import { cn } from "@/lib/utils"
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
}
const { class: className, id, links, item } = Astro.props
---
<Section
class={cn("bg-accent/50 shadow-none", className)}
id={id}
variant="floating"
>
<SectionSpread class="@5xl:items-center">
<SectionProse>
<slot />
</SectionProse>
<SectionContent>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "secondary"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
</SectionSpread>
</Section>

View file

@ -0,0 +1,83 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
image?: {
src: string
alt: string
}
}
const { class: className, id, links, item, image } = Astro.props
---
<Section class={className} id={id} variant="floating">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription class="text-foreground">
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
<SectionMedia class="absolute inset-0 size-full opacity-50">
<Image sizes="(1536px) 1536px, 100vw" {...image} />
</SectionMedia>
</Section>

View file

@ -0,0 +1,88 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
}
const { class: className, id, links, image, item } = Astro.props
---
<Section class={className} id={id} variant="floating">
<SectionSpread class="@5xl:items-center">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
</SectionContent>
<SectionContent>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription class="text-foreground">
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
</SectionSpread>
<SectionMedia class="absolute inset-0 size-full opacity-50">
<Image sizes="(1536px) 1536px, 100vw" {...image} />
</SectionMedia>
</Section>

View file

@ -0,0 +1,78 @@
---
import { cn } from "@/lib/utils"
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
}
const { class: className, id, links, item } = Astro.props
---
<Section class={cn("bg-accent/50", className)} id={id}>
<SectionSpread class="@5xl:items-center">
<SectionProse>
<slot />
</SectionProse>
<SectionContent>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "secondary"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
</SectionSpread>
</Section>

View file

@ -0,0 +1,90 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
}
const { class: className, id, links, image, item } = Astro.props
---
<Section class={className} id={id} size="lg">
<SectionMedia class="absolute inset-0 size-full rounded-none mask-y-from-0%">
<Image sizes="(1536px) 1536px, 100vw" {...image} />
</SectionMedia>
<SectionSpread class="relative @5xl:items-center">
<SectionProse size="lg">
<slot />
</SectionProse>
<SectionContent class="shrink-0">
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button
class="w-full"
variant={i === 0 ? "default" : "secondary"}
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription class="text-foreground">
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
</SectionSpread>
</Section>

View file

@ -0,0 +1,91 @@
---
import { cn } from "@/lib/utils"
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
}
const { class: className, id, links, item } = Astro.props
---
<Section
class={cn(
"border-primary/20 my-0 -mt-px rounded-none shadow-none",
className
)}
id={id}
variant="floating"
size="lg"
>
<SectionContent
class={cn(
"before:from-primary/20 relative items-center before:absolute before:top-full before:left-0 before:-z-10 before:h-[200%] before:w-full before:animate-pulse before:rounded-full before:bg-gradient-to-b before:to-transparent before:blur-3xl"
)}
>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
<SectionProse class="text-center" size="lg">
<slot />
</SectionProse>
<SectionActions class="justify-center">
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "outline"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
</Section>

View file

@ -0,0 +1,61 @@
---
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<Accordion>
{
items?.map(({ title, description }) => (
<AccordionItem>
<AccordionTrigger>{title}</AccordionTrigger>
<AccordionContent>{description}</AccordionContent>
</AccordionItem>
))
}
</Accordion>
</Section>

View file

@ -0,0 +1,64 @@
---
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<Accordion>
{
items?.map(({ title, description }) => (
<AccordionItem>
<AccordionTrigger>{title}</AccordionTrigger>
<AccordionContent>{description}</AccordionContent>
</AccordionItem>
))
}
</Accordion>
</SectionSplit>
</Section>

View file

@ -0,0 +1,61 @@
---
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSpread class="@5xl:items-end">
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionSpread>
<Accordion>
{
items?.map(({ title, description }) => (
<AccordionItem>
<AccordionTrigger>{title}</AccordionTrigger>
<AccordionContent>{description}</AccordionContent>
</AccordionItem>
))
}
</Accordion>
</Section>

View file

@ -0,0 +1,61 @@
---
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id} style="--section-width: 672px;">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<Accordion>
{
items?.map(({ title, description }) => (
<AccordionItem>
<AccordionTrigger>{title}</AccordionTrigger>
<AccordionContent>{description}</AccordionContent>
</AccordionItem>
))
}
</Accordion>
</Section>

View file

@ -0,0 +1,90 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileActions,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
icon?: string
href?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid>
{
items?.map(({ title, description, icon, links }) => (
<Tile>
<TileMedia variant="icon">
<Icon name={icon} />
</TileMedia>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
<TileActions>
{links?.map(({ icon, text, href, target }) => (
<Button
class="h-auto p-0"
variant="link"
href={href}
target={target}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))}
</TileActions>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,90 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileActions,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
icon?: string
href?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent class="items-center">
<SectionProse class="text-center">
<slot />
</SectionProse>
<SectionActions class="justify-center">
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid>
{
items?.map(({ title, description, icon, links }) => (
<Tile class="items-center">
<TileMedia variant="icon">
<Icon name={icon} />
</TileMedia>
<TileContent class="items-center text-center text-balance">
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
<TileActions class="justify-center">
{links?.map(({ icon, text, href, target }) => (
<Button
class="h-auto p-0"
variant="link"
href={href}
target={target}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))}
</TileActions>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,58 @@
---
import { Image } from "@/components/ui/image"
import {
Section,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileSplit,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
items?: {
href?: string
title?: string
description?: string
image?: {
src: string
alt: string
}
}[]
}
const { class: className, id, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent class="@5xl:items-center">
<SectionProse class="@5xl:text-center">
<slot />
</SectionProse>
</SectionContent>
<SectionGrid>
{
items?.map(({ title, description, href, image }) => (
<Tile href={href}>
<TileSplit class="items-center">
<TileMedia>
<Image sizes="230px" {...image} />
</TileMedia>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
</TileSplit>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,95 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
import {
Tile,
TileActions,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
icon?: string
href?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit class="@5xl:grid-cols-[1fr_2fr]">
<SectionContent
class="@5xl:sticky @5xl:top-[calc(var(--section-py)+var(--header-height,0px))]"
>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid size="lg">
{
items?.map(({ title, description, icon, links }) => (
<Tile>
<TileMedia variant="icon">
<Icon name={icon} />
</TileMedia>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
<TileActions>
{links?.map(({ icon, text, href, target }) => (
<Button
class="h-auto p-0"
variant="link"
href={href}
target={target}
>
{text}
{icon && <Icon name={icon} />}
</Button>
))}
</TileActions>
</Tile>
))
}
</SectionGrid>
</SectionSplit>
</Section>

View file

@ -0,0 +1,100 @@
---
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileActions,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
icon?: string
href?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section
class={cn(
"border-primary/20 my-0 -mt-px rounded-none shadow-none",
className
)}
id={id}
variant="floating"
>
<SectionContent class="items-center">
<SectionProse class="text-center">
<slot />
</SectionProse>
<SectionActions class="justify-center">
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid>
{
items?.map(({ title, description, icon, links }) => (
<Tile class="items-center">
<TileMedia class="bg-primary/5 border-primary/20" variant="icon">
<Icon class="text-primary" name={icon} />
</TileMedia>
<TileContent class="items-center text-center text-balance">
<TileTitle>{title}</TileTitle>
<TileDescription class="text-foreground">
{description}
</TileDescription>
</TileContent>
<TileActions class="justify-center">
{links?.map(({ icon, text, href, target }) => (
<Button
class="h-auto p-0"
variant="link"
href={href}
target={target}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))}
</TileActions>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,72 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileDescription,
TileSplit,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
icon?: string
href?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid class="grid-cols-1! gap-12">
{
items?.map(({ title, description }) => (
<Tile class="border-t pt-12">
<TileSplit>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileSplit>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,92 @@
---
import {
Footer,
FooterDescription,
FooterGrid,
FooterGroup,
FooterGroupLabel,
FooterMenu,
FooterMenuItem,
FooterMenuLink,
FooterSplit,
} from "@/components/ui/footer"
import { Icon } from "@/components/ui/icon"
import { Logo, LogoImage, LogoText } from "@/components/ui/logo"
interface Props {
logo?: {
src?: string
alt?: string
text?: string
href?: string
}
description?: string
socials?: string[]
menus?: {
text?: string
href?: string
links?: {
text?: string
href?: string
}[]
}[]
links?: {
href?: string
text?: string
icon?: string
}[]
}
const { logo, description, links, socials, menus } = Astro.props
---
<Footer>
<FooterSplit class="@5xl:grid-cols-[1fr_3fr]">
<FooterGroup>
<Logo href={logo?.href || "/"}>
<LogoImage src={logo?.src} alt={logo?.alt} />
<LogoText>{logo?.text}</LogoText>
</Logo>
<FooterDescription>{description}</FooterDescription>
<FooterMenu>
{
links?.map(({ href, icon, text }) => (
<FooterMenuItem>
<FooterMenuLink href={href}>
<Icon href={href} name={icon} />
{text}
</FooterMenuLink>
</FooterMenuItem>
))
}
</FooterMenu>
<FooterMenu orientation="horizontal">
{
socials?.map((social) => (
<FooterMenuItem>
<FooterMenuLink href={social}>
<Icon href={social} />
</FooterMenuLink>
</FooterMenuItem>
))
}
</FooterMenu>
</FooterGroup>
<FooterGrid class="@5xl:justify-end">
{
menus?.map((menu) => (
<FooterGroup>
<FooterGroupLabel>{menu.text}</FooterGroupLabel>
<FooterMenu>
{menu.links?.map(({ href, text }) => (
<FooterMenuItem>
<FooterMenuLink href={href}>{text}</FooterMenuLink>
</FooterMenuItem>
))}
</FooterMenu>
</FooterGroup>
))
}
</FooterGrid>
</FooterSplit>
</Footer>

View file

@ -0,0 +1,61 @@
---
import {
Footer,
FooterCopyright,
FooterDescription,
FooterGroup,
FooterMenu,
FooterMenuItem,
FooterMenuLink,
} from "@/components/ui/footer"
import { Icon } from "@/components/ui/icon"
import { Logo, LogoImage, LogoText } from "@/components/ui/logo"
interface Props {
logo?: {
src?: string
alt?: string
text?: string
href?: string
}
description?: string
links?: {
text?: string
href?: string
}[]
socials?: string[]
}
const { logo, description, links, socials } = Astro.props
---
<Footer class="items-center">
<FooterGroup class="items-center">
<Logo href={logo?.href || "/"}>
<LogoImage src={logo?.src} alt={logo?.alt} />
<LogoText>{logo?.text}</LogoText>
</Logo>
<FooterDescription>{description}</FooterDescription>
<FooterMenu orientation="horizontal">
{
links?.map(({ href, text }) => (
<FooterMenuItem>
<FooterMenuLink href={href}>{text}</FooterMenuLink>
</FooterMenuItem>
))
}
</FooterMenu>
<FooterMenu orientation="horizontal">
{
socials?.map((social) => (
<FooterMenuItem>
<FooterMenuLink href={social}>
<Icon href={social} />
</FooterMenuLink>
</FooterMenuItem>
))
}
</FooterMenu>
</FooterGroup>
<FooterCopyright>{logo?.text}</FooterCopyright>
</Footer>

View file

@ -0,0 +1,102 @@
---
import {
Footer,
FooterCopyright,
FooterDescription,
FooterGrid,
FooterGroup,
FooterGroupLabel,
FooterMenu,
FooterMenuItem,
FooterMenuLink,
FooterSplit,
FooterSpread,
} from "@/components/ui/footer"
import { Icon } from "@/components/ui/icon"
import { Logo, LogoImage, LogoText } from "@/components/ui/logo"
interface Props {
logo?: {
src?: string
alt?: string
text?: string
href?: string
}
description?: string
socials?: string[]
menus?: {
text?: string
href?: string
links?: {
text?: string
href?: string
}[]
}[]
links?: {
href?: string
text?: string
icon?: string
}[]
}
const { logo, description, links, socials, menus } = Astro.props
---
<Footer>
<FooterSplit class="@5xl:grid-cols-[1fr_3fr]">
<FooterDescription class="text-base">{description}</FooterDescription>
<FooterGrid class="@5xl:justify-end">
{
menus?.map((menu) => (
<FooterGroup>
<FooterGroupLabel>{menu.text}</FooterGroupLabel>
<FooterMenu>
{menu.links?.map(({ href, text }) => (
<FooterMenuItem>
<FooterMenuLink href={href}>{text}</FooterMenuLink>
</FooterMenuItem>
))}
</FooterMenu>
</FooterGroup>
))
}
</FooterGrid>
</FooterSplit>
<Logo class="h-auto max-h-none w-full max-w-none" href={logo?.href || "/"}>
<LogoImage
src={logo?.src}
alt={logo?.alt}
sizes="(min-width: 1536px) 1536px, 100vw"
class="h-auto max-h-none w-full max-w-none object-contain"
/>
<LogoText>{logo?.text}</LogoText>
</Logo>
<FooterSpread>
<FooterCopyright>{logo?.text}</FooterCopyright>
<FooterMenu
orientation="horizontal"
class="@5xl:absolute @5xl:left-1/2 @5xl:-translate-x-1/2"
>
{
socials?.map((social) => (
<FooterMenuItem>
<FooterMenuLink href={social}>
<Icon href={social} />
</FooterMenuLink>
</FooterMenuItem>
))
}
</FooterMenu>
<FooterMenu orientation="horizontal">
{
links?.map(({ href, text }) => (
<FooterMenuItem>
<FooterMenuLink class="text-xs" href={href}>
{text}
</FooterMenuLink>
</FooterMenuItem>
))
}
</FooterMenu>
</FooterSpread>
</Footer>

View file

@ -0,0 +1,200 @@
---
import MenuIcon from "lucide-static/icons/menu.svg"
import { Button } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { Header, HeaderActions } from "@/components/ui/header"
import { Icon } from "@/components/ui/icon"
import { Logo, LogoImage, LogoText } from "@/components/ui/logo"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuSub,
NavigationMenuSubItem,
NavigationMenuSubLink,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
} from "@/components/ui/sidebar"
import { ThemeToggle } from "@/components/ui/theme-toggle"
interface Props {
logo?: {
src?: string
alt?: string
text?: string
href?: string
}
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
socials?: string[]
menus?: {
text?: string
href?: string
links?: {
text?: string
href?: string
}[]
}[]
}
const { logo, menus, links, socials } = Astro.props
---
<Header>
<Logo href={logo?.href || "/"}>
<LogoImage src={logo?.src} alt={logo?.alt} />
<LogoText>{logo?.text}</LogoText>
</Logo>
<NavigationMenu class="mr-auto @max-5xl:hidden">
<NavigationMenuList>
{
menus?.map((menu) => (
<NavigationMenuItem>
{menu.links && menu.links.length > 0 ? (
<>
<NavigationMenuTrigger
class="inline-flex h-8 items-center bg-transparent px-3 py-0"
href={menu.href}
>
{menu.text}
</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuSub>
{menu.links?.map((link) => (
<NavigationMenuSubItem>
<NavigationMenuSubLink href={link.href}>
{link.text}
</NavigationMenuSubLink>
</NavigationMenuSubItem>
))}
</NavigationMenuSub>
</NavigationMenuContent>
</>
) : (
<NavigationMenuLink
class="inline-flex h-8 items-center bg-transparent px-3 py-0"
variant="trigger"
href={menu.href}
>
{menu.text}
</NavigationMenuLink>
)}
</NavigationMenuItem>
))
}
</NavigationMenuList>
</NavigationMenu>
<HeaderActions class="@max-5xl:hidden">
{
socials?.map((social) => (
<>
<Button variant="ghost" size="icon-sm" href={social} target="_blank">
<Icon href={social} />
</Button>
</>
))
}
<ThemeToggle />
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === links.length - 1 ? "default" : "outline"}
size="sm"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</HeaderActions>
<Sheet class="first:ml-auto">
<SheetTrigger
class="@5xl:hidden"
variant="ghost"
size="icon"
name="open-menu-button"
>
<MenuIcon class="size-5" />
</SheetTrigger>
<SheetContent class="overflow-y-auto px-4 py-12">
<SidebarMenu>
{
menus?.map((menu) => (
<SidebarMenuItem>
{menu.links && menu.links.length > 0 ? (
<Collapsible>
<CollapsibleTrigger>
<SidebarMenuButton class="h-10 rounded-md px-4 text-xl">
{menu.text}
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{menu.links?.map((link) => (
<SidebarMenuSubItem>
<SidebarMenuSubButton href={link.href}>
{link.text}
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</Collapsible>
) : (
<SidebarMenuButton
class="h-10 w-full rounded-md px-4 text-xl has-[>svg]:px-4"
href={menu.href}
>
{menu.text}
</SidebarMenuButton>
)}
</SidebarMenuItem>
))
}
</SidebarMenu>
<HeaderActions class="flex flex-col gap-2">
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === links.length - 1 ? "default" : "secondary"}
class="w-full"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</HeaderActions>
<HeaderActions>
{
socials?.map((social) => (
<Button variant="ghost" size="icon" href={social}>
<Icon href={social} />
</Button>
))
}
</HeaderActions>
</SheetContent>
</Sheet>
</Header>

View file

@ -0,0 +1,195 @@
---
import MenuIcon from "lucide-static/icons/menu.svg"
import { Button } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { Header, HeaderActions } from "@/components/ui/header"
import { Icon } from "@/components/ui/icon"
import { Logo, LogoImage, LogoText } from "@/components/ui/logo"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuSub,
NavigationMenuSubItem,
NavigationMenuSubLink,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
} from "@/components/ui/sidebar"
import { ThemeToggle } from "@/components/ui/theme-toggle"
interface Props {
logo?: {
src?: string
alt?: string
text?: string
href?: string
}
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
socials?: string[]
menus?: {
text?: string
href?: string
links?: {
text?: string
href?: string
}[]
}[]
}
const { logo, menus, links, socials } = Astro.props
---
<Header>
<Logo href={logo?.href || "/"}>
<LogoImage src={logo?.src} alt={logo?.alt} />
<LogoText>{logo?.text}</LogoText>
</Logo>
<NavigationMenu class="absolute left-1/2 -translate-x-1/2 @max-5xl:hidden">
<NavigationMenuList>
{
menus?.map((menu) => (
<NavigationMenuItem>
{menu.links && menu.links.length > 0 ? (
<>
<NavigationMenuTrigger
class="inline-flex h-8 items-center bg-transparent px-3 py-0"
href={menu.href}
>
{menu.text}
</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuSub>
{menu.links?.map((link) => (
<NavigationMenuSubItem>
<NavigationMenuSubLink href={link.href}>
{link.text}
</NavigationMenuSubLink>
</NavigationMenuSubItem>
))}
</NavigationMenuSub>
</NavigationMenuContent>
</>
) : (
<NavigationMenuLink
class="inline-flex h-8 items-center bg-transparent px-3 py-0"
variant="trigger"
href={menu.href}
>
{menu.text}
</NavigationMenuLink>
)}
</NavigationMenuItem>
))
}
</NavigationMenuList>
</NavigationMenu>
<HeaderActions class="@max-5xl:hidden">
<ThemeToggle />
{
socials?.map((social) => (
<Button variant="ghost" size="icon-sm" href={social} target="_blank">
<Icon href={social} />
</Button>
))
}
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === links.length - 1 ? "default" : "ghost"}
size="sm"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</HeaderActions>
<Sheet>
<SheetTrigger
class="@5xl:hidden"
variant="ghost"
size="icon"
name="open-menu-button"
>
<MenuIcon class="size-5" />
</SheetTrigger>
<SheetContent class="overflow-y-auto px-4 py-12">
<SidebarMenu>
{
menus?.map((menu) => (
<SidebarMenuItem>
{menu.links && menu.links.length > 0 ? (
<Collapsible>
<CollapsibleTrigger>
<SidebarMenuButton>{menu.text}</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{menu.links?.map((link) => (
<SidebarMenuSubItem>
<SidebarMenuSubButton href={link.href}>
{link.text}
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</Collapsible>
) : (
<SidebarMenuButton
class="h-10 w-full rounded-md px-4 text-xl has-[>svg]:px-4"
href={menu.href}
>
{menu.text}
</SidebarMenuButton>
)}
</SidebarMenuItem>
))
}
</SidebarMenu>
<HeaderActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === links.length - 1 ? "default" : "ghost"}
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</HeaderActions>
<HeaderActions>
{
socials?.map((social) => (
<Button variant="ghost" size="icon" href={social}>
<Icon href={social} />
</Button>
))
}
</HeaderActions>
</SheetContent>
</Sheet>
</Header>

View file

@ -0,0 +1,213 @@
---
import MenuIcon from "lucide-static/icons/menu.svg"
import { Button } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { Header, HeaderActions } from "@/components/ui/header"
import { Icon } from "@/components/ui/icon"
import { Logo, LogoImage, LogoText } from "@/components/ui/logo"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuSub,
NavigationMenuSubItem,
NavigationMenuSubLink,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
} from "@/components/ui/sidebar"
import { ThemeToggle } from "@/components/ui/theme-toggle"
interface Props {
logo?: {
src?: string
alt?: string
text?: string
href?: string
}
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
socials?: string[]
menus?: {
text?: string
href?: string
links?: {
text?: string
href?: string
}[]
}[]
}
const { logo, menus, links, socials } = Astro.props
const githubStarCount = (
await (await fetch("https://api.github.com/repos/fulldotdev/ui")).json()
).stargazers_count
---
<Header class="bg-background/60 backdrop-blur-md">
<Logo href={logo?.href || "/"}>
<LogoImage src={logo?.src} alt={logo?.alt} />
<LogoText>{logo?.text}</LogoText>
</Logo>
<NavigationMenu class="mr-auto @max-5xl:hidden">
<NavigationMenuList>
{
menus?.map((menu) => (
<NavigationMenuItem>
{menu.links && menu.links.length > 0 ? (
<>
<NavigationMenuTrigger
class="inline-flex h-8 items-center bg-transparent px-3 py-0"
href={menu.href}
>
{menu.text}
</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuSub>
{menu.links?.map((link) => (
<NavigationMenuSubItem>
<NavigationMenuSubLink href={link.href}>
{link.text}
</NavigationMenuSubLink>
</NavigationMenuSubItem>
))}
</NavigationMenuSub>
</NavigationMenuContent>
</>
) : (
<NavigationMenuLink
class="inline-flex h-8 items-center bg-transparent px-3 py-0"
variant="trigger"
href={menu.href}
>
{menu.text}
</NavigationMenuLink>
)}
</NavigationMenuItem>
))
}
</NavigationMenuList>
</NavigationMenu>
<HeaderActions class="@max-5xl:hidden">
<ThemeToggle />
{
socials?.map((social) => (
<>
<Button variant="ghost" size="icon-sm" href={social} target="_blank">
<Icon href={social} />
</Button>
</>
))
}
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === links.length - 1 ? "default" : "outline"}
size="sm"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
<Button
variant="outline"
size="sm"
href="https://github.com/fulldotdev/ui"
target="_blank"
>
<Icon name="github" />
{githubStarCount}
</Button>
</HeaderActions>
<Sheet class="first:ml-auto @5xl:hidden">
<SheetTrigger
class="@5xl:hidden"
variant="ghost"
size="icon"
name="open-menu-button"
>
<MenuIcon class="size-5" />
</SheetTrigger>
<SheetContent class="overflow-y-auto px-4 py-12">
<SidebarMenu>
{
menus?.map((menu) => (
<SidebarMenuItem>
{menu.links && menu.links.length > 0 ? (
<Collapsible>
<CollapsibleTrigger>
<SidebarMenuButton class="h-10 rounded-md px-4 text-xl">
{menu.text}
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{menu.links?.map((link) => (
<SidebarMenuSubItem>
<SidebarMenuSubButton href={link.href}>
{link.text}
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</Collapsible>
) : (
<SidebarMenuButton
class="h-10 w-full rounded-md px-4 text-xl has-[>svg]:px-4"
href={menu.href}
>
{menu.text}
</SidebarMenuButton>
)}
</SidebarMenuItem>
))
}
</SidebarMenu>
<HeaderActions class="flex flex-col gap-2">
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === links.length - 1 ? "default" : "secondary"}
class="w-full"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</HeaderActions>
<HeaderActions>
{
socials?.map((social) => (
<Button variant="ghost" size="icon" href={social}>
<Icon href={social} />
</Button>
))
}
</HeaderActions>
</SheetContent>
</Sheet>
</Header>

View file

@ -0,0 +1,67 @@
---
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
link?: {
text?: string
href?: string
icon?: string
target?: string
}
links?: {
text?: string
href?: string
icon?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, link, links, image } = Astro.props
const { icon: linkIcon, text: linkText, ...linkProps } = link || {}
---
<Section class={className} id={id}>
<SectionContent class="items-center">
<Badge variant="secondary" {...linkProps}>
{linkText}
{linkProps.href && linkIcon && <Icon name={linkIcon} />}
</Badge>
<SectionProse class="text-center text-balance" size="lg">
<slot />
</SectionProse>
<SectionActions class="justify-center">
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia>
<Image sizes="(min-width: 1536px) 1536px, 100vw" priority {...image} />
</SectionMedia>
</Section>

View file

@ -0,0 +1,98 @@
---
import { cn } from "@/lib/utils"
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
image?: {
src: string
alt: string
}
}
const { class: className, id, links, item, image } = Astro.props
---
<Section
class={cn("-mt-(--header-height) justify-start", className)}
id={id}
size="lg"
>
<SectionSpread class="relative @5xl:items-center">
<SectionProse size="lg">
<slot />
</SectionProse>
<SectionContent class="shrink-0">
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button
class="w-full"
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
</SectionSpread>
<SectionMedia
class="mask-y-to-black-100% absolute inset-0 size-full rounded-none bg-white mask-y-from-0% dark:bg-black"
>
<Image sizes="100vw" priority {...image} class="opacity-75" />
</SectionMedia>
</Section>

View file

@ -0,0 +1,98 @@
---
import { cn } from "@/lib/utils"
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
image?: {
src: string
alt: string
}
}
const { class: className, id, links, item, image } = Astro.props
---
<Section
class={cn("justify-start pb-[72cqw] @5xl:pb-[36cqw]", className)}
id={id}
size="lg"
>
<SectionSpread class="relative @5xl:items-center">
<SectionProse size="lg">
<slot />
</SectionProse>
<SectionContent class="shrink-0">
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button
class="w-full"
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
</SectionSpread>
<SectionMedia
class="mask-y-to-black-100% absolute inset-0 size-full rounded-none bg-white mask-y-from-0% dark:bg-black"
>
<Image class="opacity-75" sizes="100vw" priority {...image} />
</SectionMedia>
</Section>

View file

@ -0,0 +1,66 @@
---
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
title?: string
description?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const {
class: className,
id,
title,
description,
links,
image,
} = Astro.props
---
<Section class={cn("gap-8 pt-8", className)} id={id}>
<SectionContent class="border-y py-8">
<SectionSpread class="@5xl:items-end">
<SectionProse class="text-balance" size="sm">
{title && <h1>{title}</h1>}
</SectionProse>
<SectionProse class="text-muted-foreground text-balance" size="sm">
{description && <p>{description}</p>}
</SectionProse>
<SectionActions class="-my-2 -ml-3 flex-row flex-nowrap">
{
links?.map(({ href, text, ...link }) => (
<Button variant="link" href={href} {...link}>
{text}
{href && <Icon name="arrow-up-right" />}
</Button>
))
}
</SectionActions>
</SectionSpread>
</SectionContent>
<SectionMedia>
<Image sizes="(min-width: 1536px) 1536px, 100vw" priority {...image} />
</SectionMedia>
</Section>

View file

@ -0,0 +1,63 @@
---
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const {
class: className,
id,
links,
image,
} = Astro.props
---
<Section class={cn("@5xl:pt-1", className)} id={id}>
<SectionSplit class="@5xl:grid-cols-[1fr_3fr] @5xl:items-start">
<SectionContent class="pt-(--section-py)">
<SectionProse class="text-balance" size="sm">
<slot />
</SectionProse>
<SectionActions class="-my-2 -ml-3">
{
links?.map(({ href, text, ...link }) => (
<Button variant="link" href={href} {...link}>
{text}
{href && <Icon name="arrow-up-right" />}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia class="relative z-100 -mr-5">
<Image
sizes="(min-width: 1536px) 1152px, (min-width: 1024px) 75vw, 100vw"
priority
{...image}
/>
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,70 @@
---
import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
link?: {
text?: string
href?: string
icon?: string
target?: string
}
links?: {
text?: string
href?: string
icon?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, link, links, image } = Astro.props
---
<Section class={className} id={id}>
<SectionContent
class={cn(
"before:from-primary/10 relative items-center before:absolute before:bottom-full before:left-0 before:h-[200%] before:w-full before:animate-pulse before:rounded-full before:bg-gradient-to-t before:to-transparent before:blur-3xl"
)}
>
<Badge variant="secondary" {...link}>
{link?.text}
{link?.href && <Icon name={link?.icon} />}
</Badge>
<SectionProse class="text-center text-balance" size="lg">
<slot />
</SectionProse>
<SectionActions class="justify-center">
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "outline"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia>
<Image sizes="(min-width: 1536px) 1536px, 100vw" priority {...image} />
</SectionMedia>
</Section>

View file

@ -0,0 +1,87 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
links?: {
text?: string
href?: string
icon?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, item, links, image } = Astro.props
---
<Section class={className} id={id}>
<SectionContent class="items-center">
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
<SectionProse class="text-center text-balance" size="lg">
<slot />
</SectionProse>
<SectionActions class="justify-center">
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia>
<Image sizes="(min-width: 1536px) 1536px, 100vw" priority {...image} />
</SectionMedia>
</Section>

View file

@ -0,0 +1,59 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
text?: string
href?: string
icon?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, links, image } = Astro.props
---
<Section class={className} id={id} size="lg">
<SectionContent class="items-center">
<SectionProse class="text-center text-balance" size="lg">
<slot />
</SectionProse>
<SectionActions class="justify-center">
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia class="absolute inset-0 rounded-none bg-white dark:bg-black">
<Image
class="opacity-50"
sizes="(min-width: 1536px) 1536px, 100vw"
priority
{...image}
/>
</SectionMedia>
</Section>

View file

@ -0,0 +1,64 @@
---
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
text?: string
href?: string
icon?: string
target?: string
}[]
image?: {
src: string
alt: string
}
}
const { class: className, id, links, image } = Astro.props
---
<Section
class={cn("justify-center py-[36cqw] @5xl:py-[18cqw]", className)}
id={id}
size="lg"
>
<SectionContent class="items-center">
<SectionProse class="text-center text-balance" size="lg">
<slot />
</SectionProse>
<SectionActions class="justify-center">
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia class="absolute inset-0 rounded-none bg-white dark:bg-black">
<Image
class="opacity-50"
sizes="(min-width: 1536px) 1536px, 100vw"
priority
{...image}
/>
</SectionMedia>
</Section>

View file

@ -0,0 +1,99 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { List, ListItem } from "@/components/ui/list"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
image?: {
src: string
alt: string
}
}
const { class: className, id, list, links, item, image } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse size="lg">
<slot />
</SectionProse>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
<SectionMedia>
<Image sizes="(min-width: 1536px) 1536px, 100vw" priority {...image} />
</SectionMedia>
</Section>

View file

@ -0,0 +1,106 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { List, ListItem } from "@/components/ui/list"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
image?: {
src: string
alt: string
}
}
const { class: className, id, list, links, item, image } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit class="@5xl:items-center">
<SectionContent>
<SectionProse class="text-balance" size="lg">
<slot />
</SectionProse>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
<SectionMedia>
<Image
sizes="(min-width: 1536px) 768px, (min-width: 1024px) 50vw, 100vw"
priority
{...image}
/>
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,108 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { List, ListItem } from "@/components/ui/list"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
image?: {
src: string
alt: string
}
}
const { class: className, id, list, links, item, image } = Astro.props
---
<Section class={className} id={id} size="lg">
<SectionSplit class="@5xl:grid-cols-[2fr_3fr] @5xl:items-center">
<SectionContent>
<SectionProse class="text-balance" size="lg">
<slot />
</SectionProse>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
<SectionMedia
class="-mr-(--section-px) rounded-r-none @5xl:-my-(--section-py)"
>
<Image
sizes="(min-width: 1536px) 922px, (min-width: 1024px) 60vw, 100vw"
priority
{...image}
/>
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,72 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
item?: {
images?: {
src: string
alt: string
}[]
rating?: number
description?: string
}
}
const { class: className, id, links, image } = Astro.props
---
<Section class={className} id={id} size="lg">
<SectionSplit class="@5xl:grid-cols-[1fr_3fr] @5xl:items-center">
<SectionContent>
<SectionProse class="text-balance" size="lg">
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMedia
class="-mr-(--section-px) rounded-r-none @5xl:-my-(--section-py)"
>
<Image
sizes="(min-width: 1536px) 1152px, (min-width: 1024px) 75vw, 100vw"
priority
{...image}
/>
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,111 @@
---
import { cn } from "@/lib/utils"
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
} from "@/components/ui/item"
import { List, ListItem } from "@/components/ui/list"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMedia,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
item?: {
images?: {
src: string
alt: string
title?: string
}[]
rating?: number
description?: string
}
image?: {
src: string
alt: string
title?: string
}
}
const { class: className, id, list, links, item, image } = Astro.props
---
<Section class={cn("-mt-(--header-height)", className)} id={id}>
<SectionSplit>
<SectionContent class="relative z-10 pt-(--header-height) @5xl:self-center">
<SectionProse size="lg">
<slot />
</SectionProse>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button
variant={i === 0 ? "default" : "secondary"}
size="lg"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<Item class="p-0">
<ItemMedia class="-space-x-5">
{
item?.images?.map((image) => (
<Avatar class="ring-background size-11 ring">
<AvatarImage {...image} />
</Avatar>
))
}
</ItemMedia>
<ItemContent class="mt-1">
<Rating rating={item?.rating} />
<ItemDescription>
{item?.description}
</ItemDescription>
</ItemContent>
</Item>
</SectionContent>
<SectionMedia
class="-mx-(--section-px) -my-(--section-py) rounded-none @max-5xl:mask-y-from-50% @5xl:-ml-[25%] @5xl:mask-b-from-50% @5xl:mask-l-from-0%"
>
<Image
sizes="(min-width: 1536px) 768px, (min-width: 1024px) 50vw, 100vw"
priority
{...image}
/>
</SectionMedia>
</SectionSplit>
</Section>

View file

@ -0,0 +1,70 @@
---
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import { Tile, TileContent, TileMedia, TileTitle } from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
href?: string
title?: string
image?: {
src: string
alt: string
}
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={cn("gap-12", className)} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid class="-mx-5 grid-cols-1 gap-1 @5xl:grid-cols-2">
{
items?.map(({ href, title, image }) => (
<Tile
href={href}
class="dark relative overflow-hidden bg-black! ring-0!"
>
<TileMedia class="w-full transition duration-500 ease-in-out group-hover/tile:scale-103">
<Image {...image} />
</TileMedia>
<TileContent class="absolute inset-0 z-10 size-full items-center justify-center p-6 text-center opacity-0 transition duration-500 ease-in-out group-hover/tile:opacity-100">
<TileTitle class="text-foreground">{title}</TileTitle>
</TileContent>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,62 @@
---
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
images?: {
src: string
alt: string
}[]
title?: string
description?: string
}
const { class: className, id, links, images, title, description } = Astro.props
---
<Section
class={cn("@5xl:pt-1 -mt-(--header-height) @5xl:gap-1", className)}
id={id}
>
<SectionSplit class="@5xl:grid-cols-[1fr_3fr] @5xl:items-start">
<SectionContent
class="@5xl:sticky @5xl:top-[calc(24px+var(--header-height,0px))]"
>
<SectionProse class="text-balance" size="sm">
{title && <h1>{title}</h1>}
{description && <p>{description}</p>}
</SectionProse>
<SectionActions>
{
links?.map(({ href, text, ...link }) => (
<Button variant="link" class="h-auto p-0!" href={href} {...link}>
{text}
{href && <Icon name="arrow-up-right" />}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid class="-mx-5 grid-cols-1 gap-1 @5xl:grid-cols-2">
{images?.map(({ ...image }) => <Image {...image} />)}
</SectionGrid>
</SectionSplit>
</Section>

View file

@ -0,0 +1,46 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}
const { class: className, id, links } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
</SectionContent>
<SectionGrid class="-mx-4" size="sm">
{
links?.map(({ icon, text, href, target }) => (
<Button
class="text-foreground justify-start text-start text-base"
variant="link"
href={href}
target={target}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,49 @@
---
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}
const { class: className, id, links } = Astro.props
---
<Section class={cn("bg-muted/50 mt-16", className)} id={id}>
<SectionSplit class="gap-x-32 @5xl:grid-cols-[1fr_2fr]">
<SectionContent>
<SectionProse class="max-w-sm">
<slot />
</SectionProse>
</SectionContent>
<SectionGrid class="-mx-4" size="sm">
{
links?.map(({ icon, text, ...link }) => (
<Button
class="text-foreground justify-start text-start text-base"
variant="link"
{...link}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionGrid>
</SectionSplit>
</Section>

View file

@ -0,0 +1,34 @@
---
import { cn } from "@/lib/utils"
import { Logo, LogoImage, LogoText } from "@/components/ui/logo"
import { Section, SectionGrid, SectionProse } from "@/components/ui/section"
interface Props {
class?: string
id?: string
logos?: {
src: string
alt: string
text?: string
href?: string
}[]
}
const { class: className, id, logos } = Astro.props
---
<Section class={cn("gap-12", className)} id={id}>
<SectionProse size="sm">
<slot />
</SectionProse>
<SectionGrid size="sm">
{
logos?.map(({ src, alt, text, href }) => (
<Logo href={href}>
<LogoImage src={src} alt={alt} />
<LogoText>{text}</LogoText>
</Logo>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,41 @@
---
import { cn } from "@/lib/utils"
import { Logo, LogoImage, LogoText } from "@/components/ui/logo"
import { Marquee, MarqueeContent } from "@/components/ui/marquee"
import { Section, SectionProse } from "@/components/ui/section"
interface Props {
class?: string
id?: string
logos?: {
src: string
alt: string
text?: string
href?: string
}[]
}
const { class: className, id, logos } = Astro.props
---
<Section
class={cn("gap-12", className)}
id={id}
style={{ "--container": "768px" }}
>
<SectionProse class="mx-auto text-center" size="sm">
<slot />
</SectionProse>
<Marquee class="mask-x-from-95%">
<MarqueeContent>
{
logos?.map(({ src, alt, text, href }) => (
<Logo href={href}>
<LogoImage src={src} alt={alt} />
<LogoText>{text}</LogoText>
</Logo>
))
}
</MarqueeContent>
</Marquee>
</Section>

View file

@ -0,0 +1,33 @@
---
import { Logo, LogoImage, LogoText } from "@/components/ui/logo"
import { Section, SectionGrid, SectionProse } from "@/components/ui/section"
interface Props {
class?: string
id?: string
logos?: {
src: string
alt: string
text?: string
href?: string
}[]
}
const { class: className, id, logos } = Astro.props
---
<Section class={className} id={id}>
<SectionProse class="mx-auto text-center" size="sm">
<slot />
</SectionProse>
<SectionGrid class="flex flex-wrap justify-center">
{
logos?.map(({ src, alt, text, href }) => (
<Logo href={href}>
<LogoImage src={src} alt={alt} />
<LogoText>{text}</LogoText>
</Logo>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,80 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { List, ListItem } from "@/components/ui/list"
import { Price, PriceUnit, PriceValue } from "@/components/ui/price"
import {
Section,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileActions,
TileContent,
TileDescription,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
items?: {
title?: string
description?: string
icon?: string
list?: string[]
links?: {
text?: string
href?: string
target?: string
}[]
price?: string | number
unit?: string
}[]
}
const { class: className, id, items } = Astro.props
---
<Section class={className} id={id}>
<SectionProse>
<slot />
</SectionProse>
<SectionGrid>
{
items?.map(({ title, description, links, list, price, unit }) => (
<Tile variant="floating" class="justify-between">
<TileContent>
<TileTitle class="text-xl">{title}</TileTitle>
<TileDescription>{description}</TileDescription>
<Price>
<PriceValue price={price} />
<PriceUnit>{unit}</PriceUnit>
</Price>
<List class="mt-4 text-sm">
{list?.map((item) => (
<ListItem>
<Icon class="-mt-0.5" name="check" />
{item}
</ListItem>
))}
</List>
</TileContent>
<TileActions class="mt-auto w-full">
{links?.map(({ text, ...link }, i) => (
<Button
class="w-full"
size="sm"
variant={i === 0 ? "default" : "outline"}
{...link}
>
{text}
</Button>
))}
</TileActions>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,83 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { List, ListItem } from "@/components/ui/list"
import { Price, PriceUnit, PriceValue } from "@/components/ui/price"
import {
Section,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
import {
Tile,
TileActions,
TileContent,
TileDescription,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
items?: {
title?: string
description?: string
icon?: string
list?: string[]
links?: {
text?: string
href?: string
target?: string
}[]
price?: string | number
unit?: string
}[]
}
const { class: className, id, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit>
<SectionProse>
<slot />
</SectionProse>
<SectionGrid class="grid-cols-1">
{
items?.map(({ title, description, links, list, price, unit }) => (
<Tile variant="floating">
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
<Price>
<PriceValue price={price} />
<PriceUnit>{unit}</PriceUnit>
</Price>
<List class="mt-4 text-sm">
{list?.map((item) => (
<ListItem>
<Icon class="-mt-0.5" name="check" />
{item}
</ListItem>
))}
</List>
</TileContent>
<TileActions class="mt-auto w-full">
{links?.map(({ text, ...link }, i) => (
<Button
class="w-full"
size="sm"
variant={i === 0 ? "default" : "outline"}
{...link}
>
{text}
</Button>
))}
</TileActions>
</Tile>
))
}
</SectionGrid>
</SectionSplit>
</Section>

View file

@ -0,0 +1,80 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { List, ListItem } from "@/components/ui/list"
import { Price, PriceUnit, PriceValue } from "@/components/ui/price"
import {
Section,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
import {
Tile,
TileActions,
TileContent,
TileDescription,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
items?: {
title?: string
description?: string
icon?: string
list?: string[]
links?: {
text?: string
href?: string
target?: string
}[]
price?: string | number
unit?: string
}[]
}
const { class: className, id, items } = Astro.props
---
<Section class={className} id={id}>
<SectionProse>
<slot />
</SectionProse>
<SectionSpread>
{
items?.map(({ title, description, links, list, price, unit }) => (
<Tile variant="floating">
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
<Price>
<PriceValue class="text-3xl" price={price} />
<PriceUnit class="mt-3">{unit}</PriceUnit>
</Price>
<List class="mt-4 text-sm">
{list?.map((item) => (
<ListItem>
<Icon class="-mt-0.5" name="check" />
{item}
</ListItem>
))}
</List>
</TileContent>
<TileActions class="mt-auto w-full">
{links?.map(({ text, ...link }, i) => (
<Button
class="w-full"
size="sm"
variant={i === 0 ? "default" : "outline"}
{...link}
>
{text}
</Button>
))}
</TileActions>
</Tile>
))
}
</SectionSpread>
</Section>

View file

@ -0,0 +1,103 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import { List, ListItem } from "@/components/ui/list"
import {
NativeCarousel,
NativeCarouselContent,
NativeCarouselItem,
NativeCarouselNext,
NativeCarouselPrevious,
} from "@/components/ui/native-carousel"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
interface Props {
class?: string
id?: string
title?: string
description?: string
list?: string[]
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
image?: {
src: string
alt: string
}
images?: {
src: string
alt: string
}[]
}
const {
class: className,
id,
title,
description,
list,
links,
images,
} = Astro.props
---
<Section class={className} id={id}>
<SectionSplit class="items-start @5xl:gap-24">
<NativeCarousel>
<NativeCarouselContent>
{
images?.map((image) => (
<NativeCarouselItem>
<Image
class="object-contain"
sizes="(min-width: 1536px) 768px, (min-width: 1024px) 50vw, 100vw"
{...image}
/>
</NativeCarouselItem>
))
}
</NativeCarouselContent>
<NativeCarouselPrevious class="left-6" />
<NativeCarouselNext class="right-6" />
</NativeCarousel>
<SectionContent>
<SectionProse>
{title && <h1>{title}</h1>}
{description && <p>{description}</p>}
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
<List>
{
list?.map((item) => (
<ListItem>
<Icon name="check" />
{item}
</ListItem>
))
}
</List>
<SectionProse>
<slot />
</SectionProse>
</SectionContent>
</SectionSplit>
</Section>

View file

@ -0,0 +1,81 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import { Price, PriceUnit, PriceValue } from "@/components/ui/price"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
href?: string
image?: {
src: string
alt: string
}
title?: string
description?: string
price?: string | number
unit?: string
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid>
{
items?.map(({ title, description, image, href, price, unit }) => (
<Tile href={href}>
<TileMedia>
<Image class="aspect-video" sizes="600px" {...image} />
</TileMedia>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
<Price>
<PriceValue price={price} />
<PriceUnit>{unit}</PriceUnit>
</Price>
</TileContent>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,76 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Price, PriceUnit, PriceValue } from "@/components/ui/price"
import {
Section,
SectionActions,
SectionGrid,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileSpread,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
title?: string
description?: string
links?: {
text?: string
href?: string
icon?: string
target?: string
}[]
items?: {
title?: string
description?: string
price?: string | number
unit?: string
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSpread class="@5xl:items-center">
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "outline" : "ghost"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionSpread>
<SectionGrid size="lg">
{
items?.map(({ title, description, price, unit }) => (
<Tile class="m-0 rounded-none border-t p-0 pt-6">
<TileSpread>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
<Price>
<PriceValue price={price} />
<PriceUnit>{unit}</PriceUnit>
</Price>
</TileSpread>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,81 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileSpread,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
title?: string
description?: string
links?: {
text?: string
href?: string
icon?: string
target?: string
}[]
items?: {
title?: string
description?: string
price?: string
unit?: string
image?: {
src?: string
alt?: string
}
}[]
}
const { class: className, id, links, items, ...props } = Astro.props
---
<Section class={className} id={id} {...props}>
<SectionSplit
class="items-start @5xl:*:sticky @5xl:*:top-[calc(var(--section-py)+var(--header-height,0px))]"
>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid size="lg">
{
items?.map(({ title, description, price }) => (
<Tile class="m-0 rounded-none border-t p-0 pt-6">
<TileSpread>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
<p>{price}</p>
</TileSpread>
</Tile>
))
}
</SectionGrid>
</SectionSplit>
</Section>

View file

@ -0,0 +1,81 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileSpread,
TileTitle,
} from "@/components/ui/tile"
interface Props {
title?: string
description?: string
links?: {
text?: string
href?: string
icon?: string
target?: string
}[]
items?: {
title?: string
description?: string
price?: string
unit?: string
image?: {
src?: string
alt?: string
}
}[]
}
const { links, items, ...props } = Astro.props
---
<Section {...props}>
<SectionSplit
class="items-start @5xl:*:sticky @5xl:*:top-[calc(var(--section-py)+var(--header-height,0px))]"
>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
</SectionContent>
<SectionContent>
<SectionGrid size="lg">
{
items?.map(({ title, description, price }) => (
<Tile class="m-0 rounded-none border-t p-0 pt-6">
<TileSpread>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
<p>{price}</p>
</TileSpread>
</Tile>
))
}
</SectionGrid>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "outline" : "ghost"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
</SectionSplit>
</Section>

View file

@ -0,0 +1,89 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import { Price, PriceUnit, PriceValue } from "@/components/ui/price"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
href?: string
image?: {
src: string
alt: string
}
images?: {
src: string
alt: string
}[]
title?: string
description?: string
price?: string | number
unit?: string
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid>
{
items?.map(({ title, description, image, images, href, price, unit }) => (
<Tile href={href}>
<TileMedia>
<Image
class="bg-muted aspect-3/4 object-contain p-6"
sizes="600px"
{...(image || images?.[0])}
/>
</TileMedia>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
<Price>
<PriceValue price={price} />
<PriceUnit>{unit}</PriceUnit>
</Price>
</TileContent>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,98 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionMasonry,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
rating?: number
image?: {
src: string
alt: string
}
item?: {
title?: string
description?: string
image?: {
src: string
alt: string
}
}
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionMasonry>
{
items?.map(({ title, description, rating, item }) => (
<Tile class="shadow-none" variant="floating">
<Item class="p-0">
<ItemMedia>
<Avatar class="size-10">
<AvatarImage {...item?.image} />
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
<Rating rating={rating} />
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
</Tile>
))
}
</SectionMasonry>
</Section>

View file

@ -0,0 +1,101 @@
---
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
rating?: number
image?: {
src: string
alt: string
}
item?: {
title?: string
description?: string
image?: {
src: string
alt: string
}
}
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid class="grid-cols-1">
{
items?.map(({ title, description, rating, item }) => (
<Tile class="shadow-none" variant="floating">
<Item class="p-0">
<ItemMedia>
<Avatar class="size-10">
<AvatarImage {...item?.image} />
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
<Rating rating={rating} />
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
</Tile>
))
}
</SectionGrid>
</SectionSplit>
</Section>

View file

@ -0,0 +1,102 @@
---
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/components/ui/item"
import {
NativeCarousel,
NativeCarouselContent,
NativeCarouselItem,
NativeCarouselNext,
NativeCarouselPrevious,
} from "@/components/ui/native-carousel"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
} from "@/components/ui/section"
import { Tile, TileDescription } from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
rating?: number
image?: {
src: string
alt: string
}
item?: {
title?: string
description?: string
image?: {
src: string
alt: string
}
}
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section
class={cn("items-center", className)}
id={id}
style={{ "--section-width": "var(--breakpoint-sm)" }}
>
<SectionContent class="items-center text-center">
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<NativeCarousel>
<NativeCarouselContent>
{
items?.map(({ description, rating, item }) => (
<NativeCarouselItem>
<Tile class="items-center bg-transparent! ring-transparent!">
<Rating rating={rating} />
<TileDescription class="text-center">
{description}
</TileDescription>
<Item class="p-0">
<ItemContent class="items-center text-center">
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
</Tile>
</NativeCarouselItem>
))
}
</NativeCarouselContent>
<NativeCarouselPrevious class="top-auto -bottom-2 left-6" />
<NativeCarouselNext class="top-auto right-6 -bottom-2" />
</NativeCarousel>
</Section>

View file

@ -0,0 +1,103 @@
---
import { cn } from "@/lib/utils"
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import { Marquee, MarqueeContent } from "@/components/ui/marquee"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
rating?: number
image?: {
src: string
alt: string
}
item?: {
title?: string
description?: string
image?: {
src: string
alt: string
}
}
}[]
}
const { class: className, id, links, items = [] } = Astro.props
---
<Section class={cn("gap-6", className)} id={id}>
<SectionContent class="mb-10">
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
{
(["left", "right"] as const).map((direction) => (
<Marquee class="-mx-(--section-px)">
<MarqueeContent direction={direction} pauseOnHover>
{[...items, ...items]?.map(({ title, description, rating, item }) => (
<Tile class="shadow-none" variant="floating">
<Item class="p-0">
<ItemMedia>
<Avatar class="size-10">
<AvatarImage {...item?.image} />
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
<Rating rating={rating} />
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
</Tile>
))}
</MarqueeContent>
</Marquee>
))
}
</Section>

View file

@ -0,0 +1,103 @@
---
import { cn } from "@/lib/utils"
import { Avatar, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
import { Marquee, MarqueeContent } from "@/components/ui/marquee"
import { Rating } from "@/components/ui/rating"
import {
Section,
SectionActions,
SectionContent,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
items?: {
title?: string
description?: string
rating?: number
image?: {
src: string
alt: string
}
item?: {
title?: string
description?: string
image?: {
src: string
alt: string
}
}
}[]
}
const { class: className, id, links, items = [] } = Astro.props
---
<Section class={cn("gap-6", className)} id={id}>
<SectionContent class="mb-10 items-center text-center">
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ icon, text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
{
(["left", "right"] as const).map((direction) => (
<Marquee class="mask-x-from-95%">
<MarqueeContent direction={direction} pauseOnHover>
{[...items, ...items]?.map(({ title, description, rating, item }) => (
<Tile class="shadow-none" variant="floating">
<Item class="p-0">
<ItemMedia>
<Avatar class="size-10">
<AvatarImage {...item?.image} />
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{item?.title}</ItemTitle>
<ItemDescription>{item?.description}</ItemDescription>
</ItemContent>
</Item>
<Rating rating={rating} />
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
</Tile>
))}
</MarqueeContent>
</Marquee>
))
}
</Section>

View file

@ -0,0 +1,93 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileSpread,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
text?: string
href?: string
}[]
items?: {
href?: string
tagline?: string
title?: string
description?: string
image?: {
src: string
alt: string
}
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSpread class="@5xl:items-end">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
</SectionContent>
<SectionActions>
{
links?.map(({ text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{text}
</Button>
))
}
</SectionActions>
</SectionSpread>
<SectionGrid>
{
items?.map(({ title, tagline, description, href, image }) => (
<Tile href={href}>
<TileMedia>
<Image sizes="600px" {...image} />
</TileMedia>
<TileSpread>
<TileContent>
<TileDescription class="text-primary text-xs font-medium">
{tagline}
</TileDescription>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
{href && (
<Icon
name="arrow-right"
class="mr-2 size-5 shrink-0 transition-transform group-hover/tile:translate-x-2"
/>
)}
</TileSpread>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,100 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
text?: string
href?: string
icon?: string
target?: string
}[]
items?: {
title?: string
description?: string
image?: {
src: string
alt: string
}
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ text, icon, href, target }, i) => (
<Button
variant={i === 0 ? "default" : "outline"}
href={href}
target={target}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid size="lg">
{
items?.map(({ title, description, image, links }) => (
<Tile
class="dark aspect-4/3 justify-end rounded-md border-0 bg-black"
variant="floating"
>
<TileMedia class="absolute inset-0 size-full mask-b-from-0% mask-b-to-black/50">
<Image sizes="620px" {...image} />
</TileMedia>
<TileContent class="w-full text-white">
<TileTitle class="text-foreground text-3xl">{title}</TileTitle>
<TileDescription class="text-foreground text-lg">
{description}
</TileDescription>
</TileContent>
<SectionActions>
{links?.map(({ text, icon, href, target }, i) => (
<Button
variant={i === 0 ? "default" : "outline"}
href={href}
target={target}
>
{icon && <Icon name={icon} />}
{text}
</Button>
))}
</SectionActions>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,98 @@
---
import { Button } from "@/components/ui/button"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
import {
Tile,
TileActions,
TileContent,
TileDescription,
TileMedia,
TileSplit,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
text?: string
href?: string
}[]
items?: {
href?: string
tagline?: string
title?: string
description?: string
image?: {
src: string
alt: string
}
links?: {
text?: string
href?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit
class="items-start @5xl:grid-cols-[2fr_3fr] @5xl:*:sticky @5xl:*:top-[calc(var(--section-py)+var(--header-height,0px))]"
>
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid size="lg">
{
items?.map(({ title, tagline, description, image, links }) => (
<Tile>
<TileSplit>
<TileMedia>
<Image sizes="400px" {...image} />
</TileMedia>
<TileContent>
<TileDescription class="text-primary text-xs font-medium">
{tagline}
</TileDescription>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
<TileActions class="mt-2">
{links?.map(({ text, ...link }, i) => (
<Button
variant={i === 0 ? "outline" : "ghost"}
size="sm"
{...link}
>
{text}
</Button>
))}
</TileActions>
</TileContent>
</TileSplit>
</Tile>
))
}
</SectionGrid>
</SectionSplit>
</Section>

View file

@ -0,0 +1,94 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileSpread,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
text?: string
href?: string
}[]
items?: {
href?: string
tagline?: string
title?: string
description?: string
image?: {
src: string
alt: string
}
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSpread class="@5xl:items-end">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
</SectionContent>
<SectionActions>
{
links?.map(({ text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{text}
</Button>
))
}
</SectionActions>
</SectionSpread>
<SectionGrid>
{
items?.map(({ title, tagline, description, href, image }) => (
<Tile href={href} variant="floating">
<TileSpread class="py-3">
<TileContent>
<TileDescription class="text-primary text-xs font-medium">
{tagline}
</TileDescription>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
{href && (
<Icon
name="arrow-right"
class="mr-3 size-5 transition-transform group-hover/tile:translate-x-3"
/>
)}
</TileSpread>
<TileMedia class="-mr-6 rounded-r-none">
<Image sizes="600px" {...image} />
</TileMedia>
</Tile>
))
}
</SectionGrid>
</Section>

View file

@ -0,0 +1,96 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileSpread,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
text?: string
href?: string
}[]
items?: {
href?: string
tagline?: string
title?: string
description?: string
image?: {
src: string
alt: string
}
links?: {
text?: string
href?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit
class="items-start @5xl:grid-cols-[1fr_2fr] @5xl:*:sticky @5xl:*:top-[calc(var(--section-py)+var(--header-height,0px))]"
>
<SectionContent>
<SectionProse class="text-balance!">
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ text, ...link }, i) => (
<Button variant={i === 0 ? "outline" : "ghost"} {...link}>
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid class="@5xl:grid-cols-2">
{
items?.map(({ title, description, href, image }) => (
<Tile
href={href}
class="dark aspect-[1/1] bg-black!"
variant="floating"
>
<TileMedia class="absolute inset-0 size-full bg-black mask-t-from-0% mask-t-to-black/10">
<Image sizes="600px" {...image} />
</TileMedia>
<TileSpread class="relative">
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription class="text-foreground">
{description}
</TileDescription>
</TileContent>
{href && (
<Icon
name="arrow-right"
class="size-5 shrink-0 transition-transform group-hover/tile:translate-x-2"
/>
)}
</TileSpread>
</Tile>
))
}
</SectionGrid>
</SectionSplit>
</Section>

View file

@ -0,0 +1,87 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSplit,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileSpread,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
text?: string
href?: string
}[]
items?: {
href?: string
tagline?: string
title?: string
description?: string
image?: {
src: string
alt: string
}
links?: {
text?: string
href?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSplit
class="items-start @5xl:grid-cols-[1fr_2fr] @5xl:*:sticky @5xl:*:top-[calc(var(--section-py)+var(--header-height,0px))]"
>
<SectionContent>
<SectionProse class="text-balance!">
<slot />
</SectionProse>
<SectionActions>
{
links?.map(({ text, ...link }, i) => (
<Button variant={i === 0 ? "outline" : "ghost"} {...link}>
{text}
</Button>
))
}
</SectionActions>
</SectionContent>
<SectionGrid class="@5xl:grid-cols-2">
{
items?.map(({ title, description, href }) => (
<Tile href={href} variant="floating">
<TileSpread>
<TileContent>
<TileTitle>{title}</TileTitle>
<TileDescription class="text-foreground">
{description}
</TileDescription>
</TileContent>
{href && (
<Icon
name="arrow-right"
class="size-5 shrink-0 transition-transform group-hover/tile:translate-x-2"
/>
)}
</TileSpread>
</Tile>
))
}
</SectionGrid>
</SectionSplit>
</Section>

View file

@ -0,0 +1,93 @@
---
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Image } from "@/components/ui/image"
import {
Section,
SectionActions,
SectionContent,
SectionGrid,
SectionProse,
SectionSpread,
} from "@/components/ui/section"
import {
Tile,
TileContent,
TileDescription,
TileMedia,
TileSpread,
TileTitle,
} from "@/components/ui/tile"
interface Props {
class?: string
id?: string
links?: {
text?: string
href?: string
}[]
items?: {
href?: string
tagline?: string
title?: string
description?: string
image?: {
src: string
alt: string
}
links?: {
icon?: string
text?: string
href?: string
target?: string
}[]
}[]
}
const { class: className, id, links, items } = Astro.props
---
<Section class={className} id={id}>
<SectionSpread class="@5xl:items-end">
<SectionContent>
<SectionProse>
<slot />
</SectionProse>
</SectionContent>
<SectionActions>
{
links?.map(({ text, ...link }, i) => (
<Button variant={i === 0 ? "default" : "outline"} {...link}>
{text}
</Button>
))
}
</SectionActions>
</SectionSpread>
<SectionGrid>
{
items?.map(({ title, tagline, description, href, image }) => (
<Tile href={href} variant="floating">
<TileMedia class="-mx-6 -mt-6 aspect-4/3">
<Image class="size-full! object-cover" sizes="600px" {...image} />
</TileMedia>
<TileSpread>
<TileContent>
<TileDescription class="text-primary text-xs font-medium">
{tagline}
</TileDescription>
<TileTitle>{title}</TileTitle>
<TileDescription>{description}</TileDescription>
</TileContent>
{href && (
<Icon
name="arrow-right"
class="size-5 shrink-0 transition-transform group-hover/tile:translate-x-2"
/>
)}
</TileSpread>
</Tile>
))
}
</SectionGrid>
</Section>

Some files were not shown because too many files have changed in this diff Show more