Compare commits

...

6 commits

408 changed files with 22928 additions and 15280 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"
}
}

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,73 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="900" viewBox="0 0 1600 900" fill="none" role="img" aria-label="IF.Trace verification flow diagram">
<defs>
<linearGradient id="g" x1="240" y1="180" x2="1360" y2="720" gradientUnits="userSpaceOnUse">
<stop stop-color="#60A5FA" stop-opacity="0.95" />
<stop offset="0.5" stop-color="#34D399" stop-opacity="0.85" />
<stop offset="1" stop-color="#F472B6" stop-opacity="0.85" />
</linearGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="10" result="b" />
<feColorMatrix
in="b"
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.35 0"
/>
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<style>
.card { fill: rgba(2,6,23,0.35); stroke: rgba(255,255,255,0.10); }
.title { fill: rgba(255,255,255,0.92); font: 600 28px ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji"; }
.sub { fill: rgba(255,255,255,0.70); font: 500 18px ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; }
.mono { fill: rgba(255,255,255,0.75); font: 500 16px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
.stroke { stroke: rgba(226,232,240,0.70); stroke-width: 2.5; }
</style>
</defs>
<rect x="0" y="0" width="1600" height="900" rx="36" fill="rgba(2,6,23,0.08)" />
<path
d="M250 740 C 520 560, 740 600, 920 420 C 1080 260, 1240 260, 1360 180"
stroke="url(#g)"
stroke-width="10"
stroke-linecap="round"
stroke-opacity="0.35"
filter="url(#glow)"
/>
<g>
<rect class="card" x="190" y="160" width="360" height="170" rx="22" />
<text class="title" x="230" y="218">1) Keep it private</text>
<text class="sub" x="230" y="254">You hold the source.</text>
<text class="mono" x="230" y="292">source_sha256</text>
</g>
<g>
<rect class="card" x="620" y="220" width="360" height="170" rx="22" />
<text class="title" x="660" y="278">2) Hash the output</text>
<text class="sub" x="660" y="314">What you will share.</text>
<text class="mono" x="660" y="352">output_sha256</text>
</g>
<g>
<rect class="card" x="1040" y="160" width="390" height="170" rx="22" />
<text class="title" x="1080" y="218">3) Publish a receipt</text>
<text class="sub" x="1080" y="254">No login required.</text>
<text class="mono" x="1080" y="292">/trace/…</text>
</g>
<g>
<rect class="card" x="900" y="540" width="530" height="190" rx="22" />
<text class="title" x="940" y="602">4) Third party verifies</text>
<text class="sub" x="940" y="638">They download bytes and compare hashes.</text>
<text class="mono" x="940" y="676">verified == (hashes match)</text>
</g>
<path class="stroke" d="M550 245 C 610 245, 600 260, 620 270" />
<path class="stroke" d="M980 270 C 1025 260, 1005 245, 1040 245" />
<path class="stroke" d="M1235 330 C 1235 400, 1170 450, 1100 540" />
<path class="stroke" d="M800 390 C 800 460, 880 500, 980 540" />
<text class="sub" x="190" y="800">Integrity claims only: byte-level verification, not interpretation.</text>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,49 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" fill="none" role="img" aria-label="IF.Trace — Open verification for confidential work">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1200" y2="630" gradientUnits="userSpaceOnUse">
<stop stop-color="#0B1020" />
<stop offset="0.55" stop-color="#07122A" />
<stop offset="1" stop-color="#0B1020" />
</linearGradient>
<linearGradient id="g" x1="210" y1="110" x2="980" y2="520" gradientUnits="userSpaceOnUse">
<stop stop-color="#60A5FA" />
<stop offset="0.55" stop-color="#34D399" />
<stop offset="1" stop-color="#F472B6" />
</linearGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="18" result="b" />
<feColorMatrix
in="b"
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.45 0"
/>
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<style>
.h1 { fill: rgba(255,255,255,0.96); font: 800 88px ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; letter-spacing: -0.02em; }
.sub { fill: rgba(255,255,255,0.78); font: 600 30px ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; }
.small { fill: rgba(255,255,255,0.68); font: 600 22px ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; }
.mono { fill: rgba(226,232,240,0.80); font: 600 18px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
</style>
</defs>
<rect x="0" y="0" width="1200" height="630" rx="44" fill="url(#bg)" />
<path
d="M165 505 C 380 335, 520 360, 670 250 C 800 155, 930 160, 1040 105"
stroke="url(#g)"
stroke-width="12"
stroke-linecap="round"
stroke-opacity="0.45"
filter="url(#glow)"
/>
<text class="small" x="110" y="140">IF.Transparent &gt; IF.Traceable &gt; IF.Trustworthy</text>
<text class="h1" x="110" y="260">IF.Trace</text>
<text class="sub" x="110" y="320">Confidential documents → open verification</text>
<text class="mono" x="110" y="390">source_sha256 → output_sha256 → /trace/…</text>
<text class="small" x="110" y="460">3rd party audit trails, without publishing the source.</text>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,141 @@
---
title: IF.Trace External Review Packet
last_updated_utc: 2025-12-31
site: https://infrafabric.io/
packet: https://infrafabric.io/review/
raw_packet: https://infrafabric.io/review/index.md
---
# IF.Trace External Review Packet (Public)
This packet exists because many “AI browser / fetch” tools cannot load a live site reliably, even when it is static HTML.
If you cannot fetch `https://infrafabric.io/` in your environment, use this packet instead. It contains the live URLs, the current plain-language intent, and screenshots.
## 1) What You Are Reviewing (plain description)
IF.Trace is a small public website for a verification protocol.
Goal: **let a third party verify the integrity of confidential work without getting logins**.
The promise is intentionally narrow:
- If the bytes someone downloads match the hashes on the receipt, it is verified.
- IF.Trace does **not** claim “correctness” or “truth” of the content.
## 2) Who The Buyer Is (working assumption)
Primary buyers we are optimizing for:
- people responsible for external review outcomes (procurement / audit / legal / security / research review)
- people who get blamed when proof is missing later
People we are not optimizing for:
- casual readers
- “cool protocol vibes” audiences
## 3) Languages
Live language options:
- English (default)
- French (`/fr/`)
Request to reviewers:
- evaluate whether the language feels like “buyer language” in both EN and FR
- flag any phrases that sound technical, salesy, or “inside baseball”
## 4) Site Map (what exists)
Main routes (public):
- Home: `https://infrafabric.io/`
- Sector: `https://infrafabric.io/verticals/`
- Pricing: `https://infrafabric.io/pricing/`
- API / developer surface: `https://infrafabric.io/api/`
- Whitepaper: `https://infrafabric.io/whitepaper/`
- About: `https://infrafabric.io/about/`
- Governance: `https://infrafabric.io/governance/`
- Review packet (this): `https://infrafabric.io/review/`
- FR: `https://infrafabric.io/fr/`
Header nav is intentionally minimal:
- `Sector | Pricing | API`
## 5) Current “Home Page” Intent (the core promise)
What we want the user to understand quickly:
- This is a way to share proof with outsiders (no login).
- The verification is a simple yes/no integrity check (hashes match).
- It supports offline bundles for review environments.
If any of that reads unclear, untrustworthy, or “too clever”, call it out.
## 6) Screenshots (desktop + mobile)
Desktop:
- Home: `https://infrafabric.io/review/screens/desktop/home.png`
- Sector: `https://infrafabric.io/review/screens/desktop/verticals.png`
- Pricing: `https://infrafabric.io/review/screens/desktop/pricing.png`
- API: `https://infrafabric.io/review/screens/desktop/api.png`
Mobile:
- Home: `https://infrafabric.io/review/screens/mobile/home.png`
- Sector: `https://infrafabric.io/review/screens/mobile/verticals.png`
- Pricing: `https://infrafabric.io/review/screens/mobile/pricing.png`
- API: `https://infrafabric.io/review/screens/mobile/api.png`
Navigation flow (useful for “first 20 seconds” critique):
- Desktop: `https://infrafabric.io/review/screens/desktop/flow/01-home.png``02-sector.png``03-pricing.png``04-api.png`
- Mobile: `https://infrafabric.io/review/screens/mobile/flow/01-home.png``02-sector.png``03-pricing.png``04-api.png`
## 7) What We Want From You (constructive, objective critique)
Please answer in a structured way.
### A) Your model and your limits
- Model name and version (if you know it)
- Knowledge cutoff (if you know it)
- Can you fetch live sites / run JavaScript, or only read text?
### B) “10-second test”
After 10 seconds on the home page, can you answer:
1) What is this?
2) Who is it for?
3) What can I do with it right now?
If not, say what blocked you.
### C) Trust signals (what makes you believe it)
From the page alone:
- What feels credible?
- What feels vague?
- What feels like marketing theatre?
### D) Cognitive load (mental effort)
- What words / sections require re-reading?
- What could be removed without losing meaning?
- What would you add **only if it reduces confusion**?
### E) Buyer psychology (the “sold” moment)
Assume a cautious buyer who has been burned before:
- What makes them say “yes, this is the right shape of solution”?
- What makes them say “no, this is risky / unclear / too early”?
### F) Layout and structure
Compare to other “serious verification / trust infrastructure” sites:
- Is the layout lean enough?
- Is it missing any “minimum viable” credibility elements?
- Is the ordering of sections right?
### G) Language quality (EN + FR)
- Any phrases that sound like a bot, like a pitch, or like an academic paper?
- Any phrases that sound culturally off in French?
### H) Concrete patches
Provide 310 actionable edits (copy or layout). No vague advice.
## 8) Bias Control (important)
Please do not be polite. Assume we prefer accuracy over encouragement.
If you find yourself agreeing with everything, pause and look for weaknesses:
- who would *not* trust this, and why?
- what could be misunderstood and cause harm?

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

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>

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