commit 648021c1c0031b092ee8cb4a9ecc709cd84902e8 Author: hknsh Date: Sun Nov 10 17:25:53 2024 +0000 feat: first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5467b13 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 hknsh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f226b5 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# `hksh's house` + +Created using Preact + Vite + +## Getting Started + +- `npm run dev` - Starts a dev server at http://localhost:5173/ + +- `npm run build` - Builds for production, emitting to `dist/`. Prerenders all found routes in app to static HTML + +- `npm run preview` - Starts a server at http://localhost:4173/ to test production build locally diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..639289e --- /dev/null +++ b/biome.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": ["./src/components/ui/*.tsx"] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..135208c Binary files /dev/null and b/bun.lockb differ diff --git a/components.json b/components.json new file mode 100644 index 0000000..aa64e81 --- /dev/null +++ b/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/style.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..c7a1f78 --- /dev/null +++ b/index.html @@ -0,0 +1,26 @@ + + + + + + + + hknsh's house + + + + + + + + + + + + + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..c73b710 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-scroll-area": "^1.2.0", + "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.1", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "embla-carousel-react": "^8.3.1", + "i18next": "^23.16.5", + "lucide-react": "^0.454.0", + "preact": "^10.22.1", + "preact-iso": "^2.8.1", + "preact-render-to-string": "^6.5.11", + "react-i18next": "^15.1.1", + "tailwind-merge": "^2.5.4", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@preact/preset-vite": "^2.9.0", + "@types/node": "^22.8.6", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", + "typescript": "^5.6.3", + "vite": "^5.3.3" + }, + "eslintConfig": { + "extends": "preact" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..7b75c83 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100755 index 0000000..3dbe9b6 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..5af1fb9 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,19 @@ +User-agent: * +Allow: / + +User-agent: Teoma +Disallow:/ +User-agent: Gigabot +Disallow:/ +User-agent: Robozilla +Disallow:/ +User-agent: Nutch +Disallow:/ +User-agent: baiduspider +Disallow:/ +User-agent: naverbot +Disallow:/ +User-agent: yeti +Disallow:/ +User-agent: psbot +Disallow:/ \ No newline at end of file diff --git a/src/assets/frameworks/astro.svg b/src/assets/frameworks/astro.svg new file mode 100644 index 0000000..a3724ee --- /dev/null +++ b/src/assets/frameworks/astro.svg @@ -0,0 +1,6 @@ + + + diff --git a/src/assets/frameworks/express.svg b/src/assets/frameworks/express.svg new file mode 100644 index 0000000..76ebd33 --- /dev/null +++ b/src/assets/frameworks/express.svg @@ -0,0 +1,6 @@ + + + diff --git a/src/assets/frameworks/fastify.svg b/src/assets/frameworks/fastify.svg new file mode 100644 index 0000000..3a4aed8 --- /dev/null +++ b/src/assets/frameworks/fastify.svg @@ -0,0 +1,10 @@ + + + + diff --git a/src/assets/frameworks/jest.svg b/src/assets/frameworks/jest.svg new file mode 100644 index 0000000..e0347b1 --- /dev/null +++ b/src/assets/frameworks/jest.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/assets/frameworks/nest.svg b/src/assets/frameworks/nest.svg new file mode 100644 index 0000000..ef28921 --- /dev/null +++ b/src/assets/frameworks/nest.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/assets/frameworks/react.svg b/src/assets/frameworks/react.svg new file mode 100644 index 0000000..61a4bb0 --- /dev/null +++ b/src/assets/frameworks/react.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/frameworks/tailwind.svg b/src/assets/frameworks/tailwind.svg new file mode 100644 index 0000000..b340a38 --- /dev/null +++ b/src/assets/frameworks/tailwind.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/assets/knedita-light.svg b/src/assets/knedita-light.svg new file mode 100644 index 0000000..edb60ba --- /dev/null +++ b/src/assets/knedita-light.svg @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/src/assets/languages/js.svg b/src/assets/languages/js.svg new file mode 100644 index 0000000..84652d9 --- /dev/null +++ b/src/assets/languages/js.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/src/assets/languages/rust.svg b/src/assets/languages/rust.svg new file mode 100644 index 0000000..a7408fc --- /dev/null +++ b/src/assets/languages/rust.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/assets/languages/ts.svg b/src/assets/languages/ts.svg new file mode 100644 index 0000000..d44639d --- /dev/null +++ b/src/assets/languages/ts.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/src/assets/tech/actions.svg b/src/assets/tech/actions.svg new file mode 100644 index 0000000..f14f3a1 --- /dev/null +++ b/src/assets/tech/actions.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/assets/tech/docker.svg b/src/assets/tech/docker.svg new file mode 100644 index 0000000..435c54d --- /dev/null +++ b/src/assets/tech/docker.svg @@ -0,0 +1,6 @@ + + + diff --git a/src/assets/tech/git.svg b/src/assets/tech/git.svg new file mode 100644 index 0000000..310412b --- /dev/null +++ b/src/assets/tech/git.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/assets/tech/github.svg b/src/assets/tech/github.svg new file mode 100644 index 0000000..5aa8261 --- /dev/null +++ b/src/assets/tech/github.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/src/assets/tech/linux.svg b/src/assets/tech/linux.svg new file mode 100644 index 0000000..19999dc --- /dev/null +++ b/src/assets/tech/linux.svg @@ -0,0 +1,8 @@ + + + diff --git a/src/assets/tech/mongo.svg b/src/assets/tech/mongo.svg new file mode 100644 index 0000000..091350c --- /dev/null +++ b/src/assets/tech/mongo.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/assets/tech/postgres.svg b/src/assets/tech/postgres.svg new file mode 100644 index 0000000..4e5a3a1 --- /dev/null +++ b/src/assets/tech/postgres.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/src/assets/tech/prisma.svg b/src/assets/tech/prisma.svg new file mode 100644 index 0000000..b20adab --- /dev/null +++ b/src/assets/tech/prisma.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/assets/tech/swagger.svg b/src/assets/tech/swagger.svg new file mode 100644 index 0000000..806b4bb --- /dev/null +++ b/src/assets/tech/swagger.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..843fe0a --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,42 @@ +import { useTranslation } from "react-i18next"; + +export function Header() { + const { t } = useTranslation(); + const scroll = (id: string) => { + const section = document.getElementById(id); + if (section) { + section.scrollIntoView({ behavior: "smooth" }); + } + }; + + return ( +
+
+
+ +
+
+
+ ); +} diff --git a/src/components/IconList.tsx b/src/components/IconList.tsx new file mode 100644 index 0000000..821a941 --- /dev/null +++ b/src/components/IconList.tsx @@ -0,0 +1,19 @@ +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; + +export function IconList({ icons }) { + return ( +
+ +
+ {Object.entries(icons).map(([key, IconComponent]) => ( +
+ {key} + {key} +
+ ))} +
+ +
+
+ ); +} diff --git a/src/components/Icons.ts b/src/components/Icons.ts new file mode 100644 index 0000000..f77eb16 --- /dev/null +++ b/src/components/Icons.ts @@ -0,0 +1,27 @@ +import Astro from "../assets/frameworks/astro.svg"; +import Express from "../assets/frameworks/express.svg"; +import Fastify from "../assets/frameworks/fastify.svg"; +import Jest from "../assets/frameworks/jest.svg"; +import Nest from "../assets/frameworks/nest.svg"; +import Javascript from "../assets/languages/js.svg"; +import Typescript from "../assets/languages/ts.svg"; +import Rust from "../assets/languages/rust.svg"; +import Docker from "../assets/tech/docker.svg"; +import Git from "../assets/tech/git.svg"; +import Linux from "../assets/tech/linux.svg"; +import Prisma from "../assets/tech/prisma.svg"; +import Swagger from "../assets/tech/swagger.svg"; +import Actions from "../assets/tech/actions.svg"; +import MongoDB from "../assets/tech/mongo.svg"; +import Postgres from "../assets/tech/postgres.svg"; +import Tailwind from "../assets/frameworks/tailwind.svg"; +import React from "../assets/frameworks/react.svg"; + +export const Icons = { + languages: { Javascript, Typescript, Rust }, + backend_frameworks: { Nest, Express, Fastify }, + frontend_frameworks: { Astro, React, Tailwind }, + devops: { Docker, Linux, Git, Actions }, + docs_tests: { Swagger, Jest }, + db_orm: { Prisma, Postgres, MongoDB }, +}; diff --git a/src/components/TechnologiesTab.tsx b/src/components/TechnologiesTab.tsx new file mode 100644 index 0000000..96e8de6 --- /dev/null +++ b/src/components/TechnologiesTab.tsx @@ -0,0 +1,37 @@ +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Icons } from "@/components/Icons"; +import { IconList } from "@/components/IconList"; + +export function TechnologiesTab() { + return ( + + + Languages + Back-end + Front-end + DevOps + Docs & Tests + Databases & ORM + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..d18eb21 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import { Slot } from "@radix-ui/react-slot"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..78cd0c3 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,83 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx new file mode 100644 index 0000000..5d18aff --- /dev/null +++ b/src/components/ui/carousel.tsx @@ -0,0 +1,260 @@ +import { ArrowLeftIcon, ArrowRightIcon } from "@radix-ui/react-icons"; +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from "embla-carousel-react"; +import * as React from "react"; + +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +type CarouselApi = UseEmblaCarouselType[1]; +type UseCarouselParameters = Parameters; +type CarouselOptions = UseCarouselParameters[0]; +type CarouselPlugin = UseCarouselParameters[1]; + +type CarouselProps = { + opts?: CarouselOptions; + plugins?: CarouselPlugin; + orientation?: "horizontal" | "vertical"; + setApi?: (api: CarouselApi) => void; +}; + +type CarouselContextProps = { + carouselRef: ReturnType[0]; + api: ReturnType[1]; + scrollPrev: () => void; + scrollNext: () => void; + canScrollPrev: boolean; + canScrollNext: boolean; +} & CarouselProps; + +const CarouselContext = React.createContext(null); + +function useCarousel() { + const context = React.useContext(CarouselContext); + + if (!context) { + throw new Error("useCarousel must be used within a "); + } + + return context; +} + +const Carousel = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & CarouselProps +>( + ( + { + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props + }, + ref, + ) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins, + ); + const [canScrollPrev, setCanScrollPrev] = React.useState(false); + const [canScrollNext, setCanScrollNext] = React.useState(false); + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return; + } + + setCanScrollPrev(api.canScrollPrev()); + setCanScrollNext(api.canScrollNext()); + }, []); + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev(); + }, [api]); + + const scrollNext = React.useCallback(() => { + api?.scrollNext(); + }, [api]); + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault(); + scrollPrev(); + } else if (event.key === "ArrowRight") { + event.preventDefault(); + scrollNext(); + } + }, + [scrollPrev, scrollNext], + ); + + React.useEffect(() => { + if (!api || !setApi) { + return; + } + + setApi(api); + }, [api, setApi]); + + React.useEffect(() => { + if (!api) { + return; + } + + onSelect(api); + api.on("reInit", onSelect); + api.on("select", onSelect); + + return () => { + api?.off("select", onSelect); + }; + }, [api, onSelect]); + + return ( + +
+ {children} +
+
+ ); + }, +); +Carousel.displayName = "Carousel"; + +const CarouselContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel(); + + return ( +
+
+
+ ); +}); +CarouselContent.displayName = "CarouselContent"; + +const CarouselItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { orientation } = useCarousel(); + + return ( +
+ ); +}); +CarouselItem.displayName = "CarouselItem"; + +const CarouselPrevious = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel(); + + return ( + + ); +}); +CarouselPrevious.displayName = "CarouselPrevious"; + +const CarouselNext = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel(); + + return ( + + ); +}); +CarouselNext.displayName = "CarouselNext"; + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +}; diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx new file mode 100644 index 0000000..2f16a5f --- /dev/null +++ b/src/components/ui/scroll-area.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/utils" + +const ScrollArea = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)) +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName + +const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = "vertical", ...props }, ref) => ( + + + +)) +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName + +export { ScrollArea, ScrollBar } diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx new file mode 100644 index 0000000..6d7f122 --- /dev/null +++ b/src/components/ui/separator.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 0000000..85d83be --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..20d36d2 --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,14 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import enJSON from "./locale/en.json"; +import jaJSON from "./locale/ja.json"; +import ptJSON from "./locale/pt.json"; + +i18n.use(initReactI18next).init({ + resources: { + en: { ...enJSON }, + ja: { ...jaJSON }, + pt: { ...ptJSON }, + }, + lng: "en", +}); diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..bb66910 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,35 @@ +import { + LocationProvider, + Route, + Router, + hydrate, + prerender as ssr, +} from "preact-iso"; + +import { Home } from "@/pages/Home"; +import { Header } from "./components/Header.jsx"; +import { NotFound } from "./pages/_404.jsx"; +import "./style.css"; +import './i18n'; + +export function App() { + return ( + +
+
+ + + + +
+ + ); +} + +if (typeof window !== "undefined") { + hydrate(, document.getElementById("app")); +} + +export async function prerender(data) { + return await ssr(); +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..ac680b3 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/locale/en.json b/src/locale/en.json new file mode 100644 index 0000000..d62c31e --- /dev/null +++ b/src/locale/en.json @@ -0,0 +1,16 @@ +{ + "translation": { + "header.home": "Home", + "header.projects": "Projects", + "header.social": "Social", + "header.git": "Git", + "hello": "Hello, I'm hknsh.", + "introduction": "I am a self-taught JavaScript programmer focused on back-end development, a Linux enthusiast, a big fan of indie and rhythm games, I read various mangas and I study other languages.", + "i18n": "Also available in:", + "description": "I started studying programming back in 2017 when I was 11 years old! Since then I've been creating a few projects to improve my skills.", + "technologies": "Technologies", + "projects": "Projects & Contributions", + "knedita": "An open-source social media.", + "biome": "Contributed to the Portuguese translation of the documentation" + } +} diff --git a/src/locale/ja.json b/src/locale/ja.json new file mode 100644 index 0000000..ac0bb8a --- /dev/null +++ b/src/locale/ja.json @@ -0,0 +1,16 @@ +{ + "translation": { + "header.home": "ホーム", + "header.projects": "プロジェクト", + "header.social": "ソーシャル", + "header.git": "Git", + "hello": "こんにちは, ハックントッシュです.", + "introduction": "僕は独学のJavaScriptプログラマーで、バックエンド開発に注力しています。Linux愛好者で、インディーゲームやリズムゲームの大ファンです。さまざまなマンガを読んでおり、他の言語も勉強しています。", + "i18n": "他の言語でも利用できます:", + "description": "2017年、まだ11歳のときにプログラミングの勉強を始めました。それ以来、スキルを磨くためにいくつかのプロジェクトを作ってきました。", + "technologies": "技術", + "projects": "プロジェクトと貢献", + "knedita": "オープンソースのSNS。", + "biome": "ポルトガル語のドキュメント翻訳に貢献しました" + } +} diff --git a/src/locale/pt.json b/src/locale/pt.json new file mode 100644 index 0000000..0cc40e7 --- /dev/null +++ b/src/locale/pt.json @@ -0,0 +1,16 @@ +{ + "translation": { + "header.home": "Início", + "header.projects": "Projetos", + "header.social": "Social", + "header.git": "Git", + "hello": "Olá, sou hknsh.", + "introduction": "Sou um programador autodidata JavaScript focado em desenvolvimento back-end, entusiasta Linux, grande fã de jogos indie e de ritmo, leio vários mangás e estudo outros idiomas.", + "i18n": "Também disponível em:", + "description": "Comecei a estudar programação em 2017, quando eu tinha apenas 11 anos. Desde então venho criando alguns projetos para aprimorar minhas habilidades", + "technologies": "Tecnologias", + "projects": "Projetos & Contribuições", + "knedita": "Uma rede social open-source.", + "biome": "Contribuí na tradução em Português da documentação" + } +} diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx new file mode 100644 index 0000000..3812536 --- /dev/null +++ b/src/pages/Home/index.tsx @@ -0,0 +1,115 @@ +import { TechnologiesTab } from "@/components/TechnologiesTab"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel"; +import { Separator } from "@/components/ui/separator"; +import { useState } from "preact/hooks"; +import { useTranslation } from "react-i18next"; +import { useEffect } from "react"; + +export function Home() { + const { + t, + i18n: { changeLanguage, language }, + } = useTranslation(); + const [, setCurrentLanguage] = useState(language); + + useEffect(() => { + const storedLanguage = localStorage.getItem("language"); + if (storedLanguage) { + setCurrentLanguage(storedLanguage); + changeLanguage(storedLanguage); + } + }, [changeLanguage]); + + const handleChangeLanguage = (lang: string) => { + setCurrentLanguage(lang); + changeLanguage(lang); + localStorage.setItem("language", lang); + }; + return ( +
+
+
+

{t("hello")}

+

{t("introduction")}

+
+

{t("i18n")}

+
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+

{t("description")}

+
+ + + +
+
+

{t("technologies")}

+ +
+
+ + + +
+
+

{t("projects")}

+ + + + +
+ + + + Project Knedita + + + +
+
+
+ + +
+
+
+
+
+ ); +} diff --git a/src/pages/_404.tsx b/src/pages/_404.tsx new file mode 100644 index 0000000..36182c8 --- /dev/null +++ b/src/pages/_404.tsx @@ -0,0 +1,8 @@ +export function NotFound() { + return ( +
+

404: Not Found

+

It's gone :(

+
+ ); +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..7c594af --- /dev/null +++ b/src/style.css @@ -0,0 +1,87 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + +@layer components { + .link { + @apply underline decoration-indigo-500 decoration-2 underline-offset-8 transition delay-100 ease-in-out hover:text-neutral-50 hover:decoration-indigo-600 text-lg md:text-xl; + } + .flex-center { + @apply flex items-center justify-center; + } + .neutral-btn { + @apply flex-center max-w-sm rounded-xl bg-neutral-800 p-3 text-sm transition ease-in hover:bg-neutral-900 md:p-4 md:text-base; + } + .neutral-btn-lg { + @apply flex-center w-48 rounded-xl bg-neutral-800 p-3 text-base transition ease-in hover:bg-neutral-900 md:p-4 md:text-base; + } + .text { + @apply text-base md:text-lg font-medium; + } + .title { + @apply text-xl md:text-4xl decoration-2 underline decoration-indigo-500 underline-offset-[12px] + } +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..a53c1ea --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,57 @@ +/** @type {import('tailwindcss').Config} */ +export default { + darkMode: ["class"], + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + theme: { + extend: { + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + colors: { + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + chart: { + 1: "hsl(var(--chart-1))", + 2: "hsl(var(--chart-2))", + 3: "hsl(var(--chart-3))", + 4: "hsl(var(--chart-4))", + 5: "hsl(var(--chart-5))", + }, + }, + }, + }, + plugins: [require("tailwindcss-animate")], +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..75cbefb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + + /* Preact Config */ + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"], + "@/*": ["./src/*"] + }, + + "baseUrl": "." + }, + "include": ["node_modules/vite/client.d.ts", "**/*"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..e81c97c --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,23 @@ +import path from "node:path"; +import preact from "@preact/preset-vite"; +import { defineConfig } from "vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + preact({ + prerender: { + enabled: true, + renderTarget: "#app", + additionalPrerenderRoutes: ["/404"], + previewMiddlewareEnabled: true, + previewMiddlewareFallback: "/404", + }, + }), + ], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, +});