diff --git a/.gitignore b/.gitignore index 5ef6a52..1ec5eb9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +certificates \ No newline at end of file diff --git a/bun.lock b/bun.lock index 9a2e975..59b338e 100644 --- a/bun.lock +++ b/bun.lock @@ -40,6 +40,7 @@ "lucide-react": "^0.544.0", "next": "15.5.4", "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", "react": "19.1.0", "react-day-picker": "^9.11.0", "react-dom": "19.1.0", @@ -180,6 +181,8 @@ "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.7", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A=="], + "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="], "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], @@ -214,6 +217,8 @@ "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], + "@radix-ui/react-form": ["@radix-ui/react-form@0.1.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ=="], + "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="], "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], @@ -226,6 +231,10 @@ "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w=="], + "@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.8", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg=="], + + "@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw=="], + "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], @@ -256,10 +265,14 @@ "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], + "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g=="], + "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="], "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="], + "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg=="], + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], @@ -464,6 +477,8 @@ "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + "radix-ui": ["radix-ui@1.4.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.16", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-navigation-menu": "1.2.14", "@radix-ui/react-one-time-password-field": "0.1.8", "@radix-ui/react-password-toggle-field": "0.1.3", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-toolbar": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA=="], + "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], "react-day-picker": ["react-day-picker@9.11.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-L4FYOaPrr3+AEROeP6IG2mCORZZfxJDkJI2df8mv1jyPrNYeccgmFPZDaHyAuPCBCddQFozkxbikj2NhMEYfDQ=="], diff --git a/next.config.ts b/next.config.ts index e9ffa30..10025d9 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ + allowedDevOrigins: ['imnyang.dev'], }; export default nextConfig; diff --git a/package.json b/package.json index f6c2929..9dee9a8 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "lucide-react": "^0.544.0", "next": "15.5.4", "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", "react": "19.1.0", "react-day-picker": "^9.11.0", "react-dom": "19.1.0", diff --git a/public/background/1.avif b/public/background/1.avif new file mode 100644 index 0000000..c40b70d Binary files /dev/null and b/public/background/1.avif differ diff --git a/public/background/10.avif b/public/background/10.avif new file mode 100644 index 0000000..07bba24 Binary files /dev/null and b/public/background/10.avif differ diff --git a/public/background/11.avif b/public/background/11.avif new file mode 100644 index 0000000..c04cb49 Binary files /dev/null and b/public/background/11.avif differ diff --git a/public/background/12.avif b/public/background/12.avif new file mode 100644 index 0000000..3c0008e Binary files /dev/null and b/public/background/12.avif differ diff --git a/public/background/13.avif b/public/background/13.avif new file mode 100644 index 0000000..5349006 Binary files /dev/null and b/public/background/13.avif differ diff --git a/public/background/14.avif b/public/background/14.avif new file mode 100644 index 0000000..e533667 Binary files /dev/null and b/public/background/14.avif differ diff --git a/public/background/2.avif b/public/background/2.avif new file mode 100644 index 0000000..c2e74c7 Binary files /dev/null and b/public/background/2.avif differ diff --git a/public/background/3.avif b/public/background/3.avif new file mode 100644 index 0000000..9b9b8bb Binary files /dev/null and b/public/background/3.avif differ diff --git a/public/background/4.avif b/public/background/4.avif new file mode 100644 index 0000000..846c9b5 Binary files /dev/null and b/public/background/4.avif differ diff --git a/public/background/5.avif b/public/background/5.avif new file mode 100644 index 0000000..261193e Binary files /dev/null and b/public/background/5.avif differ diff --git a/public/background/6.avif b/public/background/6.avif new file mode 100644 index 0000000..fbbb8cf Binary files /dev/null and b/public/background/6.avif differ diff --git a/public/background/7.avif b/public/background/7.avif new file mode 100644 index 0000000..b386682 Binary files /dev/null and b/public/background/7.avif differ diff --git a/public/background/8.avif b/public/background/8.avif new file mode 100644 index 0000000..2e9792f Binary files /dev/null and b/public/background/8.avif differ diff --git a/public/background/9.avif b/public/background/9.avif new file mode 100644 index 0000000..a5f4cf2 Binary files /dev/null and b/public/background/9.avif differ diff --git a/public/bg.avif b/public/bg.avif new file mode 100644 index 0000000..e2c685d Binary files /dev/null and b/public/bg.avif differ diff --git a/public/bg.png b/public/bg.png new file mode 100644 index 0000000..35bbba2 Binary files /dev/null and b/public/bg.png differ diff --git a/public/char.avif b/public/char.avif new file mode 100644 index 0000000..7e9208d Binary files /dev/null and b/public/char.avif differ diff --git a/src/app/favicon.ico b/src/app/favicon.ico index 718d6fe..0e5a95c 100644 Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ diff --git a/src/app/globals.css b/src/app/globals.css index cfb4661..0c750d9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,12 +1,12 @@ +@import url("https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@v1.0.3/packages/wanted-sans/fonts/webfonts/variable/split/WantedSansVariable.min.css"); @import "tailwindcss"; @import "tw-animate-css"; @custom-variant dark (&:is(.dark *)); @font-face { - font-family: 'NType82Headline'; - src: url('https://f.imnya.ng/font/NType82-Headline.woff2') format('woff2'); - + font-family: "NType82Headline"; + src: url("https://f.imnya.ng/font/NType82-Headline.woff2") format("woff2"); } .font-ntype { @@ -14,43 +14,6 @@ } :root { - --background: hsl(340 40% 98%); - --foreground: hsl(315 21% 8%); - --card: hsl(340 40% 98%); - --card-foreground: hsl(315 21% 8%); - --popover: hsl(340 40% 98%); - --popover-foreground: hsl(315 21% 8%); - --primary: hsl(340 25% 15%); - --primary-foreground: hsl(0 0% 98%); - --secondary: hsl(340 25% 95%); - --secondary-foreground: hsl(240 5.9% 10%); - --muted: hsl(340 20% 95%); - --muted-foreground: hsl(340 10% 60%); - --accent: hsl(340 25% 94%); - --accent-foreground: hsl(240 5.9% 10%); - --destructive: hsl(0 84.2% 60.2%); - --destructive-foreground: hsl(0 0% 98%); - --border: hsl(340 25% 90%); - --input: hsl(340 25% 90%); - --ring: hsl(315 21% 8%); - --chart-1: hsl(12 76% 61%); - --chart-2: hsl(173 58% 39%); - --chart-3: hsl(197 37% 24%); - --chart-4: hsl(43 74% 66%); - --chart-5: hsl(27 87% 67%); - --radius: 0.6rem; - --sidebar-background: hsl(340 25% 98%); - --sidebar-foreground: hsl(240 5.3% 26.1%); - --sidebar-primary: hsl(240 5.9% 10%); - --sidebar-primary-foreground: hsl(0 0% 98%); - --sidebar-accent: hsl(340 20% 95%); - --sidebar-accent-foreground: hsl(240 5.9% 10%); - --sidebar-border: hsl(340 20% 90%); - --sidebar-ring: hsl(217.2 91.2% 59.8%); - --sidebar: hsl(0 0% 98%); -} - -.dark { --background: hsl(315 21% 8%); --foreground: hsl(0 0% 98%); --card: hsl(315 21% 8%); @@ -151,5 +114,9 @@ } body { @apply bg-background text-foreground; + font-family: "Wanted Sans Variable", "Wanted Sans", -apple-system, + BlinkMacSystemFont, system-ui, "Segoe UI", "Apple SD Gothic Neo", + "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol", sans-serif; } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 65d2a11..ce83b95 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,5 @@ import type { Metadata } from "next"; import "./globals.css"; -import Sidebar from "@/components/sidebar"; export const metadata: Metadata = { title: "남현석 | :two_hearts: imnya.ng", @@ -15,7 +14,6 @@ export default function RootLayout({ return ( - {children} diff --git a/src/app/page.tsx b/src/app/page.tsx index fb1335a..ca85446 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,18 +1,26 @@ +import NeoFetch from "@/components/NeoFetch"; +import TimelineComponent from "@/components/timeline"; +import Top from "@/components/Top"; + export default function Home() { return ( -
-
-
-
-
-

About

-

항상 새로운 것을 찾고 삶을 더 간단명료하게 만들고 있는 학생 개발자 남현석입니다.

-
+
+ +
+ +
+

💕 About

+

초등학교 시절 운영체제에 흥미를 느껴 컴퓨터를 시작했고, 이후 프로그래밍에 관심을 갖게 되었습니다.

+

초등학교 4학년 때 Python으로 프로그래밍을 시작했으며, 현재는 TypeScript를 주로 사용합니다.

+

최근에는 정보보안 분야 중 웹 해킹에 관심이 많습니다.

+
+

대표적인 프로젝트들은 아래와 같습니다.

+ Effect Playing Contest 2025 Broadcast Develop
+ today.isangjeong +
+ + +
); } diff --git a/src/app/timeline/route.ts b/src/app/timeline/route.ts new file mode 100644 index 0000000..0daf1c8 --- /dev/null +++ b/src/app/timeline/route.ts @@ -0,0 +1,3 @@ +export const timeline = () => { + window.location.hash = '#timeline'; +}; \ No newline at end of file diff --git a/src/components/NeoFetch.tsx b/src/components/NeoFetch.tsx new file mode 100644 index 0000000..402c9a8 --- /dev/null +++ b/src/components/NeoFetch.tsx @@ -0,0 +1,110 @@ +"use client"; +import { events } from "@/lib/events"; +import { useIpData } from "../hooks/use-ip-data"; +import { useWakaTimeData } from "../hooks/use-wakatime-data"; + +export default function NeoFetch() { + const ipData = useIpData(); + const wakaTimeData = useWakaTimeData(); + + return ( +
+
+ +
+ + imnyang@adofai.gg + +

----------

+ +

+ Uptime:{" "} + {(() => { + const startDate = new Date("2010-11-08T00:00:00+09:00"); + const now = new Date(); + let diff = now.getTime() - startDate.getTime(); + + const years = Math.floor(diff / (1000 * 60 * 60 * 24 * 365)); + diff %= 1000 * 60 * 60 * 24 * 365; + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + diff %= 1000 * 60 * 60 * 24; + const hours = Math.floor(diff / (1000 * 60 * 60)); + diff %= 1000 * 60 * 60; + const mins = Math.floor(diff / (1000 * 60)); + + return `${years} years, ${days} days, ${hours} hours, ${mins} mins`; + })()} +

+ +

+ Experience:{" "} + {Object.values(events).flat().length} +

+ +

+ WakaTime:{" "} + {wakaTimeData?.data?.total_seconds_including_other_language + ? (() => { + const seconds = wakaTimeData.data.total_seconds_including_other_language; + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + return `${days} days, ${hours} hours, ${minutes} minutes, ${Math.round( + seconds % 60 + )} seconds`; + })() + : "N/A"} +

+ +

+ Most used Language:{" "} + {wakaTimeData?.data?.languages[0]?.name || "N/A"}{" "} + {wakaTimeData?.data?.languages[0]?.total_seconds + ? (() => { + const seconds = wakaTimeData.data.languages[0].total_seconds; + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + return `(${days} days, ${hours} hours, ${minutes} minutes, ${Math.round( + seconds % 60 + )} seconds)`; + })() + : "N/A"} +

+ +

+ Terminal: {ipData} +

+ +

+ Locale: ko_KR.UTF-8 +

+ +
+
+

⠀⠀

+

⠀⠀

+

⠀⠀

+

⠀⠀

+

⠀⠀

+

⠀⠀

+

⠀⠀

+

⠀⠀

+ +

⠀⠀

+

⠀⠀

+

⠀⠀

+

⠀⠀

+

⠀⠀

+

⠀⠀

+

⠀⠀

+

⠀⠀

+
+
+
+
+ ); +} diff --git a/src/components/Top.tsx b/src/components/Top.tsx new file mode 100644 index 0000000..02f1032 --- /dev/null +++ b/src/components/Top.tsx @@ -0,0 +1,105 @@ +"use client"; +import Sidebar from "@/components/sidebar"; +import Image from "next/image"; +import { useEffect, useRef, useState } from "react"; + +export default function Top() { + const containerRef = useRef(null); + const [randomBg, setRandomBg] = useState(1); + + useEffect(() => { + setRandomBg(Math.floor(Math.random() * 14) + 1); + }, []); + + const requestPermission = async () => { + const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent); + + if (!isIOS) + window.addEventListener("deviceorientation", handleDeviceOrientation); + + try { + const res = await (DeviceOrientationEvent as any).requestPermission(); + if (res === "granted") { + window.addEventListener("devicemotion", handleDeviceMotion); + window.addEventListener("deviceorientation", handleDeviceOrientation); + } + } catch { + window.addEventListener("deviceorientation", handleDeviceOrientation); + } + }; + + const handleDeviceOrientation = (event: DeviceOrientationEvent) => { + if (!containerRef.current) return; + const gamma = event.gamma || 0; + const beta = event.beta || 0; + + const x = (gamma / 90) * -50; + const y = (beta / 180) * -50; + + const bg = containerRef.current; + bg.style.backgroundPosition = `${50 + x}% ${50 + y}%`; + + const img = bg.querySelector("img"); + if (img) img.style.transform = `translate(${x * 2}px, ${y * 2}px)`; + }; + + const handleDeviceMotion = (event: DeviceMotionEvent) => { + if (!containerRef.current || !event.accelerationIncludingGravity) return; + const { x, y } = event.accelerationIncludingGravity; + if (x === null || y === null) return; + const bg = containerRef.current; + bg.style.backgroundPosition = `${50 - x * 3}% ${50 - y * 3}%`; + }; + + useEffect(() => { + return () => { + window.removeEventListener("deviceorientation", handleDeviceOrientation); + window.removeEventListener("devicemotion", handleDeviceMotion); + }; + }, []); + + return ( +
{ + e.preventDefault(); + window.scrollBy(0, window.innerHeight * (e.deltaY > 0 ? 1 : -1)); + }} + > + +
{ + const { clientX, clientY } = e; + const { innerWidth, innerHeight } = window; + const x = (clientX / innerWidth - 0.5) * -50; + const y = (clientY / innerHeight - 0.5) * -50; + + const bg = e.currentTarget; + if (bg) { + bg.style.backgroundPosition = `${50 + x}% ${50 + y}%`; + } + const img = e.currentTarget.querySelector("img"); + if (img) { + img.style.transform = `translate(${x}px, ${y}px)`; + } + }} + > + character +
+
+ ); +} diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index 75393b8..fb0a9a0 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -1,12 +1,26 @@ import Image from "next/image"; +import { Popover } from "./ui/popover"; export default function Sidebar() { - return ( -
- logo -
-

me@imnya.ng

-
+ return ( +
+
+ logo +
+

+ me@imnya.ng +

- ) -} \ No newline at end of file +
+
+ +
+
+ ); +} diff --git a/src/components/timeline.tsx b/src/components/timeline.tsx new file mode 100644 index 0000000..4350af3 --- /dev/null +++ b/src/components/timeline.tsx @@ -0,0 +1,86 @@ +"use client"; +import { events } from "@/lib/events"; +import { LinkIcon } from "lucide-react"; +import { useEffect, useState, useRef } from "react"; + +export default function TimelineComponent() { + const [selectedYear, setSelectedYear] = useState(null); + + const years = Array.from( + new Set(events.map((event) => new Date(event.date).getFullYear())) + ).sort((a, b) => b - a); + + useEffect(() => { + if (years.length > 0 && selectedYear === null) { + setSelectedYear(years[0]); + } + }, [years, selectedYear]); + + const filteredEvents = selectedYear + ? events.filter( + (event) => new Date(event.date).getFullYear() === selectedYear + ) + : []; + + return ( +
+
+

🌠 수상 및 교육

+
+
+ {/* Left column - Year buttons */} +
+ {years.map((year) => ( + + ))} +
+ + {/* Right column - Events */} +
+
+ {filteredEvents.map((event, index) => ( +
+
+ + {new Date(event.date).toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + })} + + + ㆍ{event.category} + +
+ {event.link ? ( + + {event.description}{" "} + + + ) : ( + {event.description} + )} +
+ ))} +
+
+
+
+
+ ); +} diff --git a/src/components/ui/timeline.tsx b/src/components/ui/timeline.tsx new file mode 100644 index 0000000..89bdfe2 --- /dev/null +++ b/src/components/ui/timeline.tsx @@ -0,0 +1,210 @@ +"use client" + +import * as React from "react" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +// Types +type TimelineContextValue = { + activeStep: number + setActiveStep: (step: number) => void +} + +// Context +const TimelineContext = React.createContext( + undefined +) + +const useTimeline = () => { + const context = React.useContext(TimelineContext) + if (!context) { + throw new Error("useTimeline must be used within a Timeline") + } + return context +} + +// Components +interface TimelineProps extends React.HTMLAttributes { + defaultValue?: number + value?: number + onValueChange?: (value: number) => void + orientation?: "horizontal" | "vertical" +} + +function Timeline({ + defaultValue = 1, + value, + onValueChange, + orientation = "vertical", + className, + ...props +}: TimelineProps) { + const [activeStep, setInternalStep] = React.useState(defaultValue) + + const setActiveStep = React.useCallback( + (step: number) => { + if (value === undefined) { + setInternalStep(step) + } + onValueChange?.(step) + }, + [value, onValueChange] + ) + + const currentStep = value ?? activeStep + + return ( + +
+ + ) +} + +// TimelineContent +function TimelineContent({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +// TimelineDate +interface TimelineDateProps extends React.HTMLAttributes { + asChild?: boolean +} + +function TimelineDate({ + asChild = false, + className, + ...props +}: TimelineDateProps) { + const Comp = asChild ? Slot.Root : "time" + + return ( + + ) +} + +// TimelineHeader +function TimelineHeader({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +// TimelineIndicator +interface TimelineIndicatorProps extends React.HTMLAttributes { + asChild?: boolean +} + +function TimelineIndicator({ + asChild = false, + className, + children, + ...props +}: TimelineIndicatorProps) { + return ( + + ) +} + +// TimelineItem +interface TimelineItemProps extends React.HTMLAttributes { + step: number +} + +function TimelineItem({ step, className, ...props }: TimelineItemProps) { + const { activeStep } = useTimeline() + + return ( +
+ ) +} + +// TimelineSeparator +function TimelineSeparator({ + className, + ...props +}: React.HTMLAttributes) { + return ( +