yeet
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
---
|
||||
|
||||
# Project context
|
||||
|
||||
- You're building Zenblog, a headless blogging CMS.
|
||||
@@ -64,3 +64,66 @@ pre .copy-button {
|
||||
cursor: pointer;
|
||||
border-radius: 0 0 0 0.5rem;
|
||||
}
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/styles/globals.css",
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": false
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build": "next build",
|
||||
"build:web": "next build",
|
||||
"build:web": "next build --turbo",
|
||||
"dev": "concurrently \"next dev -p 8082\" \"npm run stripe:webhook\"",
|
||||
"stripe:webhook": "stripe listen --forward-to localhost:8082/api/webhooks/stripe",
|
||||
"lint": "next lint",
|
||||
@@ -79,8 +79,8 @@
|
||||
"ai": "^3.1.30",
|
||||
"bcrypt": "^5.1.1",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^0.2.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"dotenv": "^16.4.4",
|
||||
|
||||
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,73 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
interface MarqueeProps extends ComponentPropsWithoutRef<"div"> {
|
||||
/**
|
||||
* Optional CSS class name to apply custom styles
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Whether to reverse the animation direction
|
||||
* @default false
|
||||
*/
|
||||
reverse?: boolean;
|
||||
/**
|
||||
* Whether to pause the animation on hover
|
||||
* @default false
|
||||
*/
|
||||
pauseOnHover?: boolean;
|
||||
/**
|
||||
* Content to be displayed in the marquee
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Whether to animate vertically instead of horizontally
|
||||
* @default false
|
||||
*/
|
||||
vertical?: boolean;
|
||||
/**
|
||||
* Number of times to repeat the content
|
||||
* @default 4
|
||||
*/
|
||||
repeat?: number;
|
||||
}
|
||||
|
||||
export function Marquee({
|
||||
className,
|
||||
reverse = false,
|
||||
pauseOnHover = false,
|
||||
children,
|
||||
vertical = false,
|
||||
repeat = 4,
|
||||
...props
|
||||
}: MarqueeProps) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn(
|
||||
"group flex overflow-hidden p-2 [--duration:40s] [--gap:1rem] [gap:var(--gap)]",
|
||||
{
|
||||
"flex-row": !vertical,
|
||||
"flex-col": vertical,
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
{Array(repeat)
|
||||
.fill(0)
|
||||
.map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn("flex shrink-0 justify-around [gap:var(--gap)]", {
|
||||
"animate-marquee flex-row": !vertical,
|
||||
"animate-marquee-vertical flex-col": vertical,
|
||||
"group-hover:[animation-play-state:paused]": pauseOnHover,
|
||||
"[animation-direction:reverse]": reverse,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
||||
|
||||
export const TRIAL_PERIOD_DAYS = 14;
|
||||
|
||||
export const PricingPlanId = z.enum(["hobby", "pro", "free"]);
|
||||
export const PricingPlanId = z.enum(["pro", "free"]);
|
||||
export type PricingPlanId = z.infer<typeof PricingPlanId>;
|
||||
export const isPricingPlanId = (value: string): value is PricingPlanId =>
|
||||
PricingPlanId.safeParse(value).success;
|
||||
@@ -32,8 +32,7 @@ export type PricingPlan = {
|
||||
|
||||
export const MAX_BLOGS_PER_PLAN: Record<PricingPlanId, number> = {
|
||||
free: 1,
|
||||
hobby: 3,
|
||||
pro: 100,
|
||||
pro: 999,
|
||||
};
|
||||
|
||||
export const PRICING_PLANS: PricingPlan[] = [
|
||||
@@ -54,30 +53,12 @@ export const PRICING_PLANS: PricingPlan[] = [
|
||||
"Email support",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "hobby",
|
||||
highlight: false,
|
||||
title: "Starter",
|
||||
description: "Perfect for small teams",
|
||||
monthlyPrice: 29,
|
||||
yearlyPrice: 288, // will be divided by 12 in pricing page
|
||||
features: [
|
||||
`${MAX_BLOGS_PER_PLAN.hobby} blogs`,
|
||||
"3 authors",
|
||||
"Unlimited posts",
|
||||
"Unlimited categories",
|
||||
"Unlimited tags",
|
||||
"Unlimited API requests",
|
||||
"Unlimited media files",
|
||||
"Email support",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pro",
|
||||
title: "Professional",
|
||||
title: "Pro",
|
||||
description: "For growing teams",
|
||||
monthlyPrice: 59,
|
||||
yearlyPrice: 576,
|
||||
monthlyPrice: 20,
|
||||
yearlyPrice: 200,
|
||||
features: [
|
||||
"Unlimited blogs",
|
||||
"Unlimited authors",
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
export function formatDate(
|
||||
date: string | Date,
|
||||
options?: Intl.DateTimeFormatOptions
|
||||
) {
|
||||
return new Date(date).toLocaleString(
|
||||
undefined,
|
||||
options ?? {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
}
|
||||
);
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ import { CodeBlockComponent } from "@/components/code-block";
|
||||
import { Lora } from "next/font/google";
|
||||
import { motion } from "framer-motion";
|
||||
import { toast } from "sonner";
|
||||
import { Marquee } from "@/components/magicui/marquee";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const h1Font = Lora({
|
||||
subsets: ["latin"],
|
||||
@@ -189,6 +191,16 @@ const Home = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative mx-auto mt-12 max-w-5xl">
|
||||
<Marquee pauseOnHover className="[--duration:30s]">
|
||||
{tweets.map((tweet) => (
|
||||
<TweetItem key={tweet.username} {...tweet} />
|
||||
))}
|
||||
</Marquee>
|
||||
<div className="from-background pointer-events-none absolute inset-y-0 left-0 w-1/4 bg-gradient-to-r"></div>
|
||||
<div className="from-background pointer-events-none absolute inset-y-0 right-0 w-1/4 bg-gradient-to-l"></div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mt-24 max-w-5xl rounded-xl bg-slate-950 p-2 font-mono text-white">
|
||||
<div className="mb-3 p-2 px-4">
|
||||
<FaCode className="text-2xl text-emerald-400" />
|
||||
@@ -451,4 +463,93 @@ function PricingItem({
|
||||
);
|
||||
}
|
||||
|
||||
const tweets = [
|
||||
{
|
||||
content:
|
||||
"By the way, the CMS is @zenbloghq. A brilliant CMS that simply works. Integrating it with the website didn't take 5 minutes.",
|
||||
name: "Narix Hine",
|
||||
username: "leximory",
|
||||
image: "/static/tweets/leximory.jpg",
|
||||
},
|
||||
{
|
||||
content:
|
||||
"Just tried @zenbloghq last night and i started a blog and connected to my website with some help from cursor.The site is coming together, time for a real domain I think https://jesselawrence.replit.app/blog",
|
||||
name: "Jesse",
|
||||
username: "lawrencejessej",
|
||||
image: "/static/tweets/lawrencejessej.jpg",
|
||||
},
|
||||
{
|
||||
content:
|
||||
"I started using @zenbloghq yesterday. I literally got it running on my Astro site in 10 minutes. Really great product",
|
||||
name: "Alvaro",
|
||||
username: "metasurfero",
|
||||
image: "/static/tweets/metasurfero.jpg",
|
||||
},
|
||||
{
|
||||
content: "i use zenblog to manage the blog in zenblog",
|
||||
name: "jordi",
|
||||
username: "jordienr",
|
||||
image: "/static/tweets/jordienr.jpg",
|
||||
},
|
||||
{
|
||||
content: "zenblog gud",
|
||||
name: "william",
|
||||
username: "williamhzo",
|
||||
image: "/static/tweets/williamhzo.jpg",
|
||||
},
|
||||
];
|
||||
|
||||
const TweetItem = ({
|
||||
content,
|
||||
name,
|
||||
username,
|
||||
image,
|
||||
isThread = false,
|
||||
isLast = false,
|
||||
}: {
|
||||
content: string;
|
||||
name: string;
|
||||
username: string;
|
||||
image: string;
|
||||
isThread?: boolean;
|
||||
isLast?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex w-96 flex-col gap-2 rounded-xl p-4 hover:bg-slate-100/70",
|
||||
{
|
||||
"min-h-32": !isThread,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{isThread && !isLast ? (
|
||||
<div className="absolute left-[36px] top-[70px] h-full w-px bg-slate-200"></div>
|
||||
) : null}
|
||||
<Image
|
||||
src={image}
|
||||
alt={name}
|
||||
width={40}
|
||||
height={40}
|
||||
className="z-10 rounded-full border border-slate-100"
|
||||
/>
|
||||
<div className={cn("flex flex-col")}>
|
||||
<p className="text-sm font-medium">{name}</p>
|
||||
<p className="text-xs font-medium text-zinc-500">{username}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<p
|
||||
className={cn("text-sm", {
|
||||
"ml-12": isThread,
|
||||
})}
|
||||
>
|
||||
{content}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function Pricing() {
|
||||
Yearly (Save money!)
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 grid gap-4 sm:grid-cols-3">
|
||||
<div className="mt-4 grid gap-4 sm:grid-cols-2">
|
||||
{PRICING_PLANS.map((plan) => (
|
||||
<PricingCard
|
||||
key={plan.title}
|
||||
@@ -146,7 +146,7 @@ export function PricingCard({
|
||||
$
|
||||
</span>
|
||||
<span className="text-2xl font-medium text-slate-900">
|
||||
{yearlyToMonth}
|
||||
{yearlyToMonth.toFixed(2)}
|
||||
</span>{" "}
|
||||
per month <br />
|
||||
billed ${yearlyPrice} yearly
|
||||
|
||||
@@ -2,6 +2,70 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
@apply antialiased;
|
||||
}
|
||||
|
||||
@@ -15,75 +15,154 @@ module.exports = {
|
||||
"./src/**/*.{ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
border: {
|
||||
DEFAULT: "0.3px solid var(--border-color)",
|
||||
1: "1px",
|
||||
2: "2px",
|
||||
},
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
default: [
|
||||
"var(--font-sans)",
|
||||
"ui-sans-serif",
|
||||
"system-ui",
|
||||
"sans-serif",
|
||||
"Apple Color Emoji",
|
||||
"Segoe UI Emoji",
|
||||
"Segoe UI Symbol",
|
||||
"Noto Color Emoji",
|
||||
],
|
||||
mono: [
|
||||
"var(--font-mono)",
|
||||
"ui-monospace",
|
||||
"SFMono-Regular",
|
||||
"Menlo",
|
||||
"Monaco",
|
||||
"Consolas",
|
||||
"Liberation Mono",
|
||||
"Courier New",
|
||||
"monospace",
|
||||
],
|
||||
},
|
||||
textShadow: {
|
||||
sm: "0 1px 2px var(--tw-shadow-color)",
|
||||
DEFAULT: "0 2px 4px var(--tw-shadow-color)",
|
||||
lg: "0 8px 16px var(--tw-shadow-color)",
|
||||
},
|
||||
highlight: {
|
||||
DEFAULT: "shadow",
|
||||
},
|
||||
borderColor: {
|
||||
DEFAULT: "#e4e4e7",
|
||||
},
|
||||
|
||||
colors: {
|
||||
slate: {
|
||||
50: "#f9fafb",
|
||||
150: "#f4f5f7",
|
||||
},
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: 0 },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: 0 },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
border: {
|
||||
'1': '1px',
|
||||
'2': '2px',
|
||||
DEFAULT: '0.3px solid var(--border-color)'
|
||||
},
|
||||
container: {
|
||||
center: true,
|
||||
padding: '2rem',
|
||||
screens: {
|
||||
'2xl': '1400px'
|
||||
}
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
default: [
|
||||
'var(--font-sans)',
|
||||
'ui-sans-serif',
|
||||
'system-ui',
|
||||
'sans-serif',
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Segoe UI Symbol',
|
||||
'Noto Color Emoji'
|
||||
],
|
||||
mono: [
|
||||
'var(--font-mono)',
|
||||
'ui-monospace',
|
||||
'SFMono-Regular',
|
||||
'Menlo',
|
||||
'Monaco',
|
||||
'Consolas',
|
||||
'Liberation Mono',
|
||||
'Courier New',
|
||||
'monospace'
|
||||
]
|
||||
},
|
||||
textShadow: {
|
||||
sm: '0 1px 2px var(--tw-shadow-color)',
|
||||
DEFAULT: '0 2px 4px var(--tw-shadow-color)',
|
||||
lg: '0 8px 16px var(--tw-shadow-color)'
|
||||
},
|
||||
highlight: {
|
||||
DEFAULT: 'shadow'
|
||||
},
|
||||
borderColor: {
|
||||
DEFAULT: '#e4e4e7'
|
||||
},
|
||||
colors: {
|
||||
slate: {
|
||||
'50': '#f9fafb',
|
||||
'150': '#f4f5f7'
|
||||
},
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))'
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))'
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))'
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))'
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))'
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))'
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))'
|
||||
},
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
chart: {
|
||||
'1': 'hsl(var(--chart-1))',
|
||||
'2': 'hsl(var(--chart-2))',
|
||||
'3': 'hsl(var(--chart-3))',
|
||||
'4': 'hsl(var(--chart-4))',
|
||||
'5': 'hsl(var(--chart-5))'
|
||||
}
|
||||
},
|
||||
keyframes: {
|
||||
'infinite-scroll': {
|
||||
from: {
|
||||
transform: 'translateX(0)'
|
||||
},
|
||||
to: {
|
||||
transform: 'translateX(-100%)'
|
||||
}
|
||||
},
|
||||
'accordion-down': {
|
||||
from: {
|
||||
height: 0
|
||||
},
|
||||
to: {
|
||||
height: 'var(--radix-accordion-content-height)'
|
||||
}
|
||||
},
|
||||
'accordion-up': {
|
||||
from: {
|
||||
height: 'var(--radix-accordion-content-height)'
|
||||
},
|
||||
to: {
|
||||
height: 0
|
||||
}
|
||||
},
|
||||
marquee: {
|
||||
from: {
|
||||
transform: 'translateX(0)'
|
||||
},
|
||||
to: {
|
||||
transform: 'translateX(calc(-100% - var(--gap)))'
|
||||
}
|
||||
},
|
||||
'marquee-vertical': {
|
||||
from: {
|
||||
transform: 'translateY(0)'
|
||||
},
|
||||
to: {
|
||||
transform: 'translateY(calc(-100% - var(--gap)))'
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||
'infinite-scroll': 'infinite-scroll 25s linear infinite',
|
||||
marquee: 'marquee var(--duration) infinite linear',
|
||||
'marquee-vertical': 'marquee-vertical var(--duration) linear infinite'
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
require("tailwindcss-animate"),
|
||||
|
||||