svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+export {
+ Avatar,
+ AvatarImage,
+ AvatarFallback,
+ AvatarGroup,
+ AvatarGroupCount,
+ AvatarBadge,
+}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..75b8c3d
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,67 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Slot } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
+ outline:
+ "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
+ ghost:
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
+ destructive:
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default:
+ "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
+ icon: "size-8",
+ "icon-xs":
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
+ "icon-sm":
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
+ "icon-lg": "size-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant = "default",
+ size = "default",
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps
& {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot.Root : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/link.tsx b/src/components/ui/link.tsx
new file mode 100644
index 0000000..0e29628
--- /dev/null
+++ b/src/components/ui/link.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+import { Button } from "./button";
+
+export interface LinkProps extends React.ComponentProps {
+ href: string;
+ children: React.ReactNode;
+}
+
+const Link: React.FC = ({ href, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export { Link };
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..72465b2
--- /dev/null
+++ b/src/components/ui/tabs.tsx
@@ -0,0 +1,88 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Tabs as TabsPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Tabs({
+ className,
+ orientation = "horizontal",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+const tabsListVariants = cva(
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
+ {
+ variants: {
+ variant: {
+ default: "bg-muted",
+ line: "gap-1 bg-transparent",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function TabsList({
+ className,
+ variant = "default",
+ ...props
+}: React.ComponentProps &
+ VariantProps) {
+ return (
+
+ )
+}
+
+function TabsTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
diff --git a/src/components/views/Footer.astro b/src/components/views/Footer.astro
new file mode 100644
index 0000000..57461ed
--- /dev/null
+++ b/src/components/views/Footer.astro
@@ -0,0 +1,9 @@
+---
+import { FooterBar } from "@/components/views/footer-bar"
+---
+
+
diff --git a/src/components/views/Header.astro b/src/components/views/Header.astro
new file mode 100644
index 0000000..76bd15b
--- /dev/null
+++ b/src/components/views/Header.astro
@@ -0,0 +1,40 @@
+---
+import { Link } from "@/components/ui/link";
+import { HeaderNav } from "@/components/views/header-nav";
+
+const pathname = Astro.url.pathname;
+---
+
+
diff --git a/src/components/views/footer-bar.tsx b/src/components/views/footer-bar.tsx
new file mode 100644
index 0000000..ac320fd
--- /dev/null
+++ b/src/components/views/footer-bar.tsx
@@ -0,0 +1,106 @@
+import { Button } from "@/components/ui/button";
+import { footerNavLinks, socialLinks } from "@/lib/nav-links";
+import { cn } from "@/lib/utils";
+
+function SocialIcon({ icon }: { icon: (typeof socialLinks)[number]["icon"] }) {
+ const className = "size-4";
+
+ switch (icon) {
+ case "facebook":
+ return (
+
+
+
+ );
+ case "instagram":
+ return (
+
+
+
+
+
+ );
+ case "x":
+ return (
+
+
+
+ );
+ case "telegram":
+ return (
+
+
+
+ );
+ }
+}
+
+type FooterBarProps = {
+ className?: string;
+};
+
+export function FooterBar({ className }: FooterBarProps) {
+ return (
+
+
+ {footerNavLinks.map((link) => (
+
+ {link.label}
+
+ ))}
+
+
+ {socialLinks.map((social) => (
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/views/header-nav.tsx b/src/components/views/header-nav.tsx
new file mode 100644
index 0000000..f9d1b80
--- /dev/null
+++ b/src/components/views/header-nav.tsx
@@ -0,0 +1,32 @@
+import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { getActiveNavTab, headerNavLinks } from "@/lib/nav-links"
+import { cn } from "@/lib/utils"
+
+type HeaderNavProps = {
+ pathname?: string
+ className?: string
+}
+
+export function HeaderNav({ pathname = "/", className }: HeaderNavProps) {
+ const activeTab = getActiveNavTab(pathname)
+
+ return (
+
+
+ {headerNavLinks.map((link) => (
+
+ {link.label}
+
+ ))}
+
+
+ )
+}
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index f6b55d4..d729104 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -1,23 +1,31 @@
+---
+import "../styles/global.css";
+import Header from "@/components/views/Header.astro";
+import Footer from "@/components/views/Footer.astro";
+
+interface Props {
+ title?: string;
+}
+
+const { title = "نکستا" } = Astro.props;
+---
+
-
+
-
+
+
- Astro Basics
+ {title}
-
+
+
+
+
+
-
-
diff --git a/src/lib/nav-links.ts b/src/lib/nav-links.ts
new file mode 100644
index 0000000..7ea7784
--- /dev/null
+++ b/src/lib/nav-links.ts
@@ -0,0 +1,27 @@
+export const headerNavLinks = [
+ { value: "plans", label: "پلنها", href: "/plans" },
+ { value: "contact", label: "تماس با ما", href: "/contact" },
+ { value: "blog", label: "بلاگ", href: "/blog" },
+] as const
+
+export const footerNavLinks = [
+ { label: "تماس با ما", href: "/contact" },
+ { label: "بلاگ", href: "/blog" },
+] as const
+
+export const socialLinks = [
+ { label: "Facebook", href: "https://facebook.com", icon: "facebook" },
+ { label: "Instagram", href: "https://instagram.com", icon: "instagram" },
+ { label: "X", href: "https://x.com", icon: "x" },
+ { label: "Telegram", href: "https://t.me", icon: "telegram" },
+] as const
+
+export type HeaderNavValue = (typeof headerNavLinks)[number]["value"]
+
+export function getActiveNavTab(pathname: string): HeaderNavValue {
+ const path = pathname.replace(/\/$/, "") || "/"
+
+ if (path === "/blog" || path.startsWith("/blog/")) return "blog"
+ if (path === "/contact" || path.startsWith("/contact/")) return "contact"
+ return "plans"
+}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..bd0c391
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/src/pages/blog.astro b/src/pages/blog.astro
new file mode 100644
index 0000000..eb698be
--- /dev/null
+++ b/src/pages/blog.astro
@@ -0,0 +1,9 @@
+---
+import Layout from "../layouts/Layout.astro";
+---
+
+
+
+
diff --git a/src/pages/contact.astro b/src/pages/contact.astro
new file mode 100644
index 0000000..463cbf0
--- /dev/null
+++ b/src/pages/contact.astro
@@ -0,0 +1,9 @@
+---
+import Layout from "../layouts/Layout.astro";
+---
+
+
+
+
diff --git a/src/pages/index.astro b/src/pages/index.astro
index c04f360..6ce0937 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,11 +1,3 @@
---
-import Welcome from '../components/Welcome.astro';
-import Layout from '../layouts/Layout.astro';
-
-// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
-// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
+return Astro.redirect("/plans");
---
-
-
-
-
diff --git a/src/pages/plans.astro b/src/pages/plans.astro
new file mode 100644
index 0000000..18eba28
--- /dev/null
+++ b/src/pages/plans.astro
@@ -0,0 +1,9 @@
+---
+import Layout from "../layouts/Layout.astro";
+---
+
+
+
+
diff --git a/src/styles/global.css b/src/styles/global.css
new file mode 100644
index 0000000..d151149
--- /dev/null
+++ b/src/styles/global.css
@@ -0,0 +1,227 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+@import "shadcn/tailwind.css";
+@import "@fontsource-variable/geist";
+
+@custom-variant dark (&:is(.dark *));
+@theme inline {
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --radius-2xl: calc(var(--radius) + 8px);
+ --radius-3xl: calc(var(--radius) + 12px);
+ --radius-4xl: calc(var(--radius) + 16px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+ /* Figma palette extras */
+ --color-tertiary: var(--tertiary);
+ --color-tertiary-foreground: var(--tertiary-foreground);
+ --color-foreground-disabled: var(--foreground-disabled);
+}
+
+/* Figma palette: primary #A5FF01, bg #060912, card #17181C, text main #EFEEEA, text sec #A2A09B, text disabled #363636, tertiary #32D9CB, secondary #00A6C0 */
+:root {
+ --radius: 0.625rem;
+ /* Background */
+ --background: #060912;
+ /* Text main */
+ --foreground: #efeeea;
+ /* Card */
+ --card: #17181c;
+ --card-foreground: #efeeea;
+ --popover: #17181c;
+ --popover-foreground: #efeeea;
+ /* Primary */
+ --primary: #a5ff01;
+ --primary-foreground: #060912;
+ /* Secondary */
+ --secondary: #00a6c0;
+ --secondary-foreground: #efeeea;
+ /* Muted / surfaces */
+ --muted: #17181c;
+ /* Text secondary */
+ --muted-foreground: #a2a09b;
+ /* Tertiary = accent */
+ --tertiary: #32d9cb;
+ --tertiary-foreground: #17181c;
+ --accent: #32d9cb;
+ --accent-foreground: #17181c;
+ /* Text disabled */
+ --foreground-disabled: #363636;
+ --destructive: #ef4444;
+ --border: #2d3b2e;
+ --input: #2d3b2e;
+ --ring: #a5ff01;
+ --chart-1: #a5ff01;
+ --chart-2: #00a6c0;
+ --chart-3: #32d9cb;
+ --chart-4: #efeeea;
+ --chart-5: #a2a09b;
+ --sidebar: #17181c;
+ --sidebar-foreground: #efeeea;
+ --sidebar-primary: #a5ff01;
+ --sidebar-primary-foreground: #060912;
+ --sidebar-accent: #00a6c0;
+ --sidebar-accent-foreground: #efeeea;
+ --sidebar-border: #2d3b2e;
+ --sidebar-ring: #a5ff01;
+}
+
+.dark {
+ --background: #060912;
+ --foreground: #efeeea;
+ --card: #17181c;
+ --card-foreground: #efeeea;
+ --popover: #17181c;
+ --popover-foreground: #efeeea;
+ --primary: #a5ff01;
+ --primary-foreground: #060912;
+ --secondary: #00a6c0;
+ --secondary-foreground: #efeeea;
+ --muted: #17181c;
+ --muted-foreground: #a2a09b;
+ --tertiary: #32d9cb;
+ --tertiary-foreground: #17181c;
+ --accent: #32d9cb;
+ --accent-foreground: #17181c;
+ --foreground-disabled: #363636;
+ --destructive: #ef4444;
+ --border: #2d3b2e;
+ --input: #2d3b2e;
+ --ring: #a5ff01;
+ --chart-1: #a5ff01;
+ --chart-2: #00a6c0;
+ --chart-3: #32d9cb;
+ --chart-4: #efeeea;
+ --chart-5: #a2a09b;
+ --sidebar: #17181c;
+ --sidebar-foreground: #efeeea;
+ --sidebar-primary: #a5ff01;
+ --sidebar-primary-foreground: #060912;
+ --sidebar-accent: #00a6c0;
+ --sidebar-accent-foreground: #efeeea;
+ --sidebar-border: #2d3b2e;
+ --sidebar-ring: #a5ff01;
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+
+ html {
+ min-height: 100vh;
+ min-height: 100dvh;
+ }
+
+ body {
+ @apply bg-background text-foreground;
+ min-height: 100vh;
+ min-height: 100dvh;
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ font-family: "iransansxv", sans-serif;
+ }
+}
+
+@layer utilities {
+ .chat-messages-scroll {
+ scrollbar-width: thin;
+ scrollbar-color: var(--primary) transparent;
+ }
+
+ .chat-messages-scroll::-webkit-scrollbar {
+ width: 8px;
+ }
+
+ .chat-messages-scroll::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ .chat-messages-scroll::-webkit-scrollbar-thumb {
+ background-color: color-mix(in oklab, var(--primary) 70%, transparent);
+ border-radius: 9999px;
+ }
+
+ .chat-messages-scroll::-webkit-scrollbar-thumb:hover {
+ background-color: color-mix(in oklab, var(--primary) 90%, transparent);
+ }
+
+ .chat-messages-scroll::-webkit-scrollbar-button {
+ -webkit-appearance: none;
+ display: none !important;
+ width: 0 !important;
+ height: 0 !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ border: 0 !important;
+ background: transparent !important;
+ }
+
+ .chat-messages-scroll::-webkit-scrollbar-button:single-button,
+ .chat-messages-scroll::-webkit-scrollbar-button:start:decrement,
+ .chat-messages-scroll::-webkit-scrollbar-button:end:increment,
+ .chat-messages-scroll::-webkit-scrollbar-button:vertical:start:decrement,
+ .chat-messages-scroll::-webkit-scrollbar-button:vertical:end:increment {
+ -webkit-appearance: none;
+ display: none !important;
+ width: 0 !important;
+ height: 0 !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ border: 0 !important;
+ background: transparent !important;
+ }
+
+ .scrollbar-styled {
+ scrollbar-width: thin;
+ scrollbar-color: rgb(var(--muted)) transparent;
+ }
+
+ .scrollbar-styled::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ .scrollbar-styled::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ .scrollbar-styled::-webkit-scrollbar-thumb {
+ background-color: rgb(var(--muted));
+ border-radius: 3px;
+ }
+
+ .scrollbar-styled::-webkit-scrollbar-thumb:hover {
+ background-color: rgb(var(--muted) / 0.8);
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
index 8bf91d3..ca028d9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,4 +1,10 @@
{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]