diff --git a/bun.lock b/bun.lock index b679183..4997316 100644 --- a/bun.lock +++ b/bun.lock @@ -36,6 +36,7 @@ "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", + "framer-motion": "^12.23.24", "input-otp": "^1.4.2", "lucide-react": "^0.544.0", "next": "16.0.0", @@ -417,6 +418,8 @@ "fast-equals": ["fast-equals@5.3.2", "", {}, "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ=="], + "framer-motion": ["framer-motion@12.23.24", "", { "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w=="], + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -461,6 +464,10 @@ "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + "motion-dom": ["motion-dom@12.23.23", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA=="], + + "motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="], + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "next": ["next@16.0.0", "", { "dependencies": { "@next/env": "16.0.0", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.0", "@next/swc-darwin-x64": "16.0.0", "@next/swc-linux-arm64-gnu": "16.0.0", "@next/swc-linux-arm64-musl": "16.0.0", "@next/swc-linux-x64-gnu": "16.0.0", "@next/swc-linux-x64-musl": "16.0.0", "@next/swc-win32-arm64-msvc": "16.0.0", "@next/swc-win32-x64-msvc": "16.0.0", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-nYohiNdxGu4OmBzggxy9rczmjIGI+TpR5vbKTsE1HqYwNm1B+YSiugSrFguX6omMOKnDHAmBPY4+8TNJk0Idyg=="], diff --git a/package.json b/package.json index 396d2b2..75f0ab6 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", + "framer-motion": "^12.23.24", "input-otp": "^1.4.2", "lucide-react": "^0.544.0", "next": "16.0.0", diff --git a/public/bg.avif b/public/bg.avif deleted file mode 100644 index e2c685d..0000000 Binary files a/public/bg.avif and /dev/null differ diff --git a/public/bg.png b/public/bg.png deleted file mode 100644 index 35bbba2..0000000 Binary files a/public/bg.png and /dev/null differ diff --git a/public/char.avif b/public/char.avif index 2d21370..ea2d596 100644 Binary files a/public/char.avif and b/public/char.avif differ diff --git a/src/app/page.tsx b/src/app/page.tsx index ca85446..876e546 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -15,7 +15,7 @@ export default function Home() {

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


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

- Effect Playing Contest 2025 Broadcast Develop
+ Effect Playing Contest 2025 Broadcast Develop
today.isangjeong diff --git a/src/components/ui/animated-text-cycle.tsx b/src/components/ui/animated-text-cycle.tsx new file mode 100644 index 0000000..9c00fef --- /dev/null +++ b/src/components/ui/animated-text-cycle.tsx @@ -0,0 +1,112 @@ +import * as React from "react"; +import { useState, useEffect, useRef } from "react"; +import { motion, AnimatePresence } from "framer-motion"; + +interface AnimatedTextCycleProps { + words: string[]; + interval?: number; + className?: string; +} + +export default function AnimatedTextCycle({ + words, + interval = 5000, + className = "", +}: AnimatedTextCycleProps) { + const [currentIndex, setCurrentIndex] = useState(0); + const [width, setWidth] = useState("auto"); + const measureRef = useRef(null); + + // Get the width of the current word + useEffect(() => { + if (measureRef.current) { + const elements = measureRef.current.children; + if (elements.length > currentIndex) { + // Add a small buffer (10px) to prevent text wrapping + const newWidth = elements[currentIndex].getBoundingClientRect().width; + setWidth(`${newWidth}px`); + } + } + }, [currentIndex]); + + useEffect(() => { + const timer = setInterval(() => { + setCurrentIndex((prevIndex) => (prevIndex + 1) % words.length); + }, interval); + + return () => clearInterval(timer); + }, [interval, words.length]); + + // Container animation for the whole word + const containerVariants = { + hidden: { + y: -20, + opacity: 0, + filter: "blur(8px)" + }, + visible: { + y: 0, + opacity: 1, + filter: "blur(0px)", + transition: { + duration: 0.4, + ease: "easeOut" + } + }, + exit: { + y: 20, + opacity: 0, + filter: "blur(8px)", + transition: { + duration: 0.3, + ease: "easeIn" + } + }, + }; + + return ( + <> + {/* Hidden measurement div with all words rendered */} + + + {/* Visible animated word */} + + + + {words[currentIndex]} + + + + + ); +} \ No newline at end of file