commit
af58b96aff
|
|
@ -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",
|
||||
|
|
@ -1115,9 +1139,6 @@
|
|||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1132,9 +1153,6 @@
|
|||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1149,9 +1167,6 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1166,9 +1181,6 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1183,9 +1195,6 @@
|
|||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1200,9 +1209,6 @@
|
|||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1217,9 +1223,6 @@
|
|||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1234,9 +1237,6 @@
|
|||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1251,9 +1251,6 @@
|
|||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1268,9 +1265,6 @@
|
|||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1285,9 +1279,6 @@
|
|||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1302,9 +1293,6 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1319,9 +1307,6 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1412,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",
|
||||
|
|
@ -1571,9 +1781,6 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1595,9 +1802,6 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1619,9 +1823,6 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1643,9 +1844,6 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1818,9 +2016,6 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1838,9 +2033,6 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1858,9 +2050,6 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1878,9 +2067,6 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -2283,9 +2469,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 +2687,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": {
|
||||
|
|
@ -2568,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",
|
||||
|
|
@ -2665,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",
|
||||
|
|
@ -2731,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",
|
||||
|
|
@ -2766,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",
|
||||
|
|
@ -3050,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",
|
||||
|
|
@ -3144,9 +3411,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"
|
||||
},
|
||||
|
|
@ -3412,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",
|
||||
|
|
@ -3492,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",
|
||||
|
|
@ -3543,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",
|
||||
|
|
@ -3566,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",
|
||||
|
|
@ -3662,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",
|
||||
|
|
@ -3732,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",
|
||||
|
|
@ -3752,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",
|
||||
|
|
@ -3760,9 +4098,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": {
|
||||
|
|
@ -3996,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",
|
||||
|
|
@ -4032,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",
|
||||
|
|
@ -4104,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",
|
||||
|
|
@ -4278,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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
/* ─── 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;
|
||||
|
||||
--font-family-primary: 'Pretendard', system-ui, sans-serif;
|
||||
--font-family-display: 'Playfair Display', serif;
|
||||
--font-family-inter: 'Inter', sans-serif;
|
||||
/* 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;
|
||||
/* ─── 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 10h12M12 6l4 4-4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 227 B |
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" />
|
||||
<path d="M9 12l2 2 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 312 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 12L6 8l4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 220 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 4l4 4-4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 218 B |
|
|
@ -0,0 +1,16 @@
|
|||
<svg width="28" height="20" viewBox="0 0 28 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="inf-grad" x1="0" y1="0" x2="28" y2="20" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="currentColor" stop-opacity="0.9"/>
|
||||
<stop offset="45%" stop-color="currentColor" stop-opacity="0.55"/>
|
||||
<stop offset="100%" stop-color="currentColor" stop-opacity="0.85"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M2,10 C2,4 8,4 14,10 C20,16 26,16 26,10 C26,4 20,4 14,10 C8,16 2,16 2,10Z"
|
||||
stroke="url(#inf-grad)"
|
||||
stroke-width="2.8"
|
||||
stroke-linejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 651 B |
|
|
@ -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 = "네이버 블로그, 플레이스, 소셜미디어 종합 분석 리포트 받아보기";
|
||||
|
|
@ -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 마케팅 엔진. 콘텐츠 기획, 생성, 영상 제작, 채널 배포, 데이터 분석까지 하나로.";
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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<AgdpSlot, string> = {
|
||||
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",
|
||||
};
|
||||
|
|
@ -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",
|
||||
},
|
||||
];
|
||||
|
|
@ -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",
|
||||
},
|
||||
];
|
||||
|
|
@ -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 };
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className="relative flex flex-col items-center justify-center py-[96px] h-[512px] bg-[#0A1128] overflow-hidden">
|
||||
const { ref, inView } = useInView<HTMLElement>();
|
||||
const { url, setUrl, handleAnalyze } = useAnalyze();
|
||||
|
||||
{/* 배경 글로우 원 */}
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full bg-[rgba(173,70,255,0.20)] blur-[50px] pointer-events-none" />
|
||||
return (
|
||||
<section ref={ref} className="py-20 md:py-24 bg-navy-900 text-white px-6 relative overflow-hidden">
|
||||
<div className="absolute inset-0 -z-10 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-navy-800 via-navy-900 to-navy-900 opacity-80" />
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] bg-violet-500/20 rounded-full blur-[100px] pointer-events-none" />
|
||||
|
||||
{/* 타이틀 */}
|
||||
<div className="relative flex flex-col items-center gap-4 pb-16">
|
||||
<div
|
||||
className="bg-gradient-to-r from-[#FFF3EB] via-[#E4CFFF] to-[#F5F9FF] bg-clip-text text-transparent text-[48px] font-bold leading-[48px]"
|
||||
style={{ fontFamily: "var(--font-family-display)" }}
|
||||
>
|
||||
Ready to Transform Your Marketing?
|
||||
</div>
|
||||
<div className="text-[#E9D4FF] text-[18px] leading-[29.25px] break-keep">
|
||||
URL 하나로 시작하는 완벽한 마케팅 자동화. 지금 바로 무료 진단을 받아보세요.
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-4xl mx-auto text-center relative z-10">
|
||||
<h2
|
||||
className={`display-36 md:display-48 mb-4 leading-tight text-transparent bg-clip-text bg-gradient-to-r from-marketing-cream via-marketing-lilac to-marketing-ice break-keep ${
|
||||
inView ? "animate-fade-in-up" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
{CTA_HEADLINE}
|
||||
</h2>
|
||||
|
||||
{/* 버튼 */}
|
||||
<div className="relative flex flex-col gap-4 w-[448px]">
|
||||
<button className="flex items-center justify-center w-full h-[56px] rounded-full border border-white/20 bg-gradient-to-r from-white via-[#E4CFFF] to-[#F5F9FF] shadow text-[#0A1128]/60 text-base font-medium cursor-pointer">
|
||||
Enter Your URL
|
||||
</button>
|
||||
<button className="flex items-center justify-center w-full h-[56px] bg-gradient-to-r from-[#4F1DA1] to-[#021341] rounded-[999px] text-white text-base font-medium leading-6 cursor-pointer">
|
||||
Analyze ➜
|
||||
</button>
|
||||
<div className="text-[#E9D4FF]/80 text-center text-sm leading-5">
|
||||
네이버 블로그, 플레이스, 소셜미디어 종합 분석 리포트 받아보기
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
className={`body-18-md-20 font-light text-lavender-200 mb-10 max-w-2xl mx-auto break-keep ${
|
||||
inView ? "animate-fade-in-up animation-delay-100" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
{CTA_DESCRIPTION}
|
||||
</p>
|
||||
|
||||
<div
|
||||
className={`flex flex-col items-center justify-center gap-4 max-w-md mx-auto ${
|
||||
inView ? "animate-fade-in-up animation-delay-200" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="url"
|
||||
placeholder={CTA_URL_PLACEHOLDER}
|
||||
value={url}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAnalyze}
|
||||
className="w-full px-10 py-4 body-18 font-medium text-white rounded-full transition-all shadow-xl hover:shadow-2xl flex items-center justify-center gap-2 group cursor-pointer bg-gradient-to-r from-violet-700 to-navy-950 hover:from-violet-hover hover:to-violet-hover break-keep"
|
||||
>
|
||||
{CTA_BUTTON_LABEL}
|
||||
<ArrowRightIcon aria-hidden="true" className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
||||
</button>
|
||||
<p className="label-12 text-lavender-200/80 mt-2 break-keep">{CTA_FOOTNOTE}</p>
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className="flex flex-col items-center justify-center py-[140px] bg-cover bg-center" style={{ backgroundImage: `url(${bg})` }}>
|
||||
<div className="flex flex-col items-center gap-6">
|
||||
const { url, setUrl, handleAnalyze } = useAnalyze();
|
||||
|
||||
{/* 배지 */}
|
||||
<div className="flex items-center gap-2 bg-transparent shadow rounded-[999px] px-4 py-2">
|
||||
<img src={twinkle} alt="twinkle" />
|
||||
<div className="text-[#314158] text-center text-xl font-medium leading-5">
|
||||
Infinite Marketing for Premium Medical Business & Marketing Agency
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<section className="relative pt-16 pb-12 md:pt-16 md:pb-16 overflow-hidden flex flex-col items-center justify-center text-center px-6">
|
||||
|
||||
{/* 타이틀 */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div
|
||||
className="text-[#0A1128] text-center text-[72px] font-bold leading-[79.2px] tracking-[-1.8px]"
|
||||
style={{ fontFamily: "var(--font-family-display)" }}
|
||||
>
|
||||
Marketing becomes a
|
||||
</div>
|
||||
<div
|
||||
className="bg-gradient-to-r from-[#0A1128] to-[#3B5998] bg-clip-text text-transparent text-[72px] font-bold leading-[96px] tracking-[-1.8px]"
|
||||
style={{ fontFamily: "var(--font-family-display)" }}
|
||||
>
|
||||
self-improving engine.
|
||||
</div>
|
||||
</div>
|
||||
{/* 배경 그라디언트 */}
|
||||
<div className="absolute inset-0 -z-10 opacity-70 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-hero-wash-start via-hero-wash-mid to-hero-wash-end" />
|
||||
|
||||
{/* 설명 */}
|
||||
<div className="flex flex-col items-center mb-4 text-[#45556C] text-center text-xl font-normal leading-[32.5px]">
|
||||
<p>AI가 콘텐츠를 만들고 데이터가 마케팅을 개선합니다.</p>
|
||||
<p>콘텐츠 기획, 생성, 영상 제작, 채널 배포, 데이터 분석을 하나의 자동화 엔진으로.</p>
|
||||
</div>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
|
||||
{/* 버튼 */}
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<button className="flex items-center justify-center w-full h-[56px] border border-[#E2E8F0] bg-white shadow rounded-[999px] text-[#90A1B9] text-base font-medium cursor-pointer hover:bg-[#F1F5F9] hover:text-[#64748B]">
|
||||
Enter Your URL
|
||||
</button>
|
||||
<button className="flex items-center justify-center w-full h-[56px] bg-gradient-to-r from-[#4F1DA1] to-[#021341] rounded-[999px] text-white text-base font-medium leading-6 cursor-pointer">
|
||||
Analyze ➜
|
||||
</button>
|
||||
<div className="text-[#62748E] text-center text-lg font-normal leading-4">
|
||||
네이버 블로그, 플레이스, 소셜미디어 등 Online Presence 종합 분석 리포트를 제공합니다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/* 배지 */}
|
||||
<div className="animate-fade-in-up inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/60 backdrop-blur-sm border border-white/40 shadow-sm mb-8">
|
||||
<InfinityLoopIcon aria-hidden="true" className="text-violet-500 shrink-0" />
|
||||
<span className="body-14-medium text-neutral-80 break-keep">{HERO_BADGE_TEXT}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
{/* 제목 */}
|
||||
<h1 className="animate-fade-in-up animation-delay-100 display-48 md:display-72 tracking-[-0.02em] text-navy-900 mb-6 break-keep">
|
||||
<span className="tracking-[0.05em]">
|
||||
In<span className="ml-[-0.04em]">f</span><span className="ml-[-0.04em]">inite</span>
|
||||
</span>{" "}
|
||||
Growth
|
||||
<br className="hidden md:block" />
|
||||
<span className="text-gradient">Marketing Engine.</span>
|
||||
</h1>
|
||||
|
||||
{/* 설명 */}
|
||||
<p className="animate-fade-in-up animation-delay-200 body-18-md-20 text-neutral-70 mb-10 max-w-3xl mx-auto leading-relaxed break-keep">
|
||||
{HERO_LEAD_EN}
|
||||
<br className="hidden md:block" />
|
||||
{HERO_LEAD_KO}
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="animate-fade-in-up animation-delay-300 flex flex-col items-center justify-center gap-5 max-w-lg mx-auto w-full">
|
||||
<div className="relative w-full group">
|
||||
<input
|
||||
type="url"
|
||||
placeholder={HERO_URL_PLACEHOLDER}
|
||||
value={url}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleAnalyze}
|
||||
className="w-full px-8 py-5 body-16-medium text-white rounded-2xl bg-gradient-to-r from-violet-700 to-navy-950 shadow-xl hover:shadow-2xl hover:scale-[1.02] active:scale-[0.98] flex items-center justify-center gap-3 group transition-all cursor-pointer break-keep"
|
||||
>
|
||||
{HERO_CTA_BUTTON_LABEL}
|
||||
<ArrowRightIcon aria-hidden="true" className="group-hover:translate-x-1 transition-transform" />
|
||||
</button>
|
||||
<p className="label-12 text-neutral-70 mt-2 break-keep">{HERO_FOOTNOTE}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* 블롭 데코레이션 */}
|
||||
<div className="absolute top-1/4 left-10 w-64 h-64 bg-lavender-300 rounded-full mix-blend-multiply filter blur-3xl opacity-30 animate-blob pointer-events-none" />
|
||||
<div className="absolute top-1/3 right-10 w-64 h-64 bg-marketing-blush rounded-full mix-blend-multiply filter blur-3xl opacity-30 animate-blob animation-delay-2000 pointer-events-none" />
|
||||
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 w-64 h-64 bg-hero-wash-start rounded-full mix-blend-multiply filter blur-3xl opacity-30 animate-blob animation-delay-4000 pointer-events-none" />
|
||||
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className="flex flex-col items-center justify-center py-[96px] bg-[#F8FAFC]">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div
|
||||
className="text-[#0A1128] text-center text-[48px] font-bold leading-[48px]"
|
||||
style={{ fontFamily: "var(--font-family-display)" }}
|
||||
>
|
||||
Premium Medical Marketing is Hard
|
||||
</div>
|
||||
<div className="text-[#45556C] text-[18px] leading-[29.25px]">
|
||||
병원 마케팅이 직면한 3가지 핵심 과제
|
||||
</div>
|
||||
</div>
|
||||
const { ref, inView } = useInView<HTMLElement>();
|
||||
|
||||
<div className="flex gap-6">
|
||||
{PROBLEMS.map(({ title, description, gradient }, index) => (
|
||||
<div key={index} className="flex flex-col w-[368px] h-[227px] gap-4 rounded-2xl bg-white shadow items-start justify-center px-10 py-12">
|
||||
<div className={gradient ? "bg-gradient-to-r from-[#4F39F6] to-[#9810FA] bg-clip-text text-transparent text-[24px] font-bold leading-[32px]" : "text-[#0A1128] text-[24px] font-bold leading-[32px]"}>
|
||||
{title}
|
||||
</div>
|
||||
<div className="text-[#45556C] text-[16px] leading-[20px] break-keep">
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
return (
|
||||
<section id="problem" ref={ref} className="py-24 bg-neutral-10 px-6 relative overflow-hidden">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
|
||||
<div className={`text-center mb-16 ${inView ? "animate-fade-in-up" : "opacity-0"}`}>
|
||||
<h2 className="headline-30 md:display-48 text-navy-900 mb-4 break-keep">
|
||||
Premium Medical Marketing is Hard
|
||||
</h2>
|
||||
<p className="body-18 text-neutral-70 max-w-2xl mx-auto break-keep">
|
||||
병원 마케팅이 직면한 3가지 핵심 과제
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{PROBLEM_CARDS.map((problem, idx) => (
|
||||
<div
|
||||
key={problem.title}
|
||||
className={`bg-white rounded-2xl p-8 md:p-10 border border-neutral-20 shadow-sm hover:shadow-md transition-shadow flex flex-col justify-center ${
|
||||
inView ? `animate-fade-in-up ${PROBLEM_CARD_STAGGER[idx]}` : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<h3
|
||||
className={`font-sans headline-24 mb-4 break-keep ${
|
||||
problem.highlight
|
||||
? "text-transparent bg-clip-text bg-gradient-to-r from-violet-700 to-violet-500"
|
||||
: "text-navy-900"
|
||||
}`}
|
||||
>
|
||||
{problem.title}
|
||||
</h3>
|
||||
<p className="body-16 text-neutral-70 leading-relaxed break-keep">{problem.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<svg className="absolute w-full h-full" viewBox="0 0 600 600">
|
||||
{chars.map((char, i) => {
|
||||
const angle = (startAngle + i * angleStep) * (Math.PI / 180)
|
||||
return (
|
||||
<text
|
||||
key={i}
|
||||
x={cx + radius * Math.cos(angle)}
|
||||
y={cy + radius * Math.sin(angle)}
|
||||
fontSize="11"
|
||||
fill="rgba(255,255,255,0.4)"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
{char}
|
||||
</text>
|
||||
)
|
||||
})}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
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 (
|
||||
<div className="relative flex flex-col items-center justify-center py-[128px] bg-[#0A1128] overflow-hidden">
|
||||
const { ref, inView } = useInView<HTMLElement>();
|
||||
|
||||
{/* 배경 글로우 원 */}
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] rounded-full bg-[rgba(173,70,255,0.20)] blur-[50px] pointer-events-none" />
|
||||
return (
|
||||
<section id="solution" ref={ref} className="py-32 bg-navy-900 text-white px-6 relative overflow-hidden">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] bg-violet-500/20 rounded-full blur-[120px] pointer-events-none" />
|
||||
|
||||
<div className="relative flex flex-col items-center">
|
||||
|
||||
{/* 배지 */}
|
||||
<div className="flex mb-8 items-center gap-2 border border-white/20 bg-white/10 backdrop-blur-[4px] rounded-[999px] px-4 py-2">
|
||||
<img src={twinkle_pink} alt="twinkle" />
|
||||
<div className="text-[#F3E8FF] text-center text-xl font-medium leading-5">AI Marketing Engine</div>
|
||||
</div>
|
||||
|
||||
{/* 타이틀 */}
|
||||
<div
|
||||
className="bg-gradient-to-r from-[#FFF3EB] via-[#E4CFFF] to-[#F5F9FF] bg-clip-text text-transparent text-[60px] font-bold leading-[75px] mb-6"
|
||||
style={{ fontFamily: "var(--font-family-display)" }}
|
||||
>
|
||||
Infinite Marketing Engine
|
||||
</div>
|
||||
|
||||
{/* 설명 */}
|
||||
<div className="flex flex-col items-center gap-1 w-[900px]" style={{ fontFamily: "var(--font-family-inter)" }}>
|
||||
<span className="text-white text-2xl leading-[39px]">
|
||||
Infinite Marketing for Premium Medical Business & Marketing Agency
|
||||
</span>
|
||||
<span className="text-[#CAD5E2] text-2xl font-light leading-[39px] break-keep">
|
||||
Infinite Marketing은 Premium Medical Business와 Marketing Agency를 위한 AI Marketing Automation Platform입니다.
|
||||
CLINICAD는 마케팅 분석, 콘텐츠 생성, 영상 콘텐츠 제작, 채널 배포, 성과 분석과 피드백 적용을 하나의 Self-Improving Marketing Engine으로 제공합니다.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 원 레이어 */}
|
||||
<div className="relative w-[600px] h-[600px] flex items-center justify-center mt-10 mb-16">
|
||||
<div className="absolute w-[310px] h-[310px] rounded-full border border-dashed border-white/40" />
|
||||
|
||||
<RewardSignalText />
|
||||
|
||||
{/* 중앙 원 */}
|
||||
<div className="z-10 w-[224px] h-[224px] rounded-full bg-[#0D0A2E] border border-white/10 flex flex-col items-center justify-center gap-1">
|
||||
<span className="text-[#E9D4FF] text-2xl font-medium leading-[30px]">AGDP</span>
|
||||
<span className="text-white text-[30px] font-bold leading-[37.5px] text-center" style={{ fontFamily: "var(--font-family-display)" }}>
|
||||
Infinite<br />Marketing
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 위성 원 */}
|
||||
{AGDP_ITEMS.map(({ letter, label, angle }) => (
|
||||
<div
|
||||
key={letter}
|
||||
className="absolute flex flex-col items-center gap-2"
|
||||
style={{ transform: `rotate(${angle}deg) translateY(-230px) rotate(-${angle}deg)` }}
|
||||
>
|
||||
<div className="w-20 h-20 rounded-full flex items-center justify-center border border-[#AD46FF]/30 bg-[#0A1128]/80 shadow-[0_0_20px_0_rgba(168,85,247,0.15)] backdrop-blur-[4px] text-[#DAB2FF] text-[36px] font-bold leading-10">
|
||||
{letter}
|
||||
</div>
|
||||
<span className="text-[#E9D4FF] text-2xl font-medium leading-[30px]">{label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* AGDP Cycle 설명 */}
|
||||
<div className="flex max-w-[780px] px-4 py-6 rounded-2xl border border-white/10 bg-white/5 backdrop-blur-[4px]">
|
||||
<span className="text-[#DAB2FF] text-base font-bold leading-6 whitespace-nowrap">AGDP Cycle: </span>
|
||||
<div className="text-center text-base leading-6 break-keep">
|
||||
<span className="text-[#CAD5E2]">분석(Analysis) → 생성(Generation) → 배포(Distribution) → 성과(Performance)의 무한 루프를 통해 콘텐츠 품질(CTR)을 </span>
|
||||
<span className="text-white">자율 최적화</span>
|
||||
<span className="text-[#CAD5E2]">합니다.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
<div className="max-w-4xl mx-auto text-center relative z-10">
|
||||
<MarketingEngineIntro visible={inView} />
|
||||
<AgdpEngineDiagram visible={inView} />
|
||||
<AgdpCycleSummaryCard visible={inView} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className="flex flex-col items-center justify-center py-[96px]">
|
||||
<div className="flex flex-col gap-16">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div
|
||||
className="text-[#0A1128] text-center text-[48px] font-bold leading-[48px]"
|
||||
style={{ fontFamily: "var(--font-family-display)" }}
|
||||
>
|
||||
Who is Infinite Marketing for
|
||||
</div>
|
||||
<div>프리미엄 의료 서비스와 전문 마케팅 에이전시를 위한 최적의 솔루션</div>
|
||||
</div>
|
||||
const { ref, inView } = useInView<HTMLElement>();
|
||||
|
||||
<div className="flex gap-6">
|
||||
{SOLUTION_CARDS.map(({ title, description, items, gradient }, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex flex-col gap-6 p-[48px] w-[560px] rounded-2xl border border-white/20 bg-gradient-to-br ${gradient} to-white shadow-[0_8px_30px_0_rgba(0,0,0,0.04)] backdrop-blur-[6px]`}
|
||||
>
|
||||
<div className="text-[#0A1128] text-[48px] font-bold leading-[60px]" style={{ fontFamily: "var(--font-family-display)" }}>
|
||||
{title}
|
||||
</div>
|
||||
<div className="text-[#45556C] text-[18px] leading-[29.25px] break-keep">
|
||||
{description}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3 w-full pr-20">
|
||||
{items.map((item, i) => (
|
||||
<div key={i} className="flex items-center justify-center h-[50px] px-3 py-5 rounded-[16px] shadow whitespace-nowrap bg-white">
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<section ref={ref} className="py-24 bg-white px-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
|
||||
<div className={`text-center mb-16 ${inView ? "animate-fade-in-up" : "opacity-0"}`}>
|
||||
<h2 className="headline-30 md:display-48 text-navy-900 mb-4 break-keep">
|
||||
Who is Infinite Marketing for
|
||||
</h2>
|
||||
<p className="body-18 text-neutral-70 max-w-2xl mx-auto break-keep">
|
||||
프리미엄 의료 서비스와 전문 마케팅 에이전시를 위한 최적의 솔루션
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{SOLUTION_CARDS.map(({ title, description, items, cardClass, tagClass, animClass }) => (
|
||||
<div
|
||||
key={title}
|
||||
className={`p-10 md:p-12 rounded-3xl backdrop-blur-xl shadow-[0_8px_32px_0_rgba(31,38,135,0.07)] ${cardClass} ${
|
||||
inView ? animClass : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<h3 className="display-36 md:display-48 leading-tight text-navy-900 mb-6 break-keep">{title}</h3>
|
||||
<p className="body-18 text-neutral-70 mb-10 leading-relaxed break-keep">{description}</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{items.map((item) => (
|
||||
<span
|
||||
key={item}
|
||||
className={`body-14-medium bg-white px-5 py-3 rounded-2xl shadow-sm text-neutral-80 hover:shadow-md transition-shadow break-keep ${tagClass}`}
|
||||
>
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className="flex flex-col items-center justify-center py-[128px] bg-cover bg-center" style={{ backgroundImage: `url(${bg})` }}>
|
||||
<div className="flex flex-col items-center justify-center gap-6 mb-[96px]">
|
||||
<div className="text-[#0A1128] text-center text-[48px] font-bold leading-[48px]" style={{ fontFamily: "var(--font-family-display)" }}>
|
||||
Core Modules
|
||||
</div>
|
||||
<div className="text-[#45556C] text-[18px] font-normal leading-[29.25px]">
|
||||
성능 개선 반영 자율 순환 마케팅 시스템
|
||||
</div>
|
||||
</div>
|
||||
const { ref, inView } = useInView<HTMLElement>();
|
||||
|
||||
<div className="relative w-[1100px] h-[1100px] flex items-center justify-center">
|
||||
<div className="absolute z-10 text-[#1D293D] text-center text-[31px] font-bold leading-[38.75px] mb-30" style={{ fontFamily: "var(--font-family-display)" }}>
|
||||
Self-Improving<br />Growth Engine
|
||||
</div>
|
||||
return (
|
||||
<section id="modules" ref={ref} className="py-24 md:py-32 bg-white px-6 overflow-hidden relative">
|
||||
<div className="absolute top-[-10%] left-[-10%] w-[50vw] h-[50vw] min-w-[600px] min-h-[600px] rounded-full bg-marketing-cream opacity-80 blur-[120px] animate-blob-large pointer-events-none" />
|
||||
<div className="absolute top-[20%] right-[-10%] w-[40vw] h-[40vw] min-w-[500px] min-h-[500px] rounded-full bg-marketing-lilac opacity-40 blur-[120px] animate-blob-large animation-delay-7000 pointer-events-none" />
|
||||
<div className="absolute bottom-[-10%] left-[20%] w-[60vw] h-[60vw] min-w-[700px] min-h-[700px] rounded-full bg-marketing-ice opacity-80 blur-[120px] animate-blob-large animation-delay-14000 pointer-events-none" />
|
||||
|
||||
{CORE_MODULES.map(({ index, title, description, footer, angle }) => (
|
||||
<div
|
||||
key={index}
|
||||
className="absolute flex flex-col items-start p-[25px] rounded-[16px] bg-white shadow"
|
||||
style={{ transform: `rotate(${angle}deg) translateY(-430px) rotate(-${angle}deg)` }}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<div className="flex items-center justify-center w-10 h-10 rounded-[16px] bg-[#021341] text-white text-lg font-bold leading-7">
|
||||
{index}
|
||||
</div>
|
||||
<div className="text-[#1D293D] text-[24px] font-bold leading-[25px] tracking-[-0.5px]" style={{ fontFamily: "var(--font-family-display)" }}>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1 mb-6">
|
||||
{description.map((desc) => (
|
||||
<div key={desc} className="text-[#45556C] text-[18px] leading-[29.25px] break-keep">{desc}</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="bg-[#F1F5F9] w-full h-[1px] mb-4" />
|
||||
<div className="text-[#4F39F6] text-xl font-bold leading-[22.5px] tracking-[-0.375px] break-keep">{footer}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="max-w-7xl mx-auto relative z-10">
|
||||
<div className={`text-center mb-16 md:mb-24 ${inView ? "animate-fade-in-up" : "opacity-0"}`}>
|
||||
<h2 className="display-36 md:display-48 text-navy-900 mb-6 break-keep">Core Modules</h2>
|
||||
<p className="body-18-md-20 text-neutral-70 max-w-2xl mx-auto break-keep">
|
||||
성능 개선 반영 자율 순환 마케팅 시스템
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
<div className="hidden lg:block relative max-w-[1200px] mx-auto mt-10" style={{ height: "960px" }}>
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<CoreModulesCenterHeading visible={inView} />
|
||||
</div>
|
||||
|
||||
<div className="absolute left-1/2 -translate-x-1/2 top-0">
|
||||
<CoreModuleCard mod={CORE_MODULES[0]} className="w-[300px]" visible={inView} delayClass={MODULE_CARD_STAGGER[0]} />
|
||||
</div>
|
||||
<div className="absolute right-[20px] top-[130px]">
|
||||
<CoreModuleCard mod={CORE_MODULES[1]} className="w-[300px]" visible={inView} delayClass={MODULE_CARD_STAGGER[1]} />
|
||||
</div>
|
||||
<div className="absolute right-[80px] bottom-[30px]">
|
||||
<CoreModuleCard mod={CORE_MODULES[2]} className="w-[300px]" visible={inView} delayClass={MODULE_CARD_STAGGER[2]} />
|
||||
</div>
|
||||
<div className="absolute left-[80px] bottom-[30px]">
|
||||
<CoreModuleCard mod={CORE_MODULES[3]} className="w-[300px]" visible={inView} delayClass={MODULE_CARD_STAGGER[3]} />
|
||||
</div>
|
||||
<div className="absolute left-[20px] top-[130px]">
|
||||
<CoreModuleCard mod={CORE_MODULES[4]} className="w-[300px]" visible={inView} delayClass={MODULE_CARD_STAGGER[4]} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:hidden flex flex-col gap-8 items-center">
|
||||
<CoreModulesCenterHeading visible={inView} />
|
||||
<div className="w-full grid md:grid-cols-2 gap-6 mt-8">
|
||||
{CORE_MODULES.map((mod, idx) => (
|
||||
<CoreModuleCard
|
||||
key={mod.step}
|
||||
mod={mod}
|
||||
className="w-full"
|
||||
visible={inView}
|
||||
delayClass={MODULE_CARD_STAGGER[idx]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,56 +1,44 @@
|
|||
const CheckIcon = ({ fill }: { fill: string }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke={fill} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M9 12L11 14L15 10" stroke={fill} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
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 (
|
||||
<div className="flex flex-col items-center justify-center py-[96px] bg-white">
|
||||
<div className="flex flex-col items-center gap-4 pb-16">
|
||||
<div className="text-[#0A1128] text-center text-[48px] font-bold leading-[48px]" style={{ fontFamily: "var(--font-family-display)" }}>
|
||||
Use Cases
|
||||
</div>
|
||||
<div className="text-[#45556C] text-[18px] leading-[29.25px] break-keep">
|
||||
Infinite Marketing이 만드는 실질적인 변화를 확인해보세요!
|
||||
</div>
|
||||
</div>
|
||||
const { ref, inView } = useInView<HTMLElement>();
|
||||
|
||||
<div className="flex gap-6">
|
||||
{USE_CASES.map(({ title, descriptions, color, gradient }, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex flex-col w-[560px] h-[227px] p-10 items-start justify-center rounded-2xl border border-white/20 bg-gradient-to-br ${gradient} to-white shadow-[0_8px_30px_0_rgba(0,0,0,0.04)] backdrop-blur-[6px]`}
|
||||
>
|
||||
<div className="text-[#0A1128] text-[24px] font-bold leading-[32px] pb-[22.5px]" style={{ fontFamily: "var(--font-family-display)" }}>
|
||||
{title}
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
{descriptions.map((desc, i) => (
|
||||
<div key={i} className="flex items-center gap-2">
|
||||
<CheckIcon fill={color} /> {desc}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
return (
|
||||
<section id="use-cases" ref={ref} className="py-24 bg-white px-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className={`text-center mb-16 ${inView ? "animate-fade-in-up" : "opacity-0"}`}>
|
||||
<h2 className="headline-30 md:display-48 text-navy-900 mb-4 break-keep">Use Cases</h2>
|
||||
<p className="body-18 font-medium text-neutral-70 max-w-2xl mx-auto break-keep">
|
||||
Infinite Marketing이 만드는 실질적인 변화를 확인해보세요!
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-12">
|
||||
{USE_CASE_CARDS.map((c) => (
|
||||
<div
|
||||
key={c.title}
|
||||
className={`${c.cardClass} ${inView ? c.animClass : "opacity-0"}`}
|
||||
>
|
||||
<h3 className="headline-24 md:headline-30 text-navy-900 mb-8 break-keep">{c.title}</h3>
|
||||
<ul className="space-y-6 list-none m-0 p-0">
|
||||
{c.items.map((item) => (
|
||||
<li key={item} className="flex items-start gap-4 text-neutral-80 font-medium group break-keep">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className={`w-6 h-6 shrink-0 mt-1 ${c.iconClass}`}
|
||||
/>
|
||||
<span className="leading-relaxed body-18 group-hover:text-navy-900 transition-colors break-keep">
|
||||
{item}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
type Props = { visible: boolean };
|
||||
|
||||
export function AgdpCycleSummaryCard({ visible }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={`max-w-3xl mx-auto mt-12 text-center ${
|
||||
visible ? "animate-fade-in-up animation-delay-500" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="inline-block bg-white/5 border border-white/10 rounded-2xl px-6 py-4 backdrop-blur-sm">
|
||||
<p className="body-14-md-16 text-neutral-40 break-keep">
|
||||
<span className="font-bold text-lavender-300 break-keep">AGDP Cycle:</span>{" "}
|
||||
<span className="break-keep">
|
||||
분석(Analysis) → 생성(Generation) → 배포(Distribution) → 성과(Performance)의 무한 루프를 통해 콘텐츠 품질(CTR)을{" "}
|
||||
</span>
|
||||
<span className="text-white font-medium break-keep">자율 최적화</span>
|
||||
<span className="break-keep">합니다.</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div
|
||||
className={`relative w-full max-w-[320px] md:max-w-[500px] aspect-square mx-auto mt-16 mb-24 md:mb-32 ${
|
||||
visible ? "animate-fade-in-scale animation-delay-300" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="absolute top-[20%] left-[20%] right-[20%] bottom-[20%] rounded-full border border-white/5 shadow-[0_0_40px_rgba(255,255,255,0.02)_inset]" />
|
||||
|
||||
<svg className="absolute inset-0 w-full h-full animate-[spin_20s_linear_infinite]" viewBox="0 0 100 100" aria-hidden="true">
|
||||
<defs>
|
||||
<linearGradient id="agdp-ring-grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="var(--color-violet-500)" stopOpacity="0.1" />
|
||||
<stop offset="50%" stopColor="var(--color-lavender-300)" stopOpacity="0.5" />
|
||||
<stop offset="100%" stopColor="var(--color-violet-500)" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="30"
|
||||
fill="none"
|
||||
stroke="url(#agdp-ring-grad)"
|
||||
strokeWidth="0.5"
|
||||
strokeDasharray="2 2"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-20">
|
||||
<div className="w-40 h-40 md:w-56 md:h-56 rounded-full bg-navy-900/90 backdrop-blur-xl border border-white/5 flex flex-col items-center justify-center shadow-[0_0_60px_rgba(167,139,250,0.1)]">
|
||||
<span className="display-36 md:display-48 text-transparent bg-clip-text bg-gradient-to-r from-marketing-cream via-marketing-lilac to-marketing-ice mb-2 break-keep">
|
||||
AGDP
|
||||
</span>
|
||||
<h3 className="headline-20 md:headline-30 text-white text-center leading-tight break-keep">
|
||||
Infinite
|
||||
<br />
|
||||
Marketing
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{AGDP_NODES.map((node) => (
|
||||
<AgdpOrbitNode key={node.letter} node={node} />
|
||||
))}
|
||||
|
||||
<AgdpRewardPathLabel />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className={wrapperClass}>
|
||||
<div className="w-16 h-16 md:w-20 md:h-20 rounded-full border border-violet-500/30 bg-navy-900/80 backdrop-blur-sm flex items-center justify-center shadow-[0_0_20px_rgba(168,85,247,0.15)]">
|
||||
<span className="font-sans font-bold text-3xl md:text-4xl text-lavender-300">{node.letter}</span>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<span className="block body-18-md-20 font-medium text-lavender-200 leading-tight break-keep">
|
||||
{node.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/** P→A 호 SVG 경로 위 "Reward" 라벨 (DEMO Solution.tsx) */
|
||||
export function AgdpRewardPathLabel() {
|
||||
const pathId = "agdp-reward-path";
|
||||
|
||||
return (
|
||||
<svg className="absolute inset-0 w-full h-full pointer-events-none z-20" viewBox="0 0 100 100" aria-hidden="true">
|
||||
<defs>
|
||||
<path id={pathId} d="M 10.6 56.9 A 40 40 0 0 0 43.1 89.4" fill="none" />
|
||||
</defs>
|
||||
<text fontSize="3.5" className="break-keep font-medium uppercase tracking-widest fill-lavender-300" opacity="0.8">
|
||||
<textPath href={`#${pathId}`} startOffset="50%" textAnchor="middle">
|
||||
← Reward SIGNAL
|
||||
</textPath>
|
||||
</text>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import twinklePink from "@/assets/home/twinkle_pink.svg";
|
||||
|
||||
type Props = { visible: boolean };
|
||||
|
||||
export function MarketingEngineIntro({ visible }: Props) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/10 backdrop-blur-sm border border-white/20 mb-8 ${
|
||||
visible ? "animate-fade-in-up" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<img src={twinklePink} alt="" className="w-4 h-4 shrink-0" width={16} height={16} />
|
||||
<span className="body-14-medium text-lavender-100 break-keep">AI Marketing Engine</span>
|
||||
</div>
|
||||
|
||||
<h2
|
||||
className={`display-36 md:display-48 mb-6 leading-tight text-transparent bg-clip-text bg-gradient-to-r from-marketing-cream via-marketing-lilac to-marketing-ice break-keep ${
|
||||
visible ? "animate-fade-in-up animation-delay-100" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
Infinite Marketing Engine
|
||||
</h2>
|
||||
|
||||
<p
|
||||
className={`body-18-md-20 text-neutral-40 mb-12 max-w-3xl mx-auto leading-relaxed font-light break-keep ${
|
||||
visible ? "animate-fade-in-up animation-delay-200" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<span className="body-20-medium text-neutral-00 break-keep">
|
||||
Infinite Marketing for Premium Medical Business & Marketing Agency
|
||||
</span>
|
||||
<br className="hidden md:block" />
|
||||
Infinite Marketing은 Premium Medical Business와 Marketing Agency를 위한 AI Marketing Automation Platform입니다.
|
||||
INFINITH는 마케팅 분석, 콘텐츠 생성, 영상 콘텐츠 제작, 채널 배포, 성과 분석과 피드백 적용을 하나의 Self-Improving Marketing
|
||||
Engine으로 제공합니다.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div
|
||||
className={`bg-white rounded-2xl p-5 md:p-6 shadow-xl shadow-neutral-30/50 border border-neutral-20 flex flex-col ${className} ${
|
||||
visible ? `animate-fade-in-up ${delayClass}`.trim() : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<div className="w-10 h-10 shrink-0 rounded-xl flex items-center justify-center text-white title-18 bg-navy-950">
|
||||
{mod.step}
|
||||
</div>
|
||||
<h3 className="font-sans title-18-md-20 text-navy-800 leading-tight tracking-tight break-keep">{mod.title}</h3>
|
||||
</div>
|
||||
|
||||
<ul className="space-y-3 mb-6 flex-grow list-none m-0 p-0">
|
||||
{mod.items.map((item) => (
|
||||
<li key={item} className="body-14-md-16 text-neutral-70 leading-relaxed break-keep">
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="mt-auto pt-4 border-t border-neutral-20 font-bold body-14-md-16 tracking-tight text-violet-700 break-keep">
|
||||
{mod.highlight}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
type Props = { visible: boolean };
|
||||
|
||||
/** Core Modules 섹션 중앙 — Self-Improving Growth Engine 타이틀 */
|
||||
export function CoreModulesCenterHeading({ visible }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={`relative w-72 h-72 md:w-[400px] md:h-[400px] flex items-center justify-center ${
|
||||
visible ? "animate-fade-in-up" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-center z-10">
|
||||
<h3 className="headline-24 md:headline-30 text-navy-800 leading-tight break-keep">
|
||||
Self-Improving
|
||||
<br />
|
||||
Growth Engine
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
/**
|
||||
* 요소가 뷰포트에 진입하면 inView = true 로 바뀌는 훅.
|
||||
* framer-motion의 whileInView({ once: true }) 와 동일한 동작.
|
||||
*/
|
||||
export function useInView<T extends HTMLElement = HTMLElement>(
|
||||
options?: IntersectionObserverInit,
|
||||
) {
|
||||
const ref = useRef<T>(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 };
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
/**
|
||||
* 지정한 경로로 이동하면서 페이지 상단으로 smooth 스크롤합니다.
|
||||
* 로고 클릭 등 "홈으로 돌아가기 + 스크롤 초기화" 패턴에 사용합니다.
|
||||
*/
|
||||
export function useScrollToTop(path = "/") {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault();
|
||||
navigate(path);
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<footer className="flex w-full items-center justify-between px-[316px] py-[48px]">
|
||||
<div
|
||||
className="text-[#0A1128] text-[30px] not-italic font-bold leading-[32px] tracking-[-0.6px]"
|
||||
>
|
||||
CLINICAD
|
||||
</div>
|
||||
const handleLogoClick = useScrollToTop();
|
||||
|
||||
<div
|
||||
className="text-[#62748E] font-inter text-sm font-normal leading-5"
|
||||
>
|
||||
© 2026 CLINICAD. All rights reserved. Infinite Marketing for Premium Medical Business & Marketing Agency
|
||||
</div>
|
||||
return (
|
||||
<footer className="bg-white border-t border-slate-100 py-12 px-6">
|
||||
<div className="max-w-7xl mx-auto flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
|
||||
<div className="flex items-center gap-6">
|
||||
<span className="text-[#62748E] font-inter text-sm font-normal leading-5">Privacy Policy</span>
|
||||
<span className="text-[#62748E] font-inter text-sm font-normal leading-5">Terms of Service</span>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
{/* 로고 */}
|
||||
<a
|
||||
href="/"
|
||||
onClick={handleLogoClick}
|
||||
className="font-serif text-3xl font-black tracking-[0.05em] bg-gradient-to-r from-violet-700 to-navy-950 bg-clip-text text-transparent"
|
||||
>
|
||||
INFINITH
|
||||
</a>
|
||||
|
||||
{/* 카피라이트 */}
|
||||
<p className="body-14 text-neutral-60 text-center md:text-left">
|
||||
© {new Date().getFullYear()} CLINICAD. All rights reserved.{" "}
|
||||
<br className="md:hidden" />
|
||||
Infinite Marketing for Premium Medical Business & Marketing Agency
|
||||
</p>
|
||||
|
||||
{/* 링크 */}
|
||||
<nav className="flex items-center gap-6">
|
||||
{LINKS.map(({ label, href }) => (
|
||||
<Link
|
||||
key={label}
|
||||
to={href}
|
||||
className="body-14-medium text-neutral-60 hover:text-navy-900 transition-colors"
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<nav className="w-full h-14 flex items-center justify-between px-[316px]" style={{ background: "var(--Color-Neutral-00)" }}>
|
||||
const handleLogoClick = useScrollToTop();
|
||||
|
||||
{/* 로고 */}
|
||||
<span
|
||||
onClick={() => window.location.href = "/"}
|
||||
className="text-[32px] font-bold leading-[32px] tracking-[-0.6px] ml-6 cursor-pointer"
|
||||
style={{ color: "#0A1128", fontFamily: "var(--font-family-display)" }}
|
||||
>
|
||||
CLINAD
|
||||
</span>
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 h-20 bg-white/70 backdrop-blur-lg border-b border-white/20">
|
||||
<div className="max-w-7xl mx-auto px-6 h-full flex items-center justify-between">
|
||||
|
||||
{/* 메뉴 */}
|
||||
<ul className="flex items-center gap-8">
|
||||
{MENUS.map(({ name }, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="text-sm font-medium leading-5 cursor-pointer whitespace-nowrap"
|
||||
style={{ color: "var(--Color-Neutral-70)", fontFamily: "var(--font-family-inter)" }}
|
||||
>
|
||||
{name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{/* 로고 */}
|
||||
<a
|
||||
href="/"
|
||||
onClick={handleLogoClick}
|
||||
className="font-serif text-3xl font-black tracking-[0.05em] bg-gradient-to-r from-violet-700 to-navy-950 bg-clip-text text-transparent"
|
||||
>
|
||||
INFINITH
|
||||
</a>
|
||||
|
||||
{/* 로그인 버튼 */}
|
||||
<button
|
||||
onClick={() => window.location.href = "/login"}
|
||||
className="flex items-center justify-center w-[85px] h-[40px] shadow rounded-[99px] mr-6 bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white text-sm font-medium leading-5 cursor-pointer"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</nav>
|
||||
)
|
||||
{/* 메뉴 — md 이상만 표시 */}
|
||||
<ul className="hidden md:flex items-center gap-8">
|
||||
{MENUS.map(({ label, href }) => (
|
||||
<li key={label}>
|
||||
<a
|
||||
href={href}
|
||||
className="body-14-medium text-neutral-70 hover:text-navy-900 transition-colors"
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* 로그인 버튼 */}
|
||||
<Link
|
||||
to="/login"
|
||||
className="body-14-medium px-6 py-2.5 text-white rounded-full shadow-sm hover:shadow-md hover:opacity-90 transition-all bg-gradient-to-r from-violet-700 to-navy-950"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<header className="flex-none"><GNB /></header>
|
||||
<main className="flex-grow"><Outlet /></main>
|
||||
<footer className="flex-none"><Footer /></footer>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-neutral-10">
|
||||
<GNB />
|
||||
{/* fixed GNB(h-20) 높이만큼 상단 여백 확보 */}
|
||||
<main className="flex-grow pt-20">
|
||||
<Outlet />
|
||||
</main>
|
||||
<Footer />
|
||||
<PageNavigator />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div
|
||||
data-no-print
|
||||
className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 flex items-center gap-1 bg-white/90 backdrop-blur-xl border border-slate-200 rounded-full px-2 py-2 shadow-[0_4px_20px_rgba(0,0,0,0.08)]"
|
||||
>
|
||||
{/* 이전 페이지 */}
|
||||
<button
|
||||
onClick={() => prev && navigate(prev.path)}
|
||||
disabled={!prev}
|
||||
aria-label={prev ? `이전: ${prev.label}` : "이전 페이지 없음"}
|
||||
className="body-14-medium flex items-center gap-2 px-3 py-2 rounded-full text-neutral-70 transition-all hover:bg-neutral-20 hover:text-navy-900 disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer"
|
||||
>
|
||||
<ChevronLeftIcon aria-hidden="true" />
|
||||
{prev && <span className="hidden sm:inline">{prev.label}</span>}
|
||||
</button>
|
||||
|
||||
{/* 페이지 인디케이터 */}
|
||||
<div className="flex items-center gap-2 px-2">
|
||||
{PAGE_FLOW.map((page, i) => (
|
||||
<button
|
||||
key={page.path}
|
||||
onClick={() => navigate(page.path)}
|
||||
title={page.label}
|
||||
aria-label={page.label}
|
||||
className={`rounded-full transition-all cursor-pointer ${
|
||||
i === currentIndex
|
||||
? "w-6 h-2 bg-navy-900"
|
||||
: "w-2 h-2 bg-neutral-40 hover:bg-neutral-60"
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 다음 페이지 */}
|
||||
<button
|
||||
onClick={() => next && navigate(next.path)}
|
||||
disabled={!next}
|
||||
aria-label={next ? `다음: ${next.label}` : "다음 페이지 없음"}
|
||||
className="body-14-medium flex items-center gap-2 px-3 py-2 rounded-full text-neutral-70 transition-all hover:bg-neutral-20 hover:text-navy-900 disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer"
|
||||
>
|
||||
{next && <span className="hidden sm:inline">{next.label}</span>}
|
||||
<ChevronRightIcon aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
|
|
@ -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()],
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue