├── .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 |
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 |
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 |
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 |
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 |
20 |
23 |
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 |
--------------------------------------------------------------------------------