├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── astro.config.mjs ├── package.json ├── pnpm-lock.yaml ├── public ├── favicon.svg └── nnnoise.svg ├── src ├── components │ ├── About.tsx │ ├── DevelopedBy.tsx │ ├── Globe.tsx │ ├── HTML.astro │ ├── Picture.tsx │ ├── Projects.tsx │ ├── Socials.tsx │ ├── Stacks.tsx │ ├── icons │ │ ├── index.ts │ │ ├── lucide.tsx │ │ └── simple.tsx │ └── ui │ │ ├── Avatar │ │ ├── Avatar.styles.ts │ │ └── Avatar.tsx │ │ ├── Button │ │ └── Button.tsx │ │ ├── IconButton │ │ └── IconButton.tsx │ │ ├── Separator │ │ └── Separator.tsx │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils.ts ├── env.d.ts ├── layouts │ └── MainLayout.astro ├── pages │ ├── 404.astro │ └── index.astro └── styles │ ├── globals.css │ └── globals.ts ├── tailwind.config.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode", "bradlc.vscode-tailwindcss"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.preferences.importModuleSpecifier": "non-relative", 3 | "javascript.preferences.importModuleSpecifierEnding": "js", 4 | "typescript.preferences.importModuleSpecifier": "non-relative", 5 | "typescript.preferences.importModuleSpecifierEnding": "js", 6 | "tailwindCSS.experimental.classRegex": [["(?:(?:re)?klass(?:ed)?|group)(?:\\.\\w*)?\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]] 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 flamrdevs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # astrovehnt 2 | 3 | Bento Portfolio Template using Astro, React & TailwindCSS 4 | 5 | ## Tech Stack 6 | 7 | - [TypeScript](https://www.typescriptlang.org) 8 | - [React](https://react.dev) 9 | - [Tailwind CSS](https://tailwindcss.com) 10 | - [coloradix](https://github.com/coloradix/coloradix) 11 | - [Radix UI](https://radix-ui.com) 12 | - [klass](https://github.com/flamrdevs/klass) 13 | - [Astro](https://astro.build) 14 | 15 | ## Quick Start 16 | 17 | [Create repository from a template](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template) 18 | 19 | or 20 | 21 | Clone the repository 22 | 23 | ```bash 24 | git clone https://github.com/flamrdevs/astrovehnt.git 25 | ``` 26 | 27 | Install packages 28 | 29 | ``` 30 | pnpm i 31 | ``` 32 | 33 | Start Astro development server 34 | 35 | ``` 36 | pnpm dev 37 | ``` 38 | 39 | ### Customization 40 | 41 | #### Content 42 | 43 | [VSCode Todo Tree Extension](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.todo-tree) will help you find what needs to be changed 44 | 45 | #### Colors 46 | 47 | Color system is based on the [Radix Colors](https://www.radix-ui.com/colors) schema 48 | 49 | `tailwind.config.ts` 50 | 51 | ```typescript 52 | import coloradix /*, { import the colors you want }*/ from "@coloradix/tailwindcss"; 53 | 54 | // then configure it with the coloradix plugin 55 | ``` 56 | 57 | #### Theme 58 | 59 | Set light / Dark mode by the html data attibute 60 | 61 | `src/components/HTML.astro` 62 | 63 | ```html 64 | 65 | 66 | 67 | ``` 68 | 69 | ## Author 70 | 71 | astrovehnt developed by [flamrdevs](https://github.com/flamrdevs) 72 | 73 | ## License 74 | 75 | [MIT](./LICENSE) 76 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | 3 | import tailwind from "@astrojs/tailwind"; 4 | 5 | import react from "@astrojs/react"; 6 | 7 | export default defineConfig({ 8 | integrations: [ 9 | tailwind({ 10 | applyBaseStyles: false, 11 | nesting: true, 12 | }), 13 | react(), 14 | ], 15 | }); 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astrovehnt", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "license": "MIT", 6 | "author": { 7 | "name": "flamrdevs", 8 | "url": "https://github.com/flamrdevs" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/flamrdevs/astrovehnt.git" 13 | }, 14 | "scripts": { 15 | "dev": "astro dev", 16 | "start": "astro dev", 17 | "build": "astro check && astro build", 18 | "preview": "astro preview", 19 | "astro": "astro" 20 | }, 21 | "dependencies": { 22 | "@astrojs/check": "^0.5.10", 23 | "@klass/core": "4.0.0-next.28", 24 | "@klass/react": "4.0.0-next.28", 25 | "@radix-ui/react-avatar": "^1.0.4", 26 | "@radix-ui/react-separator": "^1.0.3", 27 | "@react-spring/web": "^9.7.3", 28 | "astro": "^4.7.0", 29 | "clsx": "^2.1.1", 30 | "cobe": "^0.6.3", 31 | "react": "^18.3.1", 32 | "react-dom": "^18.3.1", 33 | "typescript": "^5.4.5" 34 | }, 35 | "devDependencies": { 36 | "@astrojs/react": "^3.3.1", 37 | "@astrojs/tailwind": "^5.1.0", 38 | "@coloradix/tailwindcss": "^2.3.2", 39 | "@types/react": "^18.3.1", 40 | "@types/react-dom": "^18.3.0", 41 | "tailwindcss": "^3.4.3" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /public/nnnoise.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/About.tsx: -------------------------------------------------------------------------------- 1 | // TODO : update about 2 | 3 | export default () => { 4 | return ( 5 |
6 |
7 |
8 |

flamrdevs

9 |

UI/UX designer

10 |
11 |
12 | 13 |
14 | I am a UI/UX designer from Indonesia, specializing in creating user-centric and visually appealing digital experiences. With a focus 15 | on seamless and enjoyable interactions, I aim to enhance the overall user experience through strategic design solutions. 16 |
17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/DevelopedBy.tsx: -------------------------------------------------------------------------------- 1 | // TODO : update developer 2 | 3 | import { useEffect, useState } from "react"; 4 | 5 | import { useTrail, animated } from "@react-spring/web"; 6 | 7 | const text = "flamrdevs".split(""); 8 | 9 | export default () => { 10 | const [state, setState] = useState(true); 11 | 12 | const [opacity] = useTrail( 13 | text.length, 14 | () => ({ 15 | from: { 16 | opacity: 0, 17 | y: state ? -5 : 5, 18 | padding: state ? 0 : 1, 19 | }, 20 | to: { 21 | opacity: 1, 22 | y: state ? 5 : -5, 23 | padding: state ? 1 : 0, 24 | }, 25 | }), 26 | [state] 27 | ); 28 | 29 | const [color] = useTrail( 30 | text.length, 31 | () => ({ 32 | from: { 33 | color: `rgb(var(--${state ? "neutral" : "primary"}-9))`, 34 | }, 35 | to: { 36 | color: `rgb(var(--${state ? "primary" : "neutral"}-9))`, 37 | }, 38 | }), 39 | [state] 40 | ); 41 | 42 | useEffect(() => { 43 | const interval = setInterval(() => { 44 | setState((v: boolean) => !v); 45 | }, 2000); 46 | 47 | return () => { 48 | clearInterval(interval); 49 | }; 50 | }, []); 51 | 52 | return ( 53 |
54 |
55 | {opacity.map((props, i) => ( 56 | 57 | {text[i]} 58 | 59 | ))} 60 |
61 | 62 |
63 | 64 | Developed by{" "} 65 | 71 | {" "} 72 | flamrdevs 73 | 74 | 75 |
76 |
77 | ); 78 | }; 79 | -------------------------------------------------------------------------------- /src/components/Globe.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | import { useSpring } from "@react-spring/web"; 4 | 5 | import cobe from "cobe"; 6 | 7 | const color = (() => { 8 | const convert = (v: string) => { 9 | const n = Number(v); 10 | if (isNaN(n)) throw new Error("color convert error"); 11 | return n / 255; 12 | }; 13 | 14 | return (t: T) => [convert(t[0]), convert(t[1]), convert(t[2])] as [number, number, number]; 15 | })(); 16 | 17 | const getPropertyValueFrom = (style: CSSStyleDeclaration, varName: string) => 18 | style.getPropertyValue(varName).split(" ") as [string, string, string]; 19 | 20 | const Globe = () => { 21 | const ref = useRef(null); 22 | const pointerInteracting = useRef(null); 23 | const pointerInteractionMovement = useRef(0); 24 | const [{ r }, api] = useSpring(() => ({ r: 0, config: { mass: 1, tension: 280, friction: 40, precision: 0.001 } })); 25 | 26 | useEffect(() => { 27 | let phi = 0; 28 | let width = 0; 29 | const onResize = () => ref.current && (width = ref.current.offsetWidth); 30 | window.addEventListener("resize", onResize); 31 | onResize(); 32 | 33 | const style = getComputedStyle(document.documentElement); 34 | 35 | const globe = cobe(ref.current!, { 36 | devicePixelRatio: 2, 37 | width: width * 2, 38 | height: width * 2, 39 | phi: 0, 40 | theta: 0.3, 41 | dark: 1, 42 | diffuse: 3, 43 | mapSamples: 16000, 44 | mapBrightness: 2, 45 | baseColor: color(getPropertyValueFrom(style, "--neutral-9")), 46 | markerColor: color(getPropertyValueFrom(style, "--primary-9")), 47 | glowColor: color(getPropertyValueFrom(style, "--neutral-11")), 48 | markers: [ 49 | { 50 | // TODO : update location. https://cobe.vercel.app/docs/api#markers 51 | location: [-7.5360639, 112.2384017], 52 | size: 0.1, 53 | }, 54 | ], 55 | onRender: (state) => { 56 | if (!pointerInteracting.current) phi += 0.005; 57 | state.phi = phi + r.get(); 58 | state.width = width * 2; 59 | state.height = width * 2; 60 | }, 61 | }); 62 | setTimeout(() => (ref.current!.style.opacity = "1")); 63 | return () => { 64 | globe.destroy(); 65 | window.removeEventListener("resize", onResize); 66 | }; 67 | }, []); 68 | 69 | return ( 70 |
71 |
72 | { 75 | pointerInteracting.current = e.clientX - pointerInteractionMovement.current; 76 | ref.current!.style.cursor = "grabbing"; 77 | }} 78 | onPointerUp={() => { 79 | pointerInteracting.current = null; 80 | ref.current!.style.cursor = "grab"; 81 | }} 82 | onPointerOut={() => { 83 | pointerInteracting.current = null; 84 | ref.current!.style.cursor = "grab"; 85 | }} 86 | onMouseMove={(e) => { 87 | if (pointerInteracting.current !== null) { 88 | const delta = e.clientX - pointerInteracting.current; 89 | pointerInteractionMovement.current = delta; 90 | api.start({ r: delta / 200 }); 91 | } 92 | }} 93 | onTouchMove={(e) => { 94 | if (pointerInteracting.current !== null && e.touches[0]) { 95 | const delta = e.touches[0].clientX - pointerInteracting.current; 96 | pointerInteractionMovement.current = delta; 97 | api.start({ r: delta / 100 }); 98 | } 99 | }} 100 | className="w-full h-full" 101 | style={{ cursor: "grab", contain: "layout paint size", opacity: 0, transition: "opacity 1s ease" }} 102 | /> 103 |
104 |
105 | ); 106 | }; 107 | 108 | export default Globe; 109 | -------------------------------------------------------------------------------- /src/components/HTML.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types"; 3 | 4 | import "~/styles/globals.ts"; 5 | 6 | type Props = HTMLAttributes<"html"> & { 7 | title?: string; 8 | description?: string; 9 | }; 10 | 11 | const { 12 | // TODO : html default title 13 | title = "astrovehnt", 14 | // TODO : html default description 15 | description = title, 16 | ...html 17 | } = Astro.props; 18 | --- 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {title} 28 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/Picture.tsx: -------------------------------------------------------------------------------- 1 | // TODO : update picture 2 | 3 | import { Avatar } from "./ui"; 4 | import { Lucide } from "./icons"; 5 | 6 | export default () => { 7 | return ( 8 |
9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/Projects.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "./ui/utils.ts"; 2 | import { Lucide } from "./icons"; 3 | 4 | // TODO : update projects 5 | const projects = [ 6 | { 7 | title: "astrolinkt", 8 | description: "Astro template", 9 | icon: Lucide.IconImage, 10 | url: "https://github.com/flamrdevs/astrolinkt", 11 | }, 12 | { 13 | title: "astrovehnt", 14 | description: "Astro template", 15 | icon: Lucide.IconImage, 16 | url: "https://github.com/flamrdevs/astrovehnt", 17 | }, 18 | { 19 | title: "astrobuckt", 20 | description: "Astro template", 21 | icon: Lucide.IconImage, 22 | url: "https://github.com/flamrdevs/astrobuckt", 23 | }, 24 | ]; 25 | 26 | export default () => { 27 | return ( 28 |
29 | 65 |
66 | ); 67 | }; 68 | -------------------------------------------------------------------------------- /src/components/Socials.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "./ui/utils.ts"; 2 | import { Simple } from "./icons"; 3 | 4 | // TODO : update socials 5 | const socials = [ 6 | { 7 | href: "https://github.com", 8 | name: "GitHub", 9 | icon: Simple.IconGitHub, 10 | }, 11 | { 12 | href: "https://x.com", 13 | name: "X", 14 | icon: Simple.IconX, 15 | }, 16 | { 17 | href: "https://www.youtube.com", 18 | name: "Youtube", 19 | icon: Simple.IconYoutube, 20 | }, 21 | ]; 22 | 23 | export default () => { 24 | return ( 25 |
26 |
    27 | {socials.map((social) => { 28 | return ( 29 |
  • 30 | 42 | 43 | 44 |
  • 45 | ); 46 | })} 47 |
48 |
49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/Stacks.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "./ui/utils.ts"; 2 | import { Simple } from "./icons"; 3 | 4 | // TODO : update stacks 5 | const stacks = [ 6 | { 7 | name: "Figma", 8 | category: "Design", 9 | icon: Simple.IconFigma, 10 | url: "https://www.figma.com", 11 | }, 12 | { 13 | name: "Framer", 14 | category: "Design", 15 | icon: Simple.IconFramer, 16 | url: "https://www.framer.com", 17 | }, 18 | { 19 | name: "Rive", 20 | category: "Design", 21 | icon: Simple.IconRive, 22 | url: "https://rive.app", 23 | }, 24 | { 25 | name: "Arc", 26 | category: "Browser", 27 | icon: Simple.IconArc, 28 | url: "https://arc.net", 29 | }, 30 | { 31 | name: "Notion", 32 | category: "Productivity", 33 | icon: Simple.IconNotion, 34 | url: "https://www.notion.so", 35 | }, 36 | { 37 | name: "Calendly", 38 | category: "Calendar", 39 | icon: Simple.IconCalendly, 40 | url: "https://calendly.com", 41 | }, 42 | ]; 43 | 44 | export default () => { 45 | return ( 46 |
47 | 83 |
84 | ); 85 | }; 86 | -------------------------------------------------------------------------------- /src/components/icons/index.ts: -------------------------------------------------------------------------------- 1 | export * as Lucide from "./lucide.tsx"; 2 | export * as Simple from "./simple.tsx"; 3 | -------------------------------------------------------------------------------- /src/components/icons/lucide.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from "react"; 2 | import type { ReactNode, SVGProps } from "react"; 3 | 4 | type BaseProps = Omit, "role" | "viewBox" | "xmlns"> & { size?: number }; 5 | 6 | const Base = forwardRef(({ d, size = 16, width = size, height = size, ...props }, ref) => ( 7 | 21 | )); 22 | 23 | type IconProps = Omit; 24 | 25 | const create = (children: ReactNode) => 26 | forwardRef((props, ref) => ( 27 | 28 | {children} 29 | 30 | )); 31 | 32 | /** 33 | * How to add icons ? 34 | * 35 | * - Go to https://lucide.dev/icons 36 | * 37 | * - Copy the icon as SVG, example: 38 | * 39 | * 40 | * 41 | * 42 | * 43 | * - create component with `create` function then copy paste path elements, example: 44 | * export const IconX = create( 45 | * <> 46 | * 47 | * 48 | * 49 | * ); 50 | */ 51 | 52 | // TODO : update icons 53 | 54 | export const IconArrowRight = create( 55 | <> 56 | 57 | 58 | 59 | ); 60 | 61 | export const IconExternalLink = create( 62 | <> 63 | 64 | 65 | 66 | 67 | ); 68 | 69 | export const IconImage = create( 70 | <> 71 | 72 | 73 | 74 | 75 | ); 76 | -------------------------------------------------------------------------------- /src/components/icons/simple.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | type BaseProps = Omit, "role" | "viewBox" | "xmlns" | "children"> & { d: string; size?: number }; 5 | 6 | const Base = forwardRef(({ d, size = 16, width = size, height = size, ...props }, ref) => ( 7 | 17 | 18 | 19 | )); 20 | 21 | type IconProps = Omit; 22 | 23 | const create = (d: string) => forwardRef((props, ref) => ); 24 | 25 | /** 26 | * How to add icons ? 27 | * 28 | * - Go to https://simpleicons.org 29 | * 30 | * - Copy the icon as SVG, example: 31 | * 32 | * GitHub 33 | * 34 | * 35 | * 36 | * - create component with `create` function then copy paste `d` path property value, example: 37 | * export const IconGitHub = create("M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"); 38 | */ 39 | 40 | // TODO : update icons 41 | 42 | export const IconArc = create( 43 | "M23.9371 8.5089c.1471-.7147.0367-1.4661-.3364-2.0967-.4203-.7094-1.1035-1.1876-1.9075-1.3506a2.9178 2.9178 0 0 0-.5623-.0578h-.0105c-1.3768 0-2.5329.988-2.8061 2.3385-.1629.7935-.4782 1.5607-.9196 2.2701a.263.263 0 0 1-.2363.1205.2627.2627 0 0 1-.2209-.1468l-2.8587-5.9906c-.3626-.762-1.0142-1.361-1.8235-1.5975-1.3873-.4099-2.8166.2838-3.4052 1.524L5.897 9.7333c-.0788.1629-.31.1576-.3784-.0053v-.0052a2.8597 2.8597 0 0 0-2.6642-1.7972c-.3784 0-.7515.0736-1.1088.2207-1.4714.6148-2.1283 2.349-1.5187 3.8203.557 1.3295 1.4714 2.5855 2.659 3.668.084.0788.1103.1997.063.3048l-.9563 2.0074c-.6727 1.4188-.1314 3.1477 1.2664 3.8571.4099.2049.846.31 1.298.31 1.1035 0 2.123-.6411 2.5959-1.6395l.825-1.7289a.254.254 0 0 1 .3048-.1366c1.0037.2732 2.0127.4204 3.0058.4204 1.1193 0 2.2229-.1682 3.2896-.4782a.2626.2626 0 0 1 .3101.1366l.8145 1.7131c.4834 1.0195 1.4924 1.7131 2.6169 1.7184.4572 0 .8986-.0999 1.3138-.3101 1.403-.7094 1.939-2.4435 1.2664-3.8676L19.875 15.787c-.0473-.1051-.0263-.226.0578-.3048 1.9864-1.8497 3.4525-4.2723 4.0043-6.9733ZM6.2121 20.0172a1.835 1.835 0 0 1-.6764.7622 1.8352 1.8352 0 0 1-.9788.2835c-.2733 0-.5518-.063-.8093-.1891-.9038-.4467-1.2454-1.5713-.8093-2.4804l.7935-1.6658c.0684-.1471.2575-.1997.3837-.1051.1681.1209.3415.2365.5202.3521.6989.4467 1.4293.825 2.1808 1.1351.1419.0578.205.2154.1419.352l-.7462 1.5555Zm5.0763-2.0442c-4.2092 0-8.6548-2.8534-10.1262-6.4951a1.8286 1.8286 0 0 1 1.009-2.3805c.2259-.0893.4571-.1366.683-.1366.7252 0 1.4084.431 1.6974 1.1456.9196 2.2806 4.0043 4.2092 6.7368 4.2092.4204 0 .8408-.042 1.256-.1156a.2643.2643 0 0 1 .2837.1419l1.3768 2.9007c.0683.1471-.0105.3205-.1629.3626-.8986.2365-1.8182.3678-2.7536.3678Zm-.599-4.9291.6358-1.3348c.0526-.1051.205-.1051.2575 0l.6201 1.3033c.042.0841-.0158.1891-.1051.2049-.268.0368-.536.0578-.7988.0578a5.0634 5.0634 0 0 1-.4887-.0263c-.1103-.0157-.1629-.1208-.1208-.2049Zm8.4604 7.8246a1.831 1.831 0 0 1-2.0329-.2788 1.8292 1.8292 0 0 1-.4316-.5778l-4.987-10.4836c-.0998-.2102-.3994-.2102-.4939 0l-1.545 3.2529a.2623.2623 0 0 1-.3205.1366c-1.051-.3626-2.0495-.9774-2.7904-1.7184a.2552.2552 0 0 1-.0473-.2943l3.3421-7.031c.1156-.247.2943-.4677.5203-.6201 1.051-.6884 2.2806-.2575 2.7378.7041l6.8577 14.4248c.4309.9144.0946 2.0389-.8093 2.4856Zm-1.4451-9.6481a.258.258 0 0 1 .0315-.2732c.783-1.0037 1.3558-2.1756 1.6028-3.421.1734-.867.9354-1.4714 1.7919-1.4714.1472 0 .2943.0158.4467.0526.9722.2417 1.5344 1.2507 1.3295 2.2333-.4835 2.3017-1.6816 4.3879-3.3159 6.0222-.1313.1314-.3468.0946-.4256-.0683l-1.4609-3.0742Z" 44 | ); 45 | 46 | export const IconDribbble = create( 47 | "M12 24C5.385 24 0 18.615 0 12S5.385 0 12 0s12 5.385 12 12-5.385 12-12 12zm10.12-10.358c-.35-.11-3.17-.953-6.384-.438 1.34 3.684 1.887 6.684 1.992 7.308 2.3-1.555 3.936-4.02 4.395-6.87zm-6.115 7.808c-.153-.9-.75-4.032-2.19-7.77l-.066.02c-5.79 2.015-7.86 6.025-8.04 6.4 1.73 1.358 3.92 2.166 6.29 2.166 1.42 0 2.77-.29 4-.814zm-11.62-2.58c.232-.4 3.045-5.055 8.332-6.765.135-.045.27-.084.405-.12-.26-.585-.54-1.167-.832-1.74C7.17 11.775 2.206 11.71 1.756 11.7l-.004.312c0 2.633.998 5.037 2.634 6.855zm-2.42-8.955c.46.008 4.683.026 9.477-1.248-1.698-3.018-3.53-5.558-3.8-5.928-2.868 1.35-5.01 3.99-5.676 7.17zM9.6 2.052c.282.38 2.145 2.914 3.822 6 3.645-1.365 5.19-3.44 5.373-3.702-1.81-1.61-4.19-2.586-6.795-2.586-.825 0-1.63.1-2.4.285zm10.335 3.483c-.218.29-1.935 2.493-5.724 4.04.24.49.47.985.68 1.486.08.18.15.36.22.53 3.41-.43 6.8.26 7.14.33-.02-2.42-.88-4.64-2.31-6.38z" 48 | ); 49 | 50 | export const IconCalendly = create( 51 | "M19.655 14.262c.281 0 .557.023.828.064 0 .005-.005.01-.005.014-.105.267-.234.534-.381.786l-1.219 2.106c-1.112 1.936-3.177 3.127-5.411 3.127h-2.432c-2.23 0-4.294-1.191-5.412-3.127l-1.218-2.106a6.251 6.251 0 0 1 0-6.252l1.218-2.106C6.736 4.832 8.8 3.641 11.035 3.641h2.432c2.23 0 4.294 1.191 5.411 3.127l1.219 2.106c.147.252.271.519.381.786 0 .004.005.009.005.014-.267.041-.543.064-.828.064-1.816 0-2.501-.607-3.291-1.306-.764-.676-1.711-1.517-3.44-1.517h-1.029c-1.251 0-2.387.455-3.2 1.278-.796.805-1.233 1.904-1.233 3.099v1.411c0 1.196.437 2.295 1.233 3.099.813.823 1.949 1.278 3.2 1.278h1.034c1.729 0 2.676-.841 3.439-1.517.791-.703 1.471-1.306 3.287-1.301Zm.005-3.237c.399 0 .794-.036 1.179-.11-.002-.004-.002-.01-.002-.014-.073-.414-.193-.823-.349-1.218.731-.12 1.407-.396 1.986-.819 0-.004-.005-.013-.005-.018-.331-1.085-.832-2.101-1.489-3.03-.649-.915-1.435-1.719-2.331-2.395-1.867-1.398-4.088-2.138-6.428-2.138-1.448 0-2.855.28-4.175.841-1.273.543-2.423 1.315-3.407 2.299S2.878 6.552 2.341 7.83c-.557 1.324-.842 2.726-.842 4.175 0 1.448.281 2.855.842 4.174.542 1.274 1.314 2.423 2.298 3.407s2.129 1.761 3.407 2.299c1.324.556 2.727.841 4.175.841 2.34 0 4.561-.74 6.428-2.137a10.815 10.815 0 0 0 2.331-2.396c.652-.929 1.158-1.949 1.489-3.03 0-.004.005-.014.005-.018-.579-.423-1.255-.699-1.986-.819.161-.395.276-.804.349-1.218.005-.009.005-.014.005-.023.869.166 1.692.506 2.404 1.035.685.505.552 1.075.446 1.416C22.184 20.437 17.619 24 12.221 24c-6.625 0-12-5.375-12-12s5.37-12 12-12c5.398 0 9.963 3.563 11.471 8.464.106.341.239.915-.446 1.421-.717.529-1.535.873-2.404 1.034.128.716.128 1.45 0 2.166-.387-.074-.782-.11-1.182-.11-4.184 0-3.968 2.823-6.736 2.823h-1.029c-1.899 0-3.15-1.357-3.15-3.095v-1.411c0-1.738 1.251-3.094 3.15-3.094h1.034c2.768 0 2.552 2.823 6.731 2.827Z" 52 | ); 53 | 54 | export const IconFigma = create( 55 | "M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491zM12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117V7.51zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471H8.148zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981H8.148zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441c0 2.503-2.047 4.539-4.563 4.539zm-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019 1.705 0 3.093-1.376 3.093-3.068v-2.97H8.148zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49zm-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-.098z" 56 | ); 57 | 58 | export const IconFramer = create("M4 0h16v8h-8zM4 8h8l8 8H4zM4 16h8v8z"); 59 | 60 | export const IconGitHub = create( 61 | "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" 62 | ); 63 | 64 | export const IconNotion = create( 65 | "M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632z" 66 | ); 67 | 68 | export const IconRive = create( 69 | "M.643 1.475c0 .814.668 1.475 1.49 1.475H14.49c1.408 0 2.568.43 3.48 1.29.91.861 1.366 1.967 1.366 3.32 0 1.25-.456 2.274-1.367 3.072-.911.78-2.07 1.168-3.479 1.168H9.12c-.824 0-1.491.66-1.491 1.475 0 .815.667 1.475 1.491 1.475h5.93l5.342 8.482c.332.512.797.768 1.398.768.663 0 1.129-.256 1.398-.768.269-.533.217-1.096-.155-1.69l-4.753-7.56c1.284-.574 2.299-1.414 3.044-2.52.746-1.127 1.119-2.427 1.119-3.902 0-1.496-.342-2.807-1.026-3.934-.662-1.127-1.594-2.008-2.795-2.643C17.42.327 16.044 0 14.49 0H2.134C1.311 0 .643.66.643 1.475Z" 70 | ); 71 | 72 | export const IconX = create( 73 | "M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" 74 | ); 75 | 76 | export const IconYoutube = create( 77 | "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" 78 | ); 79 | -------------------------------------------------------------------------------- /src/components/ui/Avatar/Avatar.styles.ts: -------------------------------------------------------------------------------- 1 | import { group } from "../utils.ts"; 2 | 3 | export default group({ 4 | base: { 5 | root: "inline-flex items-center justify-center align-middle overflow-hidden select-none rounded-full", 6 | fallback: "flex items-center justify-center size-full bg-neutral-2 text-neutral-11", 7 | image: "size-full object-cover rounded-[inherit]", 8 | }, 9 | variants: { 10 | size: { 11 | md: { 12 | root: "w-20 h-20", 13 | fallback: "text-base", 14 | }, 15 | unset: {}, 16 | }, 17 | }, 18 | defaults: { 19 | size: "md", 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/ui/Avatar/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, useMemo } from "react"; 2 | 3 | import * as RadixAvatar from "@radix-ui/react-avatar"; 4 | 5 | import type { VariantsOfGroup } from "@klass/core/group"; 6 | 7 | import type { ClassNamesProps } from "./../types.ts"; 8 | 9 | import styles from "./Avatar.styles.ts"; 10 | 11 | export type AvatarProps = Omit & 12 | ClassNamesProps & 13 | VariantsOfGroup; 14 | 15 | export const Avatar = forwardRef(({ size, className, classNames, children, ...props }, ref) => { 16 | const cx = useMemo(() => { 17 | const variants = { size }; 18 | return { 19 | root: styles.root(variants, classNames?.root), 20 | fallback: styles.fallback(variants, classNames?.fallback), 21 | image: styles.image(variants, className ?? classNames?.image), 22 | }; 23 | }, [size, className, classNames]); 24 | 25 | return ( 26 | 27 | {children} 28 | 29 | 30 | ); 31 | }); 32 | 33 | if (import.meta.env.DEV) Avatar.displayName = "Avatar"; 34 | -------------------------------------------------------------------------------- /src/components/ui/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import { klassed } from "./../utils.ts"; 2 | 3 | export const Button = klassed( 4 | "button", 5 | { 6 | base: [ 7 | "inline-flex items-center justify-center gap-2 outline-none", 8 | "border", 9 | "focus-visible:ring-2 focus-visible:ring-offset-2", 10 | "disabled:opacity-90 disabled:pointer-events-none", 11 | ], 12 | variants: { 13 | color: { 14 | neutral: [ 15 | "bg-neutral-3 hover:bg-neutral-4 active:bg-neutral-5 text-neutral-11", 16 | "border-neutral-6", 17 | "focus-visible:ring-neutral-8 focus-visible:ring-offset-neutral-1", 18 | "disabled:bg-neutral-4 disabled:text-neutral-8 disabled:border-neutral-5", 19 | ], 20 | primary: [ 21 | "bg-primary-3 hover:bg-primary-4 active:bg-primary-5 text-primary-11", 22 | "border-primary-6", 23 | "focus-visible:ring-primary-8 focus-visible:ring-offset-primary-1", 24 | "disabled:bg-primary-4 disabled:text-primary-8 disabled:border-primary-5", 25 | ], 26 | }, 27 | size: { 28 | sm: "px-3 h-7 text-sm font-normal rounded-md", 29 | md: "px-4 h-9 text-base font-medium rounded-lg", 30 | lg: "px-5 h-11 text-lg font-medium rounded-xl", 31 | }, 32 | }, 33 | defaults: { 34 | color: "neutral", 35 | size: "md", 36 | }, 37 | }, 38 | { 39 | dp: { 40 | type: "button", 41 | }, 42 | } 43 | ); 44 | 45 | if (import.meta.env.DEV) Button.displayName = "Button"; 46 | -------------------------------------------------------------------------------- /src/components/ui/IconButton/IconButton.tsx: -------------------------------------------------------------------------------- 1 | import { klassed } from "./../utils.ts"; 2 | 3 | export const IconButton = klassed( 4 | "button", 5 | { 6 | base: [ 7 | "inline-flex items-center justify-center gap-2 outline-none", 8 | "border", 9 | "focus-visible:ring-2 focus-visible:ring-offset-2", 10 | "disabled:opacity-90 disabled:pointer-events-none", 11 | ], 12 | variants: { 13 | color: { 14 | neutral: [ 15 | "bg-neutral-3 hover:bg-neutral-4 active:bg-neutral-5 text-neutral-11", 16 | "border-neutral-6", 17 | "focus-visible:ring-neutral-8 focus-visible:ring-offset-neutral-1", 18 | "disabled:bg-neutral-4 disabled:text-neutral-8 disabled:border-neutral-5", 19 | ], 20 | primary: [ 21 | "bg-primary-3 hover:bg-primary-4 active:bg-primary-5 text-primary-11", 22 | "border-primary-6", 23 | "focus-visible:ring-primary-8 focus-visible:ring-offset-primary-1", 24 | "disabled:bg-primary-4 disabled:text-primary-8 disabled:border-primary-5", 25 | ], 26 | }, 27 | size: { 28 | sm: "w-7 h-7 text-sm font-normal rounded-md", 29 | md: "w-9 h-9 text-base font-medium rounded-lg", 30 | lg: "w-11 h-11 text-lg font-medium rounded-xl", 31 | }, 32 | }, 33 | defaults: { 34 | color: "neutral", 35 | size: "md", 36 | }, 37 | }, 38 | { 39 | dp: { 40 | type: "button", 41 | }, 42 | } 43 | ); 44 | 45 | if (import.meta.env.DEV) IconButton.displayName = "IconButton"; 46 | -------------------------------------------------------------------------------- /src/components/ui/Separator/Separator.tsx: -------------------------------------------------------------------------------- 1 | import * as RadixSeparator from "@radix-ui/react-separator"; 2 | 3 | import { mklassed } from "./../utils.ts"; 4 | 5 | export const Separator = mklassed( 6 | RadixSeparator.Root, 7 | { 8 | variants: { 9 | color: { 10 | neutral: "bg-neutral-6", 11 | primary: "bg-primary-6", 12 | }, 13 | orientation: { 14 | horizontal: "w-full h-px", 15 | vertical: "w-px h-full", 16 | }, 17 | }, 18 | defaults: { 19 | color: "neutral", 20 | orientation: "horizontal", 21 | }, 22 | }, 23 | { 24 | fp: ["orientation"], 25 | } 26 | ); 27 | 28 | if (import.meta.env.DEV) Separator.displayName = "Separator"; 29 | -------------------------------------------------------------------------------- /src/components/ui/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Avatar/Avatar.tsx"; 2 | export * from "./Button/Button.tsx"; 3 | export * from "./IconButton/IconButton.tsx"; 4 | export * from "./Separator/Separator.tsx"; 5 | -------------------------------------------------------------------------------- /src/components/ui/types.ts: -------------------------------------------------------------------------------- 1 | export type ClassNamesProps = { classNames?: { [K in T]?: string } }; 2 | -------------------------------------------------------------------------------- /src/components/ui/utils.ts: -------------------------------------------------------------------------------- 1 | import { createKlass, createReklass } from "@klass/core/create"; 2 | import { createGroup } from "@klass/core/group/create"; 3 | import * as poly from "@klass/react/create"; 4 | import * as mono from "@klass/react/mono/create"; 5 | 6 | // import { twMerge as end } from "tailwind-merge"; // optional package, install first 7 | 8 | import clsx from "clsx"; 9 | import type { ClassValue } from "clsx"; 10 | 11 | export const cn = (...classValues: ClassValue[]) => clsx(classValues); 12 | // export const cn = (...classValues: ClassValue[]) => end(clsx(classValues)); // with tailwind-merge 13 | 14 | export const klass = createKlass(); 15 | export const reklass = createReklass(); 16 | // export const klass = createKlass({ end }); // with tailwind-merge 17 | // export const reklass = createReklass({ end }); // with tailwind-merge 18 | export const klassed = poly.createKlassed(klass); 19 | export const reklassed = poly.createReklassed(reklass); 20 | export const group = createGroup(klass); 21 | export const mklassed = mono.createKlassed(klass); 22 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /src/layouts/MainLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import HTML from "~/components/HTML.astro"; 3 | 4 | type Props = { 5 | title?: string; 6 | description?: string; 7 | }; 8 | 9 | const { title, description } = Astro.props; 10 | --- 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/pages/404.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import MainLayout from "~/layouts/MainLayout.astro"; 3 | 4 | import { Separator } from "~/components/ui"; 5 | --- 6 | 7 | 8 |
9 |

10 | 404 11 | 12 | Page not found 13 |

14 |
15 |
16 | -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import MainLayout from "~/layouts/MainLayout.astro"; 3 | 4 | import Picture from "~/components/Picture.tsx"; 5 | import About from "~/components/About.tsx"; 6 | import Projects from "~/components/Projects.tsx"; 7 | import DevelopedBy from "~/components/DevelopedBy.tsx"; 8 | import Globe from "~/components/Globe.tsx"; 9 | import Stacks from "~/components/Stacks.tsx"; 10 | import Socials from "~/components/Socials.tsx"; 11 | --- 12 | 13 | 14 |
17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 |
35 |
36 | 37 |
38 |
39 |
40 | 41 | 71 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html { 6 | -webkit-tap-highlight-color: transparent; 7 | } 8 | 9 | body { 10 | @apply bg-neutral-1 font-sans text-neutral-12; 11 | } 12 | 13 | /* color variable generated by the coloradix plugin */ 14 | 15 | ::-webkit-scrollbar { 16 | width: 0.4rem; 17 | } 18 | 19 | ::-webkit-scrollbar-track { 20 | background: rgb(var(--neutral-2) / 0.5); 21 | } 22 | 23 | ::-webkit-scrollbar-thumb { 24 | background: rgb(var(--neutral-5)); 25 | } 26 | 27 | ::-webkit-scrollbar-thumb:hover { 28 | background: rgb(var(--neutral-8)); 29 | } 30 | 31 | ::-moz-selection { 32 | background: rgb(var(--primary-8)); 33 | color: white; 34 | } 35 | 36 | ::selection { 37 | background: rgb(var(--primary-8)); 38 | color: white; 39 | } 40 | -------------------------------------------------------------------------------- /src/styles/globals.ts: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | import defaultTheme from "tailwindcss/defaultTheme"; 3 | 4 | // TODO : colors 5 | import coloradix, { mauve, lime } from "@coloradix/tailwindcss"; 6 | 7 | const radix = coloradix({ 8 | mauve, 9 | lime, 10 | }) 11 | .alias({ 12 | neutral: "mauve", 13 | primary: "lime", 14 | }) 15 | .build(); 16 | 17 | export default { 18 | content: ["./src/**/*.{ts,tsx,astro}"], 19 | theme: { 20 | colors: { 21 | transparent: "transparent", 22 | current: "currentColor", 23 | ...radix.colors, 24 | }, 25 | extend: { 26 | fontFamily: { 27 | // TODO : fonts 28 | sans: ["Inter"].concat(defaultTheme.fontFamily.sans), 29 | mono: ["Roboto Mono"].concat(defaultTheme.fontFamily.mono), 30 | }, 31 | }, 32 | }, 33 | plugins: [radix.plugin], 34 | } satisfies Config; 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "paths": { 6 | "~/*": ["./src/*"] 7 | }, 8 | 9 | "jsx": "react-jsx", 10 | "jsxImportSource": "react", 11 | 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true 14 | }, 15 | "include": ["src"] 16 | } 17 | --------------------------------------------------------------------------------