Add website files
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
# bokhary-homepage
|
# bokhary-homepage
|
||||||
|
|
||||||
Homepage for bokhary.ir - coded in typescript with react.js and tailwind
|
A cute homepage for bokhary.ir
|
||||||
23
eslint.config.js
Normal file
23
eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
reactHooks.configs.flat.recommended,
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/image.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Bokhary | بخاری</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
package.json
Normal file
36
package.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "bokhary-homepage",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/vazirmatn": "^5.2.8",
|
||||||
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"@tsparticles/all": "^3.9.1",
|
||||||
|
"@tsparticles/engine": "^3.9.1",
|
||||||
|
"@tsparticles/react": "^3.0.0",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"tailwindcss": "^4.1.18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@types/react": "^19.2.5",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
|
"eslint": "^9.39.1",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
|
"globals": "^16.5.0",
|
||||||
|
"typescript": "~5.9.3",
|
||||||
|
"typescript-eslint": "^8.46.4",
|
||||||
|
"vite": "^7.2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
3221
pnpm-lock.yaml
generated
Normal file
3221
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- '@tsparticles/engine'
|
||||||
|
- esbuild
|
||||||
BIN
public/image.png
Normal file
BIN
public/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 405 KiB |
16
src/components/Bokhary.tsx
Normal file
16
src/components/Bokhary.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export default function Bokhary() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen grid place-items-center">
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<img
|
||||||
|
src="/image.png"
|
||||||
|
alt="Bokhary"
|
||||||
|
className="w-64 h-auto drop-shadow-[0_0_15px_rgba(255,255,255,0.8)]"
|
||||||
|
/>
|
||||||
|
<span className="text-4xl font-bold text-orange-200 -translate-y-18 font-vazirmatn [text-shadow:0_0_5px_rgba(0,0,0,1),0_0_10px_rgba(0,0,0,1),0_0_20px_rgba(0,0,0,0.9),0_0_30px_rgba(0,0,0,0.8)]">
|
||||||
|
بـخــــاری
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
src/components/Button.tsx
Normal file
23
src/components/Button.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { MouseEventHandler } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ModalButton({ onClick, children }: Props) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
className="relative px-5 py-1.5 rounded-lg overflow-hidden transition-all duration-300 mx-2 my-1.5
|
||||||
|
bg-linear-to-br from-orange-300/60 to-orange-400/90
|
||||||
|
hover:scale-105 active:scale-95
|
||||||
|
text-white font-medium shadow-md font-vazirmatn
|
||||||
|
before:absolute before:inset-0 before:bg-white/10 before:opacity-0 hover:before:opacity-100
|
||||||
|
before:transition-opacity before:duration-300"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
55
src/components/CodeBlock.tsx
Normal file
55
src/components/CodeBlock.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
code: string;
|
||||||
|
fileName: string;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CodeBlock({ code, fileName, className }: Props) {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const copyToClipboard = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(code);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 1000);
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
rounded-lg bg-gray-900 text-sm text-gray-100
|
||||||
|
${className}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between border-b border-gray-800 px-4 py-2 bg-gray-800 rounded-t-lg">
|
||||||
|
<span className="text-xs font-medium text-gray-300">
|
||||||
|
{fileName}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
dir='rtl'
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
className={`
|
||||||
|
flex items-center gap-1 px-2.5 py-0.5 text-xs font-medium
|
||||||
|
rounded transition-colors duration-200
|
||||||
|
${
|
||||||
|
copied
|
||||||
|
? "bg-green-600 hover:bg-green-500 text-white"
|
||||||
|
: "bg-gray-700 hover:bg-gray-600 text-gray-200"
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{copied ? "Copied" : "Copy"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre className="overflow-x-auto whitespace-pre-wrap wrap-break-word p-4">
|
||||||
|
<code>{code}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
src/components/Element.tsx
Normal file
7
src/components/Element.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default function Bokhary() {
|
||||||
|
return (
|
||||||
|
<a href="https://element.bokhary.ir" target="_blank" rel="noreferrer" className="">
|
||||||
|
Open in Element
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
30
src/components/FloatingButton.tsx
Normal file
30
src/components/FloatingButton.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { MouseEventHandler } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Button({ onClick, text }: Props) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className="group relative px-5 py-3 rounded-full overflow-hidden transition-all duration-300 hover:scale-105 active:scale-95 cursor-pointer mx-1"
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-orange-500/10 backdrop-blur-xl border border-orange-400/30 rounded-full"></div>
|
||||||
|
|
||||||
|
<div className="absolute inset-0 bg-linear-to-br from-orange-400/20 via-orange-500/5 to-transparent rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
|
||||||
|
<div className="absolute inset-0 bg-linear-to-r from-transparent via-orange-300/30 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000 rounded-full"></div>
|
||||||
|
|
||||||
|
<div className="absolute inset-0 rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-300 blur-xl bg-orange-500/40"></div>
|
||||||
|
|
||||||
|
<div className="absolute inset-px rounded-full bg-linear-to-b from-orange-400/10 to-transparent"></div>
|
||||||
|
|
||||||
|
<span className="relative flex items-center gap-2 text-white font-medium text-lg font-vazirmatn">
|
||||||
|
{text}
|
||||||
|
<svg className="w-6 h-6 transition-transform group-hover:rotate-12 duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
124
src/components/Modal.tsx
Normal file
124
src/components/Modal.tsx
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
|
import Button from "./Button";
|
||||||
|
import CodeBlock from "./CodeBlock";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hosts = `
|
||||||
|
# Bokhary Hosts
|
||||||
|
37.32.13.184 bokhary.ir
|
||||||
|
37.32.13.184 cinny.bokhary.ir
|
||||||
|
37.32.13.184 element.bokhary.ir
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
export default function Modal({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
title = "راهنمای استفاده",
|
||||||
|
}: Props) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
const handler = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") onClose();
|
||||||
|
};
|
||||||
|
document.addEventListener("keydown", handler);
|
||||||
|
return () => document.removeEventListener("keydown", handler);
|
||||||
|
}, [isOpen, onClose]);
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
fixed inset-0 z-50 flex items-center justify-center
|
||||||
|
bg-black/30 backdrop-blur-sm transition-opacity duration-300
|
||||||
|
${isOpen ? "opacity-100" : "opacity-0 pointer-events-none"}
|
||||||
|
`}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
relative w-xl max-w-[90%] bg-linear-to-br from-orange-600/90 to-orange-300/80
|
||||||
|
rounded-xl pb-6 text-center text-white shadow-xl
|
||||||
|
transform transition-all duration-300
|
||||||
|
${isOpen ? "scale-100" : "scale-95"}
|
||||||
|
max-h-[80vh]
|
||||||
|
`}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
style={{
|
||||||
|
scrollbarWidth: "none",
|
||||||
|
msOverflowStyle: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-between px-4 py-3 bg-black/20 rounded-t-xl"
|
||||||
|
style={{ background: "inherit" }}
|
||||||
|
>
|
||||||
|
<p className="text-lg font-bold font-vazirmatn" dir="rtl">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
aria-label="Close"
|
||||||
|
className="text-2xl hover:text-orange-200 transition-colors"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
overflow-y-auto overflow-x-hidden
|
||||||
|
max-h-[calc(79vh-3rem)] p-8 pt-0
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<style>{`
|
||||||
|
div::-webkit-scrollbar { display: none; }
|
||||||
|
`}</style>
|
||||||
|
|
||||||
|
<p className="mt-8 text-lg leading-relaxed font-vazirmatn text-center font-bold" dir="rtl">
|
||||||
|
کلاینتهای تحت وب
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Button onClick={() => open("https://element.bokhary.ir", "_blank")}>المنت وب</Button>
|
||||||
|
<Button onClick={() => open("https://cinny.bokhary.ir", "_blank")}>سینی</Button>
|
||||||
|
|
||||||
|
<p className="mt-8 text-lg leading-relaxed font-vazirmatn text-center font-bold" dir="rtl">
|
||||||
|
کلاینتهای اندروید
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Button onClick={() => open("https://bokhary.ir/apps/Element.apk", "_blank")}>المنت</Button>
|
||||||
|
<Button onClick={() => open("https://bokhary.ir/apps/elementx.apk", "_blank")}>المنت ایکس</Button>
|
||||||
|
<Button onClick={() => open("https://bokhary.ir/apps/fluffy.apk", "_blank")}>فلافی چت</Button>
|
||||||
|
|
||||||
|
<p className="mt-8 text-md leading-relaxed font-vazirmatn text-center" dir="rtl">
|
||||||
|
برای رفع اختلالات DNS مقادیر زیر را به فایل hosts سیستمعامل خود اضافه کنید.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CodeBlock fileName="Your Hosts file" code={hosts} className="mt-3 text-left" />
|
||||||
|
|
||||||
|
<p className="mt-8 text-md leading-relaxed font-vazirmatn text-center" dir="rtl">
|
||||||
|
یا در اندروید از نسخه تغییر یافته AdAway استفاده کنید.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
open(
|
||||||
|
"https://s34.picofile.com/file/8489250218/AdAway_6_1_4.apk.html",
|
||||||
|
"_blank"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
AdAway
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
}
|
||||||
57
src/components/Particles.tsx
Normal file
57
src/components/Particles.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { useEffect, useState, memo } from "react";
|
||||||
|
|
||||||
|
import Particles, { initParticlesEngine } from "@tsparticles/react";
|
||||||
|
import { loadAll } from "@tsparticles/all";
|
||||||
|
import type { ISourceOptions } from "@tsparticles/engine";
|
||||||
|
|
||||||
|
function BackgroundParticles() {
|
||||||
|
const [init, setInit] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initParticlesEngine(async (engine) => {
|
||||||
|
await loadAll(engine);
|
||||||
|
}).then(() => setInit(true));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const options: ISourceOptions = {
|
||||||
|
background: { color: { value: "#2b2b2b" } },
|
||||||
|
fpsLimit: 60,
|
||||||
|
particles: {
|
||||||
|
number: { value: 150, density: { enable: true, width: 1920, height: 1080 } },
|
||||||
|
color: { value: "#ff2e2e" },
|
||||||
|
shape: { type: "circle" },
|
||||||
|
opacity: { value: { min: 0.3, max: 0.8 }, animation: { enable: true, speed: 1 } },
|
||||||
|
size: { value: { min: 2, max: 5 } },
|
||||||
|
links: { enable: true, distance: 150, color: "#ffffff", opacity: 0.3, width: 1 },
|
||||||
|
move: { enable: true, speed: 1, outModes: { default: "bounce" } },
|
||||||
|
},
|
||||||
|
interactivity: {
|
||||||
|
events: {
|
||||||
|
onHover: { enable: true, mode: "grab" },
|
||||||
|
onClick: { enable: true, mode: "push" },
|
||||||
|
},
|
||||||
|
modes: {
|
||||||
|
grab: { distance: 100, links: { opacity: 0.7 } },
|
||||||
|
push: { quantity: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!init) return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
inset: 0,
|
||||||
|
width: "100dvw",
|
||||||
|
height: "100dvh",
|
||||||
|
zIndex: -1,
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Particles id="tsparticles" options={options} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(BackgroundParticles);
|
||||||
50
src/components/TimePassed.tsx
Normal file
50
src/components/TimePassed.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export default function TimePassed({ startDate = "2026-01-08T21:00:00" }) {
|
||||||
|
|
||||||
|
const OUTAGE_START_DATE = new Date(startDate);
|
||||||
|
|
||||||
|
const [formattedString, setFormattedString] = useState('');
|
||||||
|
|
||||||
|
const calculateTimePassed = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const difference = now.getTime() - OUTAGE_START_DATE.getTime();
|
||||||
|
|
||||||
|
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
|
||||||
|
const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||||
|
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
const seconds = Math.floor((difference % (1000 * 60)) / 1000);
|
||||||
|
|
||||||
|
return { days, hours, minutes, seconds };
|
||||||
|
};
|
||||||
|
|
||||||
|
const generatePersianString = (time: Record<string, number>) => {
|
||||||
|
const { days, hours, minutes, seconds } = time;
|
||||||
|
|
||||||
|
const parts = [];
|
||||||
|
|
||||||
|
if (days > 0) parts.push(`${days.toLocaleString('fa-IR')} روز`);
|
||||||
|
if (hours > 0) parts.push(`${hours.toLocaleString('fa-IR')} ساعت`);
|
||||||
|
if (minutes > 0) parts.push(`${minutes.toLocaleString('fa-IR')} دقیقه`);
|
||||||
|
if (seconds > 0) parts.push(`${seconds.toLocaleString('fa-IR')} ثانیه`);
|
||||||
|
|
||||||
|
if (parts.length === 0) return "0 ثانیه";
|
||||||
|
|
||||||
|
return parts.join(" و ") + " " + "از قطعی سراسری اینترنت گذشته است...";
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateTimer = () => {
|
||||||
|
const calculatedTime = calculateTimePassed();
|
||||||
|
setFormattedString(generatePersianString(calculatedTime));
|
||||||
|
};
|
||||||
|
|
||||||
|
updateTimer();
|
||||||
|
|
||||||
|
const intervalId = setInterval(updateTimer, 1000);
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [startDate]);
|
||||||
|
|
||||||
|
return formattedString
|
||||||
|
};
|
||||||
27
src/components/Toast.tsx
Normal file
27
src/components/Toast.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
export default function Toast({ children, onClose }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="group relative w-full px-5 py-2 rounded-lg overflow-hidden transition-all duration-300 hover:scale-105 active:scale-95 cursor-pointer">
|
||||||
|
<div className="absolute inset-0 bg-orange-500/10 backdrop-blur-xl border border-orange-400/30 rounded-lg"></div>
|
||||||
|
<div className="absolute inset-0 bg-linear-to-br from-orange-400/20 via-orange-500/5 to-transparent rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<div className="absolute inset-0 bg-linear-to-r from-transparent via-orange-300/30 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000 rounded-lg"></div>
|
||||||
|
<div className="absolute inset-0 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 blur-xl bg-orange-500/40"></div>
|
||||||
|
<div className="absolute inset-px rounded-lg bg-linear-to-b from-orange-400/10 to-transparent"></div>
|
||||||
|
|
||||||
|
<span dir='rtl' className="relative flex items-center gap-2 text-white font-medium text-sm font-vazirmatn ml-4">
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white opacity-70 hover:opacity-100 transition-opacity duration-200"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
4
src/declarations.d.ts
vendored
Normal file
4
src/declarations.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare module '@fontsource/vazirmatn' {
|
||||||
|
export const style: unknown; // Or more specific types if known, but unknown is generally safe for imports like this
|
||||||
|
export default style;
|
||||||
|
}
|
||||||
42
src/index.css
Normal file
42
src/index.css
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--font-vazirmatn: 'Vazirmatn', sans-serif;
|
||||||
|
--font-vazirmatn-sans: 'Vazirmatn', ui-sans-serif, system-ui, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: scale(0.95); }
|
||||||
|
to { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in {
|
||||||
|
animation: fadeIn 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.custom-scrollbar {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #ff9f1c #00000033;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #ff9f1c;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-clip: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: #ff7f00;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/index.tsx
Normal file
47
src/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { StrictMode, useState } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
|
import FloatingButton from "./components/FloatingButton";
|
||||||
|
import Modal from "./components/Modal";
|
||||||
|
import BackgroundParticles from "./components/Particles";
|
||||||
|
import Bokhary from "./components/Bokhary";
|
||||||
|
import Toast from "./components/Toast";
|
||||||
|
import TimePassed from "./components/TimePassed"
|
||||||
|
|
||||||
|
import "@fontsource/vazirmatn";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [toast, showToast] = useState(true);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StrictMode>
|
||||||
|
|
||||||
|
<BackgroundParticles />
|
||||||
|
|
||||||
|
<div className="relative min-h-dvh">
|
||||||
|
<div className="fixed m-2 top-0 left-0 right-0">
|
||||||
|
{toast && (
|
||||||
|
<Toast onClose={() => showToast(false)}>
|
||||||
|
<TimePassed></TimePassed>
|
||||||
|
</Toast>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="relative z-10">
|
||||||
|
<Bokhary />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-20">
|
||||||
|
<FloatingButton text="اتصال" onClick={() => setModalOpen(true)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Modal isOpen={modalOpen} onClose={() => setModalOpen(false)} />
|
||||||
|
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(<App />);
|
||||||
6
src/vite-env.d.ts
vendored
Normal file
6
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.css' {
|
||||||
|
const classes: { [key: string]: string };
|
||||||
|
export default classes;
|
||||||
|
}
|
||||||
28
tsconfig.app.json
Normal file
28
tsconfig.app.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
26
tsconfig.node.json
Normal file
26
tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
9
vite.config.ts
Normal file
9
vite.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), tailwindcss()],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user