From 0890457b990f689ebea07e425cfec227fa993304 Mon Sep 17 00:00:00 2001 From: minheon Date: Mon, 30 Mar 2026 13:49:22 +0900 Subject: [PATCH 1/3] [fix] npm audit fix --- package-lock.json | 87 +++++++---------------------------------------- 1 file changed, 12 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index 378112c..d4d9496 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1115,9 +1115,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1132,9 +1129,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1149,9 +1143,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1166,9 +1157,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1183,9 +1171,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1200,9 +1185,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1217,9 +1199,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1234,9 +1213,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1251,9 +1227,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1268,9 +1241,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1285,9 +1255,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1302,9 +1269,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1319,9 +1283,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1571,9 +1532,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1595,9 +1553,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1619,9 +1574,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1643,9 +1595,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1818,9 +1767,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1838,9 +1784,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1858,9 +1801,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1878,9 +1818,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2283,9 +2220,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2501,9 +2438,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -3144,9 +3081,9 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -3760,9 +3697,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { From 8e1cc981fae17646bdbb40279e3b51db13a351bd Mon Sep 17 00:00:00 2001 From: minheon Date: Mon, 30 Mar 2026 16:39:55 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[feat]=20main=20layout=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 441 ++++++++++++++++++++++++++++++++++ package.json | 1 + src/app/index.css | 265 ++++++++++++++++++-- src/hooks/useInView.ts | 32 +++ src/hooks/useScrollToTop.ts | 15 ++ src/layouts/Footer.tsx | 62 +++-- src/layouts/GNB.tsx | 79 +++--- src/layouts/MainLayout.tsx | 19 +- src/layouts/PageNavigator.tsx | 70 ++++++ src/vite-env.d.ts | 2 + vite.config.ts | 3 +- 11 files changed, 902 insertions(+), 87 deletions(-) create mode 100644 src/hooks/useInView.ts create mode 100644 src/hooks/useScrollToTop.ts create mode 100644 src/layouts/PageNavigator.tsx create mode 100644 src/vite-env.d.ts diff --git a/package-lock.json b/package-lock.json index d4d9496..79fd4d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "typescript": "~5.9.3", "typescript-eslint": "^8.56.1", "vite": "^7.3.1", + "vite-plugin-svgr": "^5.0.0", "vite-tsconfig-paths": "^6.1.1" } }, @@ -1023,6 +1024,29 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", @@ -1373,6 +1397,231 @@ "win32" ] }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, "node_modules/@tailwindcss/node": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", @@ -2505,6 +2754,19 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001779", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz", @@ -2602,6 +2864,33 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2668,6 +2957,17 @@ "node": ">=8" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2703,6 +3003,29 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2987,6 +3310,13 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3349,6 +3679,13 @@ "node": ">=0.8.19" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3429,6 +3766,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3480,6 +3824,13 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3503,6 +3854,16 @@ "dev": true, "license": "MIT" }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3599,6 +3960,17 @@ "dev": true, "license": "MIT" }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/node-releases": { "version": "2.0.36", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", @@ -3669,6 +4041,25 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3689,6 +4080,16 @@ "node": ">=8" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3933,6 +4334,17 @@ "node": ">=8" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3969,6 +4381,13 @@ "node": ">=8" } }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true, + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", @@ -4041,6 +4460,13 @@ } } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4215,6 +4641,21 @@ } } }, + "node_modules/vite-plugin-svgr": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-5.0.0.tgz", + "integrity": "sha512-CZFWDtbWSLnF6C+uv8u7E5Ao6UVQYBpJrS6212XsEod/Lm4ErhOoFc01/po4ie5hqvMCr5KYrlMrSGQQEtMtBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.2.0", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": ">=3.0.0" + } + }, "node_modules/vite-tsconfig-paths": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.1.tgz", diff --git a/package.json b/package.json index 49c6aa9..fdd494f 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "typescript": "~5.9.3", "typescript-eslint": "^8.56.1", "vite": "^7.3.1", + "vite-plugin-svgr": "^5.0.0", "vite-tsconfig-paths": "^6.1.1" } } diff --git a/src/app/index.css b/src/app/index.css index 7d30fc7..1158c04 100644 --- a/src/app/index.css +++ b/src/app/index.css @@ -1,31 +1,244 @@ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap'); -@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap'); +@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css'); + @import "tailwindcss"; -:root { - --Color-Neutral-90: #151515; - --Color-Neutral-80: #303030; - --Color-Neutral-70: #606060; - --Color-Neutral-60: #808080; - --Color-Neutral-40: #DADBDE; - --Color-Neutral-30: #EAEBEF; - --Color-Neutral-20: #F0F1F4; - --Color-Neutral-10: #F7F8FA; - --Color-Neutral-00: #FFF; - --Color-Negative: #E71C3B; - --Color-Negative-Hover: #C21630; - --Color-B: #4880EF; - --Color-B-Hover: #3568C5; - - --font-family-primary: 'Pretendard', system-ui, sans-serif; - --font-family-display: 'Playfair Display', serif; - --font-family-inter: 'Inter', sans-serif; +/* ─── Design Tokens ───────────────────────────────────────────────── */ +@theme { + /* Navy ─ 950(darkest) · 900 · 800 · 50(lightest) + 짙은 네이비 계열. 본문 텍스트, 다크 섹션 배경, 그라디언트 끝 */ + --color-navy-950: #021341; + --color-navy-900: #0A1128; + --color-navy-800: #1A2B5E; + --color-navy-50: #F4F6FB; + + /* Violet ─ 700(deep) · 500(accent) + 퍼플 계열. 그라디언트 시작(700), 활성 상태·아이콘(500) */ + --color-violet-700: #4F1DA1; + --color-violet-500: #6C5CE7; + /* Marketing accents (CTA / Process / Hero blobs — shared with DEMO) */ + --color-marketing-cream: #fff3eb; + --color-marketing-lilac: #e4cfff; + --color-marketing-ice: #f5f9ff; + --color-lavender-100: #f3e8ff; + --color-lavender-200: #e9d4ff; + --color-lavender-300: #d8b4fe; + --color-violet-hover: #af90ff; + /* Hero radial + blob tints */ + --color-hero-wash-start: #e0e7ff; + --color-hero-wash-mid: #faf5ff; + --color-hero-wash-end: #fdf2f8; + --color-marketing-blush: #fbcfe8; + + /* Neutral ─ 90(darkest) · 80 · 70 · 60 · 40 · 30 · 20 · 10 · 00(white) */ + --color-neutral-90: #151515; + --color-neutral-80: #303030; + --color-neutral-70: #606060; + --color-neutral-60: #808080; + --color-neutral-40: #DADBDE; + --color-neutral-30: #EAEBEF; + --color-neutral-20: #F0F1F4; + --color-neutral-10: #F7F8FA; + --color-neutral-00: #FFFFFF; + + /* Status ─ {critical | warning | good | info} × {bg | text | border | dot} + 파스텔 시맨틱 팔레트. 원색(빨강·초록) 사용 금지 */ + --color-status-critical-bg: #FFF0F0; + --color-status-critical-text: #7C3A4B; + --color-status-critical-border: #F5D5DC; + --color-status-critical-dot: #D4889A; + + --color-status-warning-bg: #FFF6ED; + --color-status-warning-text: #7C5C3A; + --color-status-warning-border: #F5E0C5; + --color-status-warning-dot: #D4A872; + + --color-status-good-bg: #F3F0FF; + --color-status-good-text: #4A3A7C; + --color-status-good-border: #D5CDF5; + --color-status-good-dot: #9B8AD4; + + --color-status-info-bg: #EFF0FF; + --color-status-info-text: #3A3F7C; + --color-status-info-border: #C5CBF5; + --color-status-info-dot: #7A84D4; + + /* Typography */ + --font-sans: "Pretendard Variable", "Pretendard", ui-sans-serif, system-ui, sans-serif; + --font-serif: "Playfair Display", ui-serif, Georgia, Cambria, serif; + --font-inter: "Inter", ui-sans-serif, system-ui, sans-serif; + + /* Animation */ + --animate-blob: blob 7s infinite; + --animate-blob-large: blob-large 25s infinite ease-in-out; + --animate-fade-in-up: fade-in-up 0.6s ease-out both; + --animate-fade-in-left: fade-in-left 0.6s ease-out both; + --animate-fade-in-right: fade-in-right 0.6s ease-out both; + --animate-fade-in-scale: fade-in-scale 0.8s ease-out both; } -html, -body { - font-family: var(--font-family-inter); - font-style: normal; - line-height: normal; - min-height: 100dvh; -} \ No newline at end of file +/* ─── Keyframes ───────────────────────────────────────────────────── */ +@keyframes blob { + 0% { transform: translate(0, 0) scale(1); } + 33% { transform: translate(30px, -50px) scale(1.1); } + 66% { transform: translate(-20px, 20px) scale(0.9); } + 100% { transform: translate(0, 0) scale(1); } +} + +@keyframes blob-large { + 0% { transform: translate(0, 0) scale(1); } + 33% { transform: translate(8vw, -8vh) scale(1.1); } + 66% { transform: translate(-8vw, 8vh) scale(0.9); } + 100% { transform: translate(0, 0) scale(1); } +} + +/* fade + slide-up: replaces framer-motion entrance (opacity 0→1, y 20→0) */ +@keyframes fade-in-up { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +/* fade + slide-left/right: whileInView x:-20→0 / x:20→0 */ +@keyframes fade-in-left { + from { opacity: 0; transform: translateX(-20px); } + to { opacity: 1; transform: translateX(0); } +} + +@keyframes fade-in-right { + from { opacity: 0; transform: translateX(20px); } + to { opacity: 1; transform: translateX(0); } +} + +@keyframes fade-in-scale { + from { opacity: 0; transform: scale(0.95); } + to { opacity: 1; transform: scale(1); } +} + +/* ─── Animation Delays ────────────────────────────────────────────── */ +/* UI entrance stagger (short) */ +.animation-delay-100 { animation-delay: 0.1s; } +.animation-delay-200 { animation-delay: 0.2s; } +.animation-delay-300 { animation-delay: 0.3s; } +.animation-delay-400 { animation-delay: 0.4s; } +.animation-delay-500 { animation-delay: 0.5s; } + +/* Blob float delays (long) */ +.animation-delay-2000 { animation-delay: 2s; } +.animation-delay-4000 { animation-delay: 4s; } +.animation-delay-7000 { animation-delay: 7s; } +.animation-delay-14000 { animation-delay: 14s; } + +/* ─── Utility Classes ─────────────────────────────────────────────── */ + +/* 라이트 섹션 헤딩 그라디언트 텍스트 */ +.text-gradient { + background: linear-gradient(to right, var(--color-navy-900), #3b5998); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* 반투명 글래스 카드 — 랜딩 섹션 */ +.glass-card { + @apply bg-white/70 backdrop-blur-xl border border-white/40 rounded-2xl; + box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07); +} + +/* 다크 섹션 위 화이트 카드 사선 그림자 */ +.card-shadow { box-shadow: 3px 4px 12px rgba(0, 0, 0, 0.06); } +.card-shadow:hover { box-shadow: 4px 6px 16px rgba(0, 0, 0, 0.09); } + +/* 브랜드 그라디언트 Primary 버튼 */ +.btn-primary { + @apply bg-gradient-to-r from-violet-700 to-navy-950 text-white rounded-full font-medium transition-all; +} + +/* ─── Typography Scale ────────────────────────────────────────────── */ +/* Display */ +.display-72 { font-family: var(--font-serif); font-size: 72px; font-weight: 700; line-height: 1.1; letter-spacing: -1.44px; } +.display-48 { font-family: var(--font-serif); font-size: 48px; font-weight: 700; line-height: 1.1; letter-spacing: -0.96px; } +.display-36 { font-family: var(--font-serif); font-size: 36px; font-weight: 700; line-height: 1.15; letter-spacing: -0.72px; } + +/* Headline */ +.headline-30 { font-family: var(--font-serif); font-size: 30px; font-weight: 700; line-height: 1.2; letter-spacing: -0.6px; } +.headline-24 { font-family: var(--font-serif); font-size: 24px; font-weight: 700; line-height: 1.3; letter-spacing: -0.48px; } +.headline-20 { font-family: var(--font-serif); font-size: 20px; font-weight: 700; line-height: 1.4; letter-spacing: -0.4px; } + +/* Title */ +.title-20 { font-family: var(--font-sans); font-size: 20px; font-weight: 700; line-height: 1.4; letter-spacing: -0.4px; } +.title-18 { font-family: var(--font-sans); font-size: 18px; font-weight: 700; line-height: 1.4; letter-spacing: -0.36px; } +.title-18-semibold { font-family: var(--font-sans); font-size: 18px; font-weight: 600; line-height: 1.4; letter-spacing: -0.36px; } +.title-16 { font-family: var(--font-sans); font-size: 16px; font-weight: 600; line-height: 1.4; letter-spacing: -0.32px; } +.title-14 { font-family: var(--font-sans); font-size: 14px; font-weight: 600; line-height: 1.4; letter-spacing: -0.28px; } + +/* Body */ +.body-20 { font-family: var(--font-sans); font-size: 20px; font-weight: 400; line-height: 1.6; letter-spacing: -0.4px; } +.body-20-medium { font-family: var(--font-sans); font-size: 20px; font-weight: 500; line-height: 1.4; letter-spacing: -0.4px; } +.body-18 { font-family: var(--font-sans); font-size: 18px; font-weight: 400; line-height: 1.6; letter-spacing: -0.36px; } +.body-16 { font-family: var(--font-sans); font-size: 16px; font-weight: 400; line-height: 1.5; letter-spacing: -0.32px; } +.body-16-medium { font-family: var(--font-sans); font-size: 16px; font-weight: 500; line-height: 1.5; letter-spacing: -0.32px; } +.body-14 { font-family: var(--font-sans); font-size: 14px; font-weight: 400; line-height: 1.5; letter-spacing: -0.28px; } +.body-14-medium { font-family: var(--font-sans); font-size: 14px; font-weight: 500; line-height: 1.5; letter-spacing: -0.28px; } + +/* Label */ +.label-12 { font-family: var(--font-sans); font-size: 12px; font-weight: 500; line-height: 1.4; letter-spacing: -0.24px; } +.label-12-semibold { font-family: var(--font-sans); font-size: 12px; font-weight: 600; line-height: 1.4; letter-spacing: -0.24px; } + +/* Responsive body: 18px → 20px at md (text-lg / text-xl) */ +.body-18-md-20 { + font-family: var(--font-sans); + font-weight: 400; + line-height: 1.625; + letter-spacing: -0.36px; + font-size: 1.125rem; +} +@media (min-width: 768px) { + .body-18-md-20 { + font-size: 1.25rem; + letter-spacing: -0.4px; + } +} + +/* Responsive sans title: 18px → 20px at md (module card titles 등) */ +.title-18-md-20 { + font-family: var(--font-sans); + font-weight: 700; + line-height: 1.4; + letter-spacing: -0.36px; + font-size: 1.125rem; +} +@media (min-width: 768px) { + .title-18-md-20 { + font-size: 1.25rem; + letter-spacing: -0.4px; + } +} + +/* Responsive list body: 14px → 16px at md */ +.body-14-md-16 { + font-family: var(--font-sans); + font-weight: 400; + line-height: 1.625; + letter-spacing: -0.28px; + font-size: 0.875rem; +} +@media (min-width: 768px) { + .body-14-md-16 { + font-size: 1rem; + letter-spacing: -0.32px; + } +} + +/* ─── Base ────────────────────────────────────────────────────────── */ +@layer base { + html, body { + @apply font-sans text-neutral-80 bg-neutral-10 antialiased; + font-style: normal; + line-height: normal; + min-height: 100dvh; + } + + h1, h2, h3, h4, h5, h6 { + @apply font-serif text-navy-900; + } +} diff --git a/src/hooks/useInView.ts b/src/hooks/useInView.ts new file mode 100644 index 0000000..cf6a0a2 --- /dev/null +++ b/src/hooks/useInView.ts @@ -0,0 +1,32 @@ +import { useEffect, useRef, useState } from "react"; + +/** + * 요소가 뷰포트에 진입하면 inView = true 로 바뀌는 훅. + * framer-motion의 whileInView({ once: true }) 와 동일한 동작. + */ +export function useInView( + options?: IntersectionObserverInit, +) { + const ref = useRef(null); + const [inView, setInView] = useState(false); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setInView(true); + observer.disconnect(); // once: true + } + }, + { threshold: 0.15, ...options }, + ); + + observer.observe(el); + return () => observer.disconnect(); + }, []); + + return { ref, inView }; +} diff --git a/src/hooks/useScrollToTop.ts b/src/hooks/useScrollToTop.ts new file mode 100644 index 0000000..d795ed6 --- /dev/null +++ b/src/hooks/useScrollToTop.ts @@ -0,0 +1,15 @@ +import { useNavigate } from "react-router-dom"; + +/** + * 지정한 경로로 이동하면서 페이지 상단으로 smooth 스크롤합니다. + * 로고 클릭 등 "홈으로 돌아가기 + 스크롤 초기화" 패턴에 사용합니다. + */ +export function useScrollToTop(path = "/") { + const navigate = useNavigate(); + + return (e: React.MouseEvent) => { + e.preventDefault(); + navigate(path); + window.scrollTo({ top: 0, behavior: "smooth" }); + }; +} diff --git a/src/layouts/Footer.tsx b/src/layouts/Footer.tsx index 73fe3ee..09eb9ed 100644 --- a/src/layouts/Footer.tsx +++ b/src/layouts/Footer.tsx @@ -1,22 +1,48 @@ +import { Link } from "react-router-dom"; +import { useScrollToTop } from "@/hooks/useScrollToTop"; + +const LINKS = [ + { label: "Privacy Policy", href: "/privacy" }, + { label: "Terms of Service", href: "/terms" }, +]; + export function Footer() { - return ( -
-
- CLINICAD -
+ const handleLogoClick = useScrollToTop(); -
- © 2026 CLINICAD. All rights reserved. Infinite Marketing for Premium Medical Business & Marketing Agency -
+ return ( +
+
-
- Privacy Policy - Terms of Service -
-
- ); + {/* 로고 */} + + INFINITH + + + {/* 카피라이트 */} +

+ © {new Date().getFullYear()} CLINICAD. All rights reserved.{" "} +
+ Infinite Marketing for Premium Medical Business & Marketing Agency +

+ + {/* 링크 */} + + + +
+ ); } \ No newline at end of file diff --git a/src/layouts/GNB.tsx b/src/layouts/GNB.tsx index 481c5dd..8a310e3 100644 --- a/src/layouts/GNB.tsx +++ b/src/layouts/GNB.tsx @@ -1,42 +1,51 @@ +import { Link } from "react-router-dom"; +import { useScrollToTop } from "@/hooks/useScrollToTop"; + const MENUS = [ - { name: "Solution", href: "/" }, - { name: "Modules", href: "/" }, - { name: "Use Cases", href: "/" }, -] + { label: "Solution", href: "/#solution" }, + { label: "Modules", href: "/#modules" }, + { label: "Use Cases", href: "/#use-cases" }, +]; export function GNB() { - return ( - + ); } \ No newline at end of file diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx index adf6ddf..d03b157 100644 --- a/src/layouts/MainLayout.tsx +++ b/src/layouts/MainLayout.tsx @@ -1,13 +1,18 @@ import { Outlet } from "react-router-dom"; import { GNB } from "@/layouts/GNB"; import { Footer } from "@/layouts/Footer"; +import { PageNavigator } from "@/layouts/PageNavigator"; export default function MainLayout() { - return ( -
-
-
-
-
- ) + return ( +
+ + {/* fixed GNB(h-20) 높이만큼 상단 여백 확보 */} +
+ +
+
+ +
+ ); } \ No newline at end of file diff --git a/src/layouts/PageNavigator.tsx b/src/layouts/PageNavigator.tsx new file mode 100644 index 0000000..2bd929c --- /dev/null +++ b/src/layouts/PageNavigator.tsx @@ -0,0 +1,70 @@ +import { useLocation, useNavigate } from "react-router-dom"; +import ChevronLeftIcon from "@/assets/home/chevron-left.svg?react"; +import ChevronRightIcon from "@/assets/home/chevron-right.svg?react"; + +const PAGE_FLOW = [ + { path: "/", label: "홈" }, + { path: "/report", label: "마케팅 분석" }, + { path: "/plan", label: "콘텐츠 기획" }, + { path: "/studio", label: "콘텐츠 제작" }, + { path: "/channels", label: "채널 연결" }, + { path: "/distribute", label: "콘텐츠 배포" }, + { path: "/performance", label: "성과 관리" }, +]; + +export function PageNavigator() { + const location = useLocation(); + const navigate = useNavigate(); + + const currentIndex = PAGE_FLOW.findIndex((p) => p.path === location.pathname); + if (currentIndex === -1) return null; + + const prev = currentIndex > 0 ? PAGE_FLOW[currentIndex - 1] : null; + const next = currentIndex < PAGE_FLOW.length - 1 ? PAGE_FLOW[currentIndex + 1] : null; + + return ( +
+ {/* 이전 페이지 */} + + + {/* 페이지 인디케이터 */} +
+ {PAGE_FLOW.map((page, i) => ( +
+ + {/* 다음 페이지 */} + +
+ ); +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..b1f45c7 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/vite.config.ts b/vite.config.ts index 1a36c56..f96f510 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,8 +2,9 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' import tsconfigPaths from 'vite-tsconfig-paths' +import svgr from 'vite-plugin-svgr' // https://vite.dev/config/ export default defineConfig({ - plugins: [react(), tsconfigPaths(), tailwindcss()], + plugins: [react(), tsconfigPaths(), tailwindcss(), svgr()], }) From bac6b9f910b07c4a2288ff366d69e0ef423275bf Mon Sep 17 00:00:00 2001 From: minheon Date: Mon, 30 Mar 2026 16:40:16 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[update]=20Home=20Page=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/home/arrow-right.svg | 3 + src/assets/home/check-circle.svg | 4 + src/assets/home/chevron-left.svg | 3 + src/assets/home/chevron-right.svg | 3 + src/assets/home/infinity-loop.svg | 16 +++ src/features/home/constants/cta_contents.ts | 8 ++ src/features/home/constants/hero_contents.ts | 13 ++ .../home/constants/modules_contents.ts | 79 +++++++++++ .../home/constants/problem_contents.ts | 25 ++++ .../home/constants/process_contents.ts | 23 +++ .../home/constants/solution_contents.ts | 31 ++++ .../home/constants/use_cases_contents.ts | 36 +++++ src/features/home/hooks/useAnalyze.ts | 15 ++ src/features/home/ui/CTASection.tsx | 89 ++++++++---- src/features/home/ui/HeroSection.tsx | 119 +++++++++------- src/features/home/ui/ProblemSection.tsx | 85 ++++++----- src/features/home/ui/ProcessSection.tsx | 133 +++--------------- src/features/home/ui/SolutionSection.tsx | 94 ++++++------- src/features/home/ui/SystemSection.tsx | 108 +++++++------- src/features/home/ui/UseCaseSection.tsx | 92 ++++++------ .../home/ui/process/AgdpCycleSummaryCard.tsx | 22 +++ .../home/ui/process/AgdpEngineDiagram.tsx | 55 ++++++++ .../home/ui/process/AgdpOrbitNode.tsx | 21 +++ .../home/ui/process/AgdpRewardPathLabel.tsx | 17 +++ .../home/ui/process/MarketingEngineIntro.tsx | 40 ++++++ .../home/ui/system/CoreModuleCard.tsx | 37 +++++ .../ui/system/CoreModulesCenterHeading.tsx | 20 +++ 27 files changed, 798 insertions(+), 393 deletions(-) create mode 100644 src/assets/home/arrow-right.svg create mode 100644 src/assets/home/check-circle.svg create mode 100644 src/assets/home/chevron-left.svg create mode 100644 src/assets/home/chevron-right.svg create mode 100644 src/assets/home/infinity-loop.svg create mode 100644 src/features/home/constants/cta_contents.ts create mode 100644 src/features/home/constants/hero_contents.ts create mode 100644 src/features/home/constants/modules_contents.ts create mode 100644 src/features/home/constants/problem_contents.ts create mode 100644 src/features/home/constants/process_contents.ts create mode 100644 src/features/home/constants/solution_contents.ts create mode 100644 src/features/home/constants/use_cases_contents.ts create mode 100644 src/features/home/hooks/useAnalyze.ts create mode 100644 src/features/home/ui/process/AgdpCycleSummaryCard.tsx create mode 100644 src/features/home/ui/process/AgdpEngineDiagram.tsx create mode 100644 src/features/home/ui/process/AgdpOrbitNode.tsx create mode 100644 src/features/home/ui/process/AgdpRewardPathLabel.tsx create mode 100644 src/features/home/ui/process/MarketingEngineIntro.tsx create mode 100644 src/features/home/ui/system/CoreModuleCard.tsx create mode 100644 src/features/home/ui/system/CoreModulesCenterHeading.tsx diff --git a/src/assets/home/arrow-right.svg b/src/assets/home/arrow-right.svg new file mode 100644 index 0000000..4b8981c --- /dev/null +++ b/src/assets/home/arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/home/check-circle.svg b/src/assets/home/check-circle.svg new file mode 100644 index 0000000..3822028 --- /dev/null +++ b/src/assets/home/check-circle.svg @@ -0,0 +1,4 @@ + diff --git a/src/assets/home/chevron-left.svg b/src/assets/home/chevron-left.svg new file mode 100644 index 0000000..b6d06ff --- /dev/null +++ b/src/assets/home/chevron-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/home/chevron-right.svg b/src/assets/home/chevron-right.svg new file mode 100644 index 0000000..b4f8f20 --- /dev/null +++ b/src/assets/home/chevron-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/home/infinity-loop.svg b/src/assets/home/infinity-loop.svg new file mode 100644 index 0000000..c589390 --- /dev/null +++ b/src/assets/home/infinity-loop.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/features/home/constants/cta_contents.ts b/src/features/home/constants/cta_contents.ts new file mode 100644 index 0000000..c7b46d1 --- /dev/null +++ b/src/features/home/constants/cta_contents.ts @@ -0,0 +1,8 @@ +/** CTA 섹션 카피 */ + +export const CTA_HEADLINE = "Ready to Transform Your Marketing?"; +export const CTA_DESCRIPTION = + "URL 하나로 시작하는 완벽한 마케팅 자동화. 지금 바로 무료 진단을 받아보세요."; +export const CTA_URL_PLACEHOLDER = "Enter Your URL"; +export const CTA_BUTTON_LABEL = "Analyze"; +export const CTA_FOOTNOTE = "네이버 블로그, 플레이스, 소셜미디어 종합 분석 리포트 받아보기"; diff --git a/src/features/home/constants/hero_contents.ts b/src/features/home/constants/hero_contents.ts new file mode 100644 index 0000000..7c6c141 --- /dev/null +++ b/src/features/home/constants/hero_contents.ts @@ -0,0 +1,13 @@ +/** Hero 섹션 카피 */ + +export const HERO_BADGE_TEXT = + "Agentic AI Marketing Automation for Premium Medical Business & Marketing Agency"; + +export const HERO_URL_PLACEHOLDER = "Enter Your Website URL"; +export const HERO_CTA_BUTTON_LABEL = "Analyze Marketing Performance"; +export const HERO_FOOTNOTE = + "네이버 블로그, 플레이스, 소셜미디어 등 Online Presence 종합 분석 리포트를 제공합니다."; + +export const HERO_LEAD_EN = "Marketing that learns, improves, and accelerates — automatically."; +export const HERO_LEAD_KO = + "쓸수록 더 정교해지는 AI 마케팅 엔진. 콘텐츠 기획, 생성, 영상 제작, 채널 배포, 데이터 분석까지 하나로."; diff --git a/src/features/home/constants/modules_contents.ts b/src/features/home/constants/modules_contents.ts new file mode 100644 index 0000000..80e6117 --- /dev/null +++ b/src/features/home/constants/modules_contents.ts @@ -0,0 +1,79 @@ +/** Core Modules 섹션 — DEMO Modules */ + +export type CoreModule = { + step: string; + title: string; + items: string[]; + highlight: string; +}; + +export const CORE_MODULES: CoreModule[] = [ + { + step: "1", + title: "Marketing Intelligence", + items: [ + "브랜딩, 마케팅 현황 분석", + "타겟 고객 분석", + "키워드 분석", + "경쟁 및 포지셔닝 분석", + "SEO 전략 & 채널별 콘텐츠 기획", + ], + highlight: "AI 기반 시장 통찰력 도출", + }, + { + step: "2", + title: "Content Creation", + items: [ + "블로그 콘텐츠 생성", + "SEO 콘텐츠 생성", + "SNS 콘텐츠 생성", + "마케팅 카피 생성", + "Human-in-the loop 프로세스", + ], + highlight: "고품질 맞춤형 콘텐츠 자동화", + }, + { + step: "3", + title: "Video Automation", + items: [ + "블로그 → 영상 변환", + "숏폼 콘텐츠 생성", + "유튜브 콘텐츠 제작", + "SNS 영상 제작", + "멀티모달 AI 엔진: 영상 + 음악 + 카피", + ], + highlight: "원클릭 영상 제작 시스템", + }, + { + step: "4", + title: "Distribution Engine", + items: [ + "블로그 게시", + "SNS 자동 게시", + "유튜브 업로드", + "콘텐츠 일정 관리", + "SEO, AEO 자동 최적화", + ], + highlight: "전 채널 통합 배포 및 최적화", + }, + { + step: "5", + title: "Performance Intelligence", + items: [ + "SEO 성과 분석", + "콘텐츠 성과 분석", + "채널 성과 분석", + "AI 콘텐츠 개선 전략 추천", + "데이터 기반 효과 검증", + ], + highlight: "실시간 성과 추적 및 개선", + }, +]; + +export const MODULE_CARD_STAGGER = [ + "", + "animation-delay-100", + "animation-delay-200", + "animation-delay-300", + "animation-delay-400", +] as const; diff --git a/src/features/home/constants/problem_contents.ts b/src/features/home/constants/problem_contents.ts new file mode 100644 index 0000000..4cdbce5 --- /dev/null +++ b/src/features/home/constants/problem_contents.ts @@ -0,0 +1,25 @@ +/** Problem 섹션 — DEMO Problems */ + +export type ProblemCard = { + title: string; + desc: string; + highlight?: boolean; +}; + +export const PROBLEM_CARDS: ProblemCard[] = [ + { + title: "콘텐츠 생산의 한계", + desc: "블로그, SEO, 유튜브, 숏폼 등 지속적 생산이 필요하지만 인력과 비용, 시간 부족으로 제한됩니다.", + }, + { + title: "영상 콘텐츠 제작 비용", + desc: "영상은 중요하지만 촬영, 편집, 기획 비용이 높습니다. 특히 숏폼 콘텐츠는 지속적인 제작이 어렵습니다.", + highlight: true, + }, + { + title: "데이터 기반의 마케팅 부족", + desc: "어떤 콘텐츠가 효과적인지, 어떤 키워드가 유입을 만드는지, 어떤 채널이 성과가 좋은지 알기 어렵고, 각 플랫폼들의 데이터들을 한눈에 파악할 수 없습니다.", + }, +]; + +export const PROBLEM_CARD_STAGGER = ["", "animation-delay-100", "animation-delay-200"] as const; diff --git a/src/features/home/constants/process_contents.ts b/src/features/home/constants/process_contents.ts new file mode 100644 index 0000000..ee1a1e5 --- /dev/null +++ b/src/features/home/constants/process_contents.ts @@ -0,0 +1,23 @@ +/** Process(다크) 섹션 — AGDP 순환 다이어그램 레이아웃 */ + +export type AgdpSlot = "left" | "top" | "right" | "bottom"; + +export type AgdpNodeDef = { + letter: string; + label: string; + slot: AgdpSlot; +}; + +export const AGDP_NODES: readonly AgdpNodeDef[] = [ + { letter: "A", label: "Analysis", slot: "left" }, + { letter: "G", label: "Generation", slot: "top" }, + { letter: "D", label: "Distribution", slot: "right" }, + { letter: "P", label: "Performance", slot: "bottom" }, +]; + +export const AGDP_SLOT_WRAPPER_CLASS: Record = { + left: "absolute top-1/2 left-0 -translate-x-1/2 -translate-y-1/2 z-30 flex flex-col items-center gap-3 md:gap-4", + top: "absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 z-30 flex flex-col items-center gap-3 md:gap-4", + right: "absolute top-1/2 right-0 translate-x-1/2 -translate-y-1/2 z-30 flex flex-col items-center gap-3 md:gap-4", + bottom: "absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2 z-30 flex flex-col items-center gap-3 md:gap-4", +}; diff --git a/src/features/home/constants/solution_contents.ts b/src/features/home/constants/solution_contents.ts new file mode 100644 index 0000000..77b1ebc --- /dev/null +++ b/src/features/home/constants/solution_contents.ts @@ -0,0 +1,31 @@ +/** Solution(Target Audience) 섹션 — DEMO TargetAudience */ + +export type SolutionCard = { + title: string; + description: string; + items: string[]; + cardClass: string; + tagClass: string; + animClass: string; +}; + +export const SOLUTION_CARDS: SolutionCard[] = [ + { + title: "Premium Medical Business", + description: + "고객 LTV가 높고 브랜드 경쟁이 심해 콘텐츠 마케팅이 필수적인 프리미엄 의료 서비스 제공 병원", + items: ["피부과", "성형외과", "치과", "안과", "헬스케어 클리닉", "피트니스"], + cardClass: "bg-gradient-to-br from-navy-50 to-white border border-neutral-20", + tagClass: "border border-neutral-20", + animClass: "animate-fade-in-left animation-delay-100", + }, + { + title: "Medical Marketing Agency", + description: + "콘텐츠 제작 비용 부담과 인력 의존도가 높고 영상 제작 생산성 개선이 필요한 병원 마케팅 대행사", + items: ["병원 마케팅 대행사", "콘텐츠 마케팅 Agency", "영상 마케팅 Agency", "광고 운영 대행사"], + cardClass: "bg-gradient-to-br from-status-good-bg to-white border border-status-good-border/50", + tagClass: "border border-status-good-border/50", + animClass: "animate-fade-in-right animation-delay-200", + }, +]; diff --git a/src/features/home/constants/use_cases_contents.ts b/src/features/home/constants/use_cases_contents.ts new file mode 100644 index 0000000..de7e5b4 --- /dev/null +++ b/src/features/home/constants/use_cases_contents.ts @@ -0,0 +1,36 @@ +/** Use Cases 섹션 — DEMO UseCases */ + +export type UseCaseCard = { + title: string; + items: string[]; + cardClass: string; + iconClass: string; + animClass: string; +}; + +export const USE_CASE_CARDS: UseCaseCard[] = [ + { + title: "Premium Medical Business", + items: [ + "SEO 콘텐츠 자동 생산으로 검색 상위 노출 달성", + "비용 부담 없이 고품질 영상 콘텐츠 대량 확대", + "자연 검색 유입 증가로 인한 환자 전환율 상승", + ], + cardClass: + "glass-card p-10 md:p-12 bg-gradient-to-br from-status-info-bg/50 to-white border border-status-info-border/30", + iconClass: "text-status-info-dot", + animClass: "animate-fade-in-left animation-delay-100", + }, + { + title: "Marketing Agency", + items: [ + "AI 기반 콘텐츠 제작 자동화로 생산성 극대화", + "블로그 텍스트 기반 영상 제작 자동화로 리소스 절감", + "다수 클라이언트 계정의 통합 운영 및 효율화", + ], + cardClass: + "glass-card p-10 md:p-12 bg-gradient-to-br from-status-good-bg/50 to-white border border-status-good-border/30", + iconClass: "text-violet-500", + animClass: "animate-fade-in-right animation-delay-200", + }, +]; diff --git a/src/features/home/hooks/useAnalyze.ts b/src/features/home/hooks/useAnalyze.ts new file mode 100644 index 0000000..30476d9 --- /dev/null +++ b/src/features/home/hooks/useAnalyze.ts @@ -0,0 +1,15 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +export function useAnalyze() { + const [url, setUrl] = useState(""); + const navigate = useNavigate(); + + const handleAnalyze = () => { + if (url.trim()) { + navigate("/report/loading", { state: { url } }); + } + }; + + return { url, setUrl, handleAnalyze }; +} diff --git a/src/features/home/ui/CTASection.tsx b/src/features/home/ui/CTASection.tsx index 2dda8b6..1f125ff 100644 --- a/src/features/home/ui/CTASection.tsx +++ b/src/features/home/ui/CTASection.tsx @@ -1,35 +1,64 @@ +import ArrowRightIcon from "@/assets/home/arrow-right.svg?react"; +import { + CTA_BUTTON_LABEL, + CTA_DESCRIPTION, + CTA_FOOTNOTE, + CTA_HEADLINE, + CTA_URL_PLACEHOLDER, +} from "@/features/home/constants/cta_contents"; +import { useAnalyze } from "@/features/home/hooks/useAnalyze"; +import { useInView } from "@/hooks/useInView"; + export function CTASection() { - return ( -
+ const { ref, inView } = useInView(); + const { url, setUrl, handleAnalyze } = useAnalyze(); - {/* 배경 글로우 원 */} -
+ return ( +
+
+
- {/* 타이틀 */} -
-
- Ready to Transform Your Marketing? -
-
- URL 하나로 시작하는 완벽한 마케팅 자동화. 지금 바로 무료 진단을 받아보세요. -
-
+
+

+ {CTA_HEADLINE} +

- {/* 버튼 */} -
- - -
- 네이버 블로그, 플레이스, 소셜미디어 종합 분석 리포트 받아보기 -
-
+

+ {CTA_DESCRIPTION} +

+ +
+ setUrl(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleAnalyze()} + className="w-full px-8 py-4 body-16-medium bg-gradient-to-r from-marketing-cream via-marketing-lilac to-marketing-ice border border-white/20 rounded-full focus:outline-none focus:ring-2 focus:ring-white/50 shadow-sm text-center text-navy-900 placeholder:text-navy-900/60 break-keep" + /> + +

{CTA_FOOTNOTE}

- ) -} \ No newline at end of file +
+
+ ); +} diff --git a/src/features/home/ui/HeroSection.tsx b/src/features/home/ui/HeroSection.tsx index e50be24..752acef 100644 --- a/src/features/home/ui/HeroSection.tsx +++ b/src/features/home/ui/HeroSection.tsx @@ -1,55 +1,78 @@ -import bg from "@/assets/home/bg_hero.png"; -import twinkle from "@/assets/home/twinkle.svg"; +import InfinityLoopIcon from "@/assets/home/infinity-loop.svg?react"; +import ArrowRightIcon from "@/assets/home/arrow-right.svg?react"; +import { + HERO_BADGE_TEXT, + HERO_CTA_BUTTON_LABEL, + HERO_FOOTNOTE, + HERO_LEAD_EN, + HERO_LEAD_KO, + HERO_URL_PLACEHOLDER, +} from "@/features/home/constants/hero_contents"; +import { useAnalyze } from "@/features/home/hooks/useAnalyze"; export function HeroSection() { - return ( -
-
+ const { url, setUrl, handleAnalyze } = useAnalyze(); - {/* 배지 */} -
- twinkle -
- Infinite Marketing for Premium Medical Business & Marketing Agency -
-
+ return ( +
- {/* 타이틀 */} -
-
- Marketing becomes a -
-
- self-improving engine. -
-
+ {/* 배경 그라디언트 */} +
- {/* 설명 */} -
-

AI가 콘텐츠를 만들고 데이터가 마케팅을 개선합니다.

-

콘텐츠 기획, 생성, 영상 제작, 채널 배포, 데이터 분석을 하나의 자동화 엔진으로.

-
+
- {/* 버튼 */} -
- - -
- 네이버 블로그, 플레이스, 소셜미디어 등 Online Presence 종합 분석 리포트를 제공합니다. -
-
- -
+ {/* 배지 */} +
+
- ) -} \ No newline at end of file + + {/* 제목 */} +

+ + Infinite + {" "} + Growth +
+ Marketing Engine. +

+ + {/* 설명 */} +

+ {HERO_LEAD_EN} +
+ {HERO_LEAD_KO} +

+ + {/* CTA */} +
+
+ setUrl(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleAnalyze()} + className="w-full px-8 py-5 body-16-medium bg-white/80 backdrop-blur-sm border border-neutral-30 rounded-2xl focus:outline-none focus:ring-2 focus:ring-violet-500/20 focus:border-violet-500/40 shadow-sm text-center text-navy-900 placeholder:text-neutral-60 transition-all group-hover:border-neutral-40 break-keep" + /> +
+ +

{HERO_FOOTNOTE}

+
+ +
+ + {/* 블롭 데코레이션 */} +
+
+
+ +
+ ); +} diff --git a/src/features/home/ui/ProblemSection.tsx b/src/features/home/ui/ProblemSection.tsx index 906c09f..f8e86e0 100644 --- a/src/features/home/ui/ProblemSection.tsx +++ b/src/features/home/ui/ProblemSection.tsx @@ -1,48 +1,45 @@ -const PROBLEMS = [ - { - title: "콘텐츠 생산의 한계", - description: "블로그, SEO, 유튜브, 숏폼 등 지속적 생산이 필요하지만 인력과 비용, 시간 부족으로 제한됩니다.", - gradient: false, - }, - { - title: "영상 콘텐츠 제작 비용", - description: "영상은 중요하지만 촬영, 편집, 기획 비용이 높습니다. 특히 숏폼 콘텐츠는 지속적인 제작이 어렵습니다.", - gradient: true, - }, - { - title: "데이터 기반의 마케팅 부족", - description: "어떤 콘텐츠가 효과적인지, 어떤 키워드가 유입을 만드는지, 어떤 채널이 성과가 좋은지 알기 어렵고, 각 플랫폼들의 데이터들을 한눈에 파악할 수 없습니다.", - gradient: false, - }, -] +import { PROBLEM_CARDS, PROBLEM_CARD_STAGGER } from "@/features/home/constants/problem_contents"; +import { useInView } from "@/hooks/useInView"; export function ProblemSection() { - return ( -
-
-
- Premium Medical Marketing is Hard -
-
- 병원 마케팅이 직면한 3가지 핵심 과제 -
-
+ const { ref, inView } = useInView(); -
- {PROBLEMS.map(({ title, description, gradient }, index) => ( -
-
- {title} -
-
- {description} -
-
- ))} -
+ return ( +
+
+ +
+

+ Premium Medical Marketing is Hard +

+

+ 병원 마케팅이 직면한 3가지 핵심 과제 +

- ) -} \ No newline at end of file + +
+ {PROBLEM_CARDS.map((problem, idx) => ( +
+

+ {problem.title} +

+

{problem.desc}

+
+ ))} +
+ +
+
+ ); +} diff --git a/src/features/home/ui/ProcessSection.tsx b/src/features/home/ui/ProcessSection.tsx index 5397b47..d57e079 100644 --- a/src/features/home/ui/ProcessSection.tsx +++ b/src/features/home/ui/ProcessSection.tsx @@ -1,121 +1,20 @@ -import twinkle_pink from "@/assets/home/twinkle_pink.svg"; - -const AGDP_ITEMS = [ - { letter: "G", label: "Generation", angle: 0 }, - { letter: "D", label: "Distribution", angle: 90 }, - { letter: "P", label: "Performance", angle: 180 }, - { letter: "A", label: "Analysis", angle: 270 }, -] - -const REWARD_SIGNAL_CONFIG = { - text: "← REWARD SIGNAL", - startAngle: 158, - endAngle: 110, - radius: 200, - cx: 300, - cy: 300, -} - -function RewardSignalText() { - const { text, startAngle, endAngle, radius, cx, cy } = REWARD_SIGNAL_CONFIG - const chars = text.split("") - const angleStep = (endAngle - startAngle) / (chars.length - 1) - - return ( - - {chars.map((char, i) => { - const angle = (startAngle + i * angleStep) * (Math.PI / 180) - return ( - - {char} - - ) - })} - - ) -} +import { AgdpCycleSummaryCard } from "@/features/home/ui/process/AgdpCycleSummaryCard"; +import { AgdpEngineDiagram } from "@/features/home/ui/process/AgdpEngineDiagram"; +import { MarketingEngineIntro } from "@/features/home/ui/process/MarketingEngineIntro"; +import { useInView } from "@/hooks/useInView"; export function ProcessSection() { - return ( -
+ const { ref, inView } = useInView(); - {/* 배경 글로우 원 */} -
+ return ( +
+
-
- - {/* 배지 */} -
- twinkle -
AI Marketing Engine
-
- - {/* 타이틀 */} -
- Infinite Marketing Engine -
- - {/* 설명 */} -
- - Infinite Marketing for Premium Medical Business & Marketing Agency - - - Infinite Marketing은 Premium Medical Business와 Marketing Agency를 위한 AI Marketing Automation Platform입니다. - CLINICAD는 마케팅 분석, 콘텐츠 생성, 영상 콘텐츠 제작, 채널 배포, 성과 분석과 피드백 적용을 하나의 Self-Improving Marketing Engine으로 제공합니다. - -
- - {/* 원 레이어 */} -
-
- - - - {/* 중앙 원 */} -
- AGDP - - Infinite
Marketing -
-
- - {/* 위성 원 */} - {AGDP_ITEMS.map(({ letter, label, angle }) => ( -
-
- {letter} -
- {label} -
- ))} -
- - {/* AGDP Cycle 설명 */} -
- AGDP Cycle:   -
- 분석(Analysis) → 생성(Generation) → 배포(Distribution) → 성과(Performance)의 무한 루프를 통해 콘텐츠 품질(CTR)을 - 자율 최적화 - 합니다. -
-
-
-
- ) -} \ No newline at end of file +
+ + + +
+
+ ); +} diff --git a/src/features/home/ui/SolutionSection.tsx b/src/features/home/ui/SolutionSection.tsx index 9e35bed..476dbeb 100644 --- a/src/features/home/ui/SolutionSection.tsx +++ b/src/features/home/ui/SolutionSection.tsx @@ -1,55 +1,47 @@ -const SOLUTION_CARDS = [ - { - title: "Premium Medical Business", - description: "고객 LTV가 높고 브랜드 경쟁이 심해 콘텐츠 마케팅이 필수적인 프리미엄 의료 서비스 제공 병원", - items: ["피부과", "성형외과", "치과", "안과", "헬스케어 클리닉", "피트니스"], - gradient: "from-[#F8FAFC]", - }, - { - title: "Medical Marketing Agency", - description: "콘텐츠 제작 비용 부담과 인력 의존도가 높고 영상 제작 생산성 개선이 필요한 병원 마케팅 대행사", - items: ["병원 마케팅 대행사", "콘텐츠 마케팅 Agency", "영상 마케팅 Agency", "광고 운영 대행사"], - gradient: "from-[#FAF5FF]", - }, -] +import { SOLUTION_CARDS } from "@/features/home/constants/solution_contents"; +import { useInView } from "@/hooks/useInView"; export function SolutionSection() { - return ( -
-
-
-
- Who is Infinite Marketing for -
-
프리미엄 의료 서비스와 전문 마케팅 에이전시를 위한 최적의 솔루션
-
+ const { ref, inView } = useInView(); -
- {SOLUTION_CARDS.map(({ title, description, items, gradient }, index) => ( -
-
- {title} -
-
- {description} -
-
- {items.map((item, i) => ( -
- {item} -
- ))} -
-
- ))} -
-
+ return ( +
+
+ +
+

+ Who is Infinite Marketing for +

+

+ 프리미엄 의료 서비스와 전문 마케팅 에이전시를 위한 최적의 솔루션 +

- ) -} \ No newline at end of file + +
+ {SOLUTION_CARDS.map(({ title, description, items, cardClass, tagClass, animClass }) => ( +
+

{title}

+

{description}

+
+ {items.map((item) => ( + + {item} + + ))} +
+
+ ))} +
+ +
+
+ ); +} diff --git a/src/features/home/ui/SystemSection.tsx b/src/features/home/ui/SystemSection.tsx index babc9bb..2f027cb 100644 --- a/src/features/home/ui/SystemSection.tsx +++ b/src/features/home/ui/SystemSection.tsx @@ -1,56 +1,62 @@ -import bg from "@/assets/home/bg_system.png"; - -const CORE_MODULES = [ - { index: 1, title: "Marketing Intelligence", description: ["브랜딩, 마케팅 현황 분석", "타겟 고객 분석", "키워드 분석", "경쟁 및 포지셔닝 분석"], footer: "SEO 전략 & 채널별 콘텐츠 기획", angle: 0 }, - { index: 2, title: "Content Creation", description: ["블로그 콘텐츠 생성", "SEO 콘텐츠 생성", "SNS 콘텐츠 생성", "마케팅 카피 생성"], footer: "Human-in-the loop 프로세스", angle: 78 }, - { index: 3, title: "Video Automation", description: ["블로그 → 영상 변환", "숏폼 콘텐츠 생성", "유튜브 콘텐츠 제작", "SNS 영상 제작"], footer: "멀티모달 AI 엔진: 영상 + 음악 + 카피", angle: 144 }, - { index: 4, title: "Distribution Engine", description: ["블로그 게시", "SNS 자동 게시", "유튜브 업로드", "콘텐츠 일정 관리"], footer: "SEO, AEO 자동 최적화", angle: 216 }, - { index: 5, title: "Performance Intelligence", description: ["SEO 성과 분석", "콘텐츠 성과 분석", "채널 성과 분석", "AI 콘텐츠 개선 전략 추천"], footer: "데이터 기반 효과 검증", angle: 283 }, -] +import { CORE_MODULES, MODULE_CARD_STAGGER } from "@/features/home/constants/modules_contents"; +import { useInView } from "@/hooks/useInView"; +import { CoreModuleCard } from "./system/CoreModuleCard"; +import { CoreModulesCenterHeading } from "./system/CoreModulesCenterHeading"; export function SystemSection() { - return ( -
-
-
- Core Modules -
-
- 성능 개선 반영 자율 순환 마케팅 시스템 -
-
+ const { ref, inView } = useInView(); -
-
- Self-Improving
Growth Engine -
+ return ( +
+
+
+
- {CORE_MODULES.map(({ index, title, description, footer, angle }) => ( -
-
-
- {index} -
-
- {title} -
-
- -
- {description.map((desc) => ( -
{desc}
- ))} -
- -
-
{footer}
-
- ))} -
+
+
+

Core Modules

+

+ 성능 개선 반영 자율 순환 마케팅 시스템 +

- ) -} \ No newline at end of file + +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ {CORE_MODULES.map((mod, idx) => ( + + ))} +
+
+
+
+ ); +} diff --git a/src/features/home/ui/UseCaseSection.tsx b/src/features/home/ui/UseCaseSection.tsx index a3c7157..3dd69f6 100644 --- a/src/features/home/ui/UseCaseSection.tsx +++ b/src/features/home/ui/UseCaseSection.tsx @@ -1,56 +1,44 @@ -const CheckIcon = ({ fill }: { fill: string }) => ( - - - - -) - -const USE_CASES = [ - { - title: "Premium Medical Business", - descriptions: ["SEO 콘텐츠 자동 생산으로 검색 상위 노출 달성", "비용 부담 없이 고품질 영상 콘텐츠 대량 확대", "자연 검색 유입 증가로 인한 환자 전환율 상승"], - color: "#2B7FFF", - gradient: "from-[#EFF6FF]", - }, - { - title: "Marketing Agency", - descriptions: ["AI 기반 콘텐츠 제작 자동화로 생산성 극대화", "블로그 텍스트 기반 영상 제작 자동화로 리소스 절감", "다수 클라이언트 계정의 통합 운영 및 효율화"], - color: "#AD46FF", - gradient: "from-[#FAF5FF]", - }, -] +import CheckCircleIcon from "@/assets/home/check-circle.svg?react"; +import { USE_CASE_CARDS } from "@/features/home/constants/use_cases_contents"; +import { useInView } from "@/hooks/useInView"; export function UseCaseSection() { - return ( -
-
-
- Use Cases -
-
- Infinite Marketing이 만드는 실질적인 변화를 확인해보세요! -
-
+ const { ref, inView } = useInView(); -
- {USE_CASES.map(({ title, descriptions, color, gradient }, index) => ( -
-
- {title} -
-
- {descriptions.map((desc, i) => ( -
- {desc} -
- ))} -
-
- ))} -
+ return ( +
+
+
+

Use Cases

+

+ Infinite Marketing이 만드는 실질적인 변화를 확인해보세요! +

- ) -} \ No newline at end of file + +
+ {USE_CASE_CARDS.map((c) => ( +
+

{c.title}

+
    + {c.items.map((item) => ( +
  • +
  • + ))} +
+
+ ))} +
+
+
+ ); +} diff --git a/src/features/home/ui/process/AgdpCycleSummaryCard.tsx b/src/features/home/ui/process/AgdpCycleSummaryCard.tsx new file mode 100644 index 0000000..75f7171 --- /dev/null +++ b/src/features/home/ui/process/AgdpCycleSummaryCard.tsx @@ -0,0 +1,22 @@ +type Props = { visible: boolean }; + +export function AgdpCycleSummaryCard({ visible }: Props) { + return ( +
+
+

+ AGDP Cycle:{" "} + + 분석(Analysis) → 생성(Generation) → 배포(Distribution) → 성과(Performance)의 무한 루프를 통해 콘텐츠 품질(CTR)을{" "} + + 자율 최적화 + 합니다. +

+
+
+ ); +} diff --git a/src/features/home/ui/process/AgdpEngineDiagram.tsx b/src/features/home/ui/process/AgdpEngineDiagram.tsx new file mode 100644 index 0000000..01abb4d --- /dev/null +++ b/src/features/home/ui/process/AgdpEngineDiagram.tsx @@ -0,0 +1,55 @@ +import { AGDP_NODES } from "@/features/home/constants/process_contents"; +import { AgdpOrbitNode } from "./AgdpOrbitNode"; +import { AgdpRewardPathLabel } from "./AgdpRewardPathLabel"; + +type Props = { visible: boolean }; + +export function AgdpEngineDiagram({ visible }: Props) { + return ( +
+
+ + + +
+
+ + AGDP + +

+ Infinite +
+ Marketing +

+
+
+ + {AGDP_NODES.map((node) => ( + + ))} + + +
+ ); +} diff --git a/src/features/home/ui/process/AgdpOrbitNode.tsx b/src/features/home/ui/process/AgdpOrbitNode.tsx new file mode 100644 index 0000000..eb139d0 --- /dev/null +++ b/src/features/home/ui/process/AgdpOrbitNode.tsx @@ -0,0 +1,21 @@ +import type { AgdpNodeDef } from "@/features/home/constants/process_contents"; +import { AGDP_SLOT_WRAPPER_CLASS } from "@/features/home/constants/process_contents"; + +type Props = { node: AgdpNodeDef }; + +export function AgdpOrbitNode({ node }: Props) { + const wrapperClass = AGDP_SLOT_WRAPPER_CLASS[node.slot]; + + return ( +
+
+ {node.letter} +
+
+ + {node.label} + +
+
+ ); +} diff --git a/src/features/home/ui/process/AgdpRewardPathLabel.tsx b/src/features/home/ui/process/AgdpRewardPathLabel.tsx new file mode 100644 index 0000000..425d65d --- /dev/null +++ b/src/features/home/ui/process/AgdpRewardPathLabel.tsx @@ -0,0 +1,17 @@ +/** P→A 호 SVG 경로 위 "Reward" 라벨 (DEMO Solution.tsx) */ +export function AgdpRewardPathLabel() { + const pathId = "agdp-reward-path"; + + return ( + + ); +} diff --git a/src/features/home/ui/process/MarketingEngineIntro.tsx b/src/features/home/ui/process/MarketingEngineIntro.tsx new file mode 100644 index 0000000..71af2b6 --- /dev/null +++ b/src/features/home/ui/process/MarketingEngineIntro.tsx @@ -0,0 +1,40 @@ +import twinklePink from "@/assets/home/twinkle_pink.svg"; + +type Props = { visible: boolean }; + +export function MarketingEngineIntro({ visible }: Props) { + return ( + <> +
+ + AI Marketing Engine +
+ +

+ Infinite Marketing Engine +

+ +

+ + Infinite Marketing for Premium Medical Business & Marketing Agency + +
+ Infinite Marketing은 Premium Medical Business와 Marketing Agency를 위한 AI Marketing Automation Platform입니다. + INFINITH는 마케팅 분석, 콘텐츠 생성, 영상 콘텐츠 제작, 채널 배포, 성과 분석과 피드백 적용을 하나의 Self-Improving Marketing + Engine으로 제공합니다. +

+ + ); +} diff --git a/src/features/home/ui/system/CoreModuleCard.tsx b/src/features/home/ui/system/CoreModuleCard.tsx new file mode 100644 index 0000000..c0dc52a --- /dev/null +++ b/src/features/home/ui/system/CoreModuleCard.tsx @@ -0,0 +1,37 @@ +import type { CoreModule } from "@/features/home/constants/modules_contents"; + +type Props = { + mod: CoreModule; + className?: string; + visible: boolean; + delayClass?: string; +}; + +export function CoreModuleCard({ mod, className = "", visible, delayClass = "" }: Props) { + return ( +
+
+
+ {mod.step} +
+

{mod.title}

+
+ +
    + {mod.items.map((item) => ( +
  • + {item} +
  • + ))} +
+ +
+ {mod.highlight} +
+
+ ); +} diff --git a/src/features/home/ui/system/CoreModulesCenterHeading.tsx b/src/features/home/ui/system/CoreModulesCenterHeading.tsx new file mode 100644 index 0000000..ac3d088 --- /dev/null +++ b/src/features/home/ui/system/CoreModulesCenterHeading.tsx @@ -0,0 +1,20 @@ +type Props = { visible: boolean }; + +/** Core Modules 섹션 중앙 — Self-Improving Growth Engine 타이틀 */ +export function CoreModulesCenterHeading({ visible }: Props) { + return ( +
+
+

+ Self-Improving +
+ Growth Engine +

+
+
+ ); +}