diff --git a/package-lock.json b/package-lock.json index f0a3c5b..92700b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "test", "version": "0.0.0", "dependencies": { + "framer-motion": "^12.38.0", "react": "^19.2.4", "react-dom": "^19.2.4" }, @@ -1535,6 +1536,33 @@ "dev": true, "license": "ISC" }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2078,6 +2106,21 @@ "node": "*" } }, + "node_modules/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2438,9 +2481,7 @@ "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", - "optional": true + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", diff --git a/package.json b/package.json index 4e0b400..968ac8e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "framer-motion": "^12.38.0", "react": "^19.2.4", "react-dom": "^19.2.4" }, diff --git a/public/images/1111.png b/public/images/1111.png deleted file mode 100644 index 325f191..0000000 Binary files a/public/images/1111.png and /dev/null differ diff --git a/public/images/hero1.png b/public/images/hero1.png index 624c2ba..a11d4dc 100644 Binary files a/public/images/hero1.png and b/public/images/hero1.png differ diff --git a/public/images/hero2.png b/public/images/hero2.png index 083ddf4..7ef3e4c 100644 Binary files a/public/images/hero2.png and b/public/images/hero2.png differ diff --git a/public/images/hero3.png b/public/images/hero3.png index f340a8e..32516ca 100644 Binary files a/public/images/hero3.png and b/public/images/hero3.png differ diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 496010a..da9b0c9 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -41,11 +41,7 @@ const menuData = [ { title: "사업 분야", items: [ - { - label: "System Integration", - to: "/business/system-integration", - desc: "맞춤형 정보시스템 구축", - }, + { label: "System Integration", to: "/business/system-integration", desc: "맞춤형 정보시스템 구축" }, { label: "플랫폼 구축", to: "/business/platform", desc: "서비스 플랫폼 기획·개발" }, { label: "R&D", to: "/business/rnd", desc: "연구 개발 및 기술 고도화" }, ], @@ -55,11 +51,7 @@ const menuData = [ items: [ { label: "항공/관제", to: "/business/aviation", desc: "항공·관제 특화 서비스" }, { label: "스마트 관광", to: "/business/tourism", desc: "관광·예약 플랫폼 구축" }, - { - label: "공공/기업 SI", - to: "/business/public-enterprise", - desc: "공공·기업 대상 구축 경험", - }, + { label: "공공/기업 SI", to: "/business/public-enterprise", desc: "공공·기업 대상 구축 경험" }, ], }, ], @@ -80,11 +72,7 @@ const menuData = [ title: "주요 솔루션", items: [ { label: "항공예약 플랫폼", to: "/solution/air-booking", desc: "예약·발권·운영 플랫폼" }, - { - label: "스마트 관광 플랫폼", - to: "/solution/smart-tour", - desc: "관광 서비스 통합 운영", - }, + { label: "스마트 관광 플랫폼", to: "/solution/smart-tour", desc: "관광 서비스 통합 운영" }, { label: "상황관제 시스템", to: "/solution/control", desc: "실시간 상황 모니터링" }, ], }, @@ -146,6 +134,7 @@ export default function PalRenewalHeader() { const [activeMenu, setActiveMenu] = useState(null); const [isHeaderHover, setIsHeaderHover] = useState(false); const [isScrolled, setIsScrolled] = useState(false); + const [isDarkHero, setIsDarkHero] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [mobileOpenKey, setMobileOpenKey] = useState(null); @@ -156,18 +145,29 @@ export default function PalRenewalHeader() { const activeData = menuData.find((item) => item.key === activeMenu); const showPanel = Boolean(activeData && !activeData.simple && isHeaderHover); - // const isActiveHeader = isScrolled || showPanel || isMobileMenuOpen; useEffect(() => { - const onScroll = () => { + const updateHeaderState = () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop || 0; - setIsScrolled(scrollTop > 2000); + const darkHeroActive = document.body.classList.contains("is-dark-hero"); + + setIsDarkHero(darkHeroActive); + setIsScrolled(!darkHeroActive && scrollTop > 80); }; - onScroll(); - window.addEventListener("scroll", onScroll, { passive: true }); + updateHeaderState(); + + const observer = new MutationObserver(() => { + updateHeaderState(); + }); + + observer.observe(document.body, { attributes: true, attributeFilter: ["class"] }); + window.addEventListener("scroll", updateHeaderState, { passive: true }); - return () => window.removeEventListener("scroll", onScroll); + return () => { + observer.disconnect(); + window.removeEventListener("scroll", updateHeaderState); + }; }, []); useEffect(() => { @@ -259,14 +259,16 @@ export default function PalRenewalHeader() { } }; + const isActiveHeader = isScrolled || showPanel || isMobileMenuOpen; + const logoSrc = isActiveHeader || !isDarkHero ? "./images/pal_logo.png" : "./images/pal_logo_wh.png"; + return ( <>

- PAL Networks - {/* PAL Networks */} + PAL Networks

diff --git a/src/css/common.css b/src/css/common.css index df2655e..11b443c 100644 --- a/src/css/common.css +++ b/src/css/common.css @@ -17,23 +17,49 @@ body{overflow-x:hidden;} .main-layout{min-height:calc(100vh - var(--header-height));padding-top:var(--header-height);} .sub-layout{min-height:calc(100vh - var(--header-height));padding-top:var(--header-height);} -/*main visual*/ -.main-section{height:200vh;} -.main-bg{position:sticky;top:var(--header-height);display:flex;max-width:1440px;margin:0 auto;height:600px;border-radius:8px;overflow:hidden;background:url('/images/1111.png') no-repeat 100% 50%/cover;transform:scale(1);transform-origin:center center;} -.main-bg-hero2{position:absolute;inset:0;background:url('/images/hero3.png') no-repeat 50% 50%/cover;opacity:0;} -.split-text-section{width:100%;} -.main-text{position:relative;display:flex;flex:1;align-items:center;justify-content:center;padding:0 40px;} -.main-text .text{position:absolute;color:#fff;font-size:48px;line-height:1.2;width:100%;text-align:center;perspective:500px;font-weight:600;left:50%;top:50%;transform:translate(-50%,-50%);} +/* main visual */ +.main-page{width:100%;overflow:hidden;} +.main-section{position:relative;height:100vh;} +.main-bg-wrap{position:relative;width:100%;height:100vh;display:flex;align-items:flex-start;justify-content:center;overflow:hidden;} +.main-bg{position:relative;width:min(1440px,calc(100vw - 80px));height:600px;border-radius:24px;overflow:hidden;transform-origin:center center;will-change:transform,width,height,border-radius;} + +.main-bg-hero1{position:absolute;inset:0;background:url('/images/hero1.png') no-repeat 50% 50%/cover;transform:scale(1.02);} +.main-bg-hero2{position:absolute;inset:0;background:url('/images/hero2.png') no-repeat 50% 50%/cover;opacity:0;} +.main-bg-hero3{position:absolute;inset:0;background:url('/images/hero3.png') no-repeat 50% 50%/cover;opacity:0;} + +.main-bg::before{content:"";position:absolute;inset:0;background:radial-gradient(circle at 50% 78%,rgba(58,64,129,.24) 0%,rgba(58,64,129,.08) 18%,rgba(58,64,129,0) 45%);z-index:1;pointer-events:none;} +.main-bg::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(4,10,24,.1) 0%,rgba(4,10,24,.32) 52%,rgba(4,10,24,.58) 100%),linear-gradient(90deg,rgba(0,0,0,.2) 0%,rgba(0,0,0,.02) 40%,rgba(0,0,0,.14) 100%);z-index:1;pointer-events:none;} + +.main-text{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:0 40px;z-index:2;} +.main-text .text{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:100%;max-width:1080px;color:#fff;text-align:center;font-size:56px;line-height:1.2;font-weight:700;letter-spacing:-.05em;will-change:transform,opacity,filter;text-shadow:0 10px 30px rgba(0,0,0,.22);} .main-text .text-change{opacity:0;} +.main-text .text-fill .fill-line{display:inline-block;color:rgba(255,255,255,.28);background:linear-gradient(90deg,#fff 0%,#fff 50%,rgba(255,255,255,.28) 50%,rgba(255,255,255,.28) 100%);background-size:220% 100%;background-position:100% 0;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;} + +.main-progress{position:absolute;left:50%;bottom:42px;transform:translateX(-50%);width:min(240px,calc(100% - 48px));height:2px;background:rgba(255,255,255,.22);z-index:3;overflow:hidden;border-radius:999px;} +.main-progress-bar{display:block;width:0%;height:100%;background:#fff;border-radius:999px;} + +.main-scroll-indicator{position:absolute;left:50%;bottom:72px;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;gap:10px;z-index:3;pointer-events:none;} +.main-scroll-line{display:block;width:1px;height:28px;background:linear-gradient(180deg,rgba(255,255,255,.15) 0%,rgba(255,255,255,.95) 100%);animation:scrollLine 1.8s ease-in-out infinite;} +.main-scroll-text{font-size:11px;line-height:1;letter-spacing:.28em;font-weight:600;color:rgba(255,255,255,.78);} -@media (max-width:1024px){ -.main-bg{height:520px;} -.main-text .text{font-size:38px;} +@keyframes scrollLine{ +0%{transform:translateY(0);opacity:.35;} +50%{transform:translateY(6px);opacity:1;} +100%{transform:translateY(0);opacity:.35;} +} + +@media (max-width:1280px){ +.main-bg{width:calc(100vw - 48px);height:540px;} +.main-text .text{font-size:44px;max-width:980px;} } @media (max-width:768px){ -.main-section{height:170vh;} -.main-bg{top:80px;height:420px;} +.main-bg{width:calc(100vw - 24px);height:72vh;border-radius:20px;} .main-text{padding:0 20px;} -.main-text .text{font-size:28px;line-height:1.3;} -} \ No newline at end of file +.main-text .text{font-size:30px;line-height:1.28;letter-spacing:-.035em;max-width:100%;} +.main-progress{bottom:28px;width:calc(100% - 32px);} +.main-scroll-indicator{bottom:56px;} +.main-scroll-line{height:22px;} +.main-scroll-text{font-size:10px;letter-spacing:.22em;} +} + diff --git a/src/css/header.css b/src/css/header.css index d740322..6b86fee 100644 --- a/src/css/header.css +++ b/src/css/header.css @@ -1,9 +1,8 @@ /*header.*/ .pal-header{position:fixed;top:0;left:0;right:0;z-index:1000;background:transparent;transition:box-shadow .35s ease;} -.pal-header::before{content:"";position:absolute;left:0;top:0;width:100%;height:100%;background:rgba(255,255,255,.96);backdrop-filter:blur(16px);opacity:0;pointer-events:none;transition:opacity .35s ease;} +.pal-header::before{content:"";position:absolute;left:0;top:0;width:100%;height:100%;background:rgba(255,255,255,.9);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);opacity:0;pointer-events:none;transition:opacity .35s ease;} .pal-header.is-scrolled::before,.pal-header.is-open::before,.pal-header.is-mobile-open::before{opacity:1;} .pal-header.is-scrolled,.pal-header.is-open,.pal-header.is-mobile-open{box-shadow:0 18px 40px rgba(15,23,42,.08);} -/* .pal-header.is-scrolled .pal-gnb-link,.pal-header.is-open .pal-gnb-link,.pal-header.is-mobile-open .pal-gnb-link{color:#111} */ .pal-header-inner{position:relative;display:flex;align-items:center;justify-content:space-between;max-width:1440px;height:96px;margin:0 auto;padding:0 40px;z-index:2;} .pal-header-logo{flex:0 0 auto;margin:0;font-size:0;line-height:1;} @@ -24,7 +23,7 @@ .pal-header-contact{display:inline-flex;align-items:center;justify-content:center;height:44px;padding:0 18px;border:1px solid rgba(17,17,17,.12);border-radius:999px;background:#fff;color:#111;font-size:14px;font-weight:700;line-height:1;text-decoration:none;transition:border-color .3s ease,background .3s ease,color .3s ease,transform .3s ease,box-shadow .3s ease;} .pal-header-contact:hover,.pal-header-contact:focus-visible{border-color:var(--color-primary);background:var(--color-primary);color:#fff;transform:translateY(-2px);box-shadow:0 12px 24px var(--color-primary-shadow);outline:none;} -.pal-header-hamburger{display:none;position:relative;width:46px;height:46px;padding:0;border:1px solid rgba(17,17,17,.08);border-radius:14px;background:#fff;cursor:pointer;transition:border-color .3s ease,transform .3s ease,box-shadow .3s ease;} +.pal-header-hamburger{display:none;position:relative;width:46px;height:46px;padding:0;border:1px solid rgba(17,17,17,.08);border-radius:14px;background:#fff;cursor:pointer;transition:border-color .3s ease,transform .3s ease,box-shadow .3s ease,background .3s ease;} .pal-header-hamburger:hover,.pal-header-hamburger:focus-visible{border-color:var(--color-primary-border-strong);transform:translateY(-2px);box-shadow:0 12px 24px rgba(15,23,42,.08);outline:none;} .pal-header-hamburger span{position:absolute;left:50%;width:18px;height:2px;border-radius:999px;background:#111;transform:translateX(-50%);transition:top .28s ease,transform .28s ease,opacity .2s ease,background .28s ease;} .pal-header-hamburger span:nth-child(1){top:15px;} @@ -34,9 +33,16 @@ .pal-header-hamburger.is-active span:nth-child(2){opacity:0;} .pal-header-hamburger.is-active span:nth-child(3){top:22px;transform:translateX(-50%) rotate(-45deg);background:var(--color-primary);} +body.is-dark-hero .pal-header:not(.is-scrolled):not(.is-open):not(.is-mobile-open)::before{opacity:0;} +body.is-dark-hero .pal-header:not(.is-scrolled):not(.is-open):not(.is-mobile-open) .pal-gnb-link{color:#fff;} +body.is-dark-hero .pal-header:not(.is-scrolled):not(.is-open):not(.is-mobile-open) .pal-gnb-link-text{text-shadow:0 2px 12px rgba(0,0,0,.28);} +body.is-dark-hero .pal-header:not(.is-scrolled):not(.is-open):not(.is-mobile-open) .pal-header-contact{border:1px solid rgba(255,255,255,.22);background:rgba(255,255,255,.08);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:#fff;box-shadow:none;} +body.is-dark-hero .pal-header:not(.is-scrolled):not(.is-open):not(.is-mobile-open) .pal-header-hamburger{border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.08);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);} +body.is-dark-hero .pal-header:not(.is-scrolled):not(.is-open):not(.is-mobile-open) .pal-header-hamburger span{background:#fff;} + .pal-mega-panel{position:absolute;top:100%;left:0;width:100%;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .18s ease;z-index:1;} .pal-mega-panel.is-visible{pointer-events:auto;opacity:1;visibility:visible;} -.pal-mega-panel::before{content:"";position:absolute;left:0;top:0;width:100%;height:100%;background:rgba(255,255,255,.98);backdrop-filter:blur(10px);border-top:1px solid rgba(17,17,17,.06);box-shadow:0 24px 50px rgba(15,23,42,.08);} +.pal-mega-panel::before{content:"";position:absolute;left:0;top:0;width:100%;height:100%;background:rgba(255,255,255,.96);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border-top:1px solid rgba(17,17,17,.06);box-shadow:0 24px 50px rgba(15,23,42,.08);} .pal-mega-panel-inner{position:relative;display:grid;grid-template-columns:360px 1fr;gap:40px;max-width:1440px;margin:0 auto;padding:24px 40px 28px;z-index:1;} diff --git a/src/pages/MainPage.jsx b/src/pages/MainPage.jsx index 8f9c7e4..f602e81 100644 --- a/src/pages/MainPage.jsx +++ b/src/pages/MainPage.jsx @@ -1,108 +1,109 @@ import { useEffect, useRef } from "react"; import { gsap } from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; -import { SplitText } from "gsap/SplitText"; -gsap.registerPlugin(ScrollTrigger, SplitText); +gsap.registerPlugin(ScrollTrigger); function MainPage() { const sectionRef = useRef(null); const bgRef = useRef(null); const hero2Ref = useRef(null); + const hero3Ref = useRef(null); const text1Ref = useRef(null); const text2Ref = useRef(null); - const splitRef = useRef(null); + const text3Ref = useRef(null); + const progressBarRef = useRef(null); useEffect(() => { - const setupText = () => { - if (splitRef.current) splitRef.current.revert(); + const ctx = gsap.context(() => { + gsap.set(hero2Ref.current, { opacity: 0, scale: 1.14, filter: "blur(10px)" }); + gsap.set(hero3Ref.current, { opacity: 0, scale: 1.14, filter: "blur(10px)" }); + gsap.set(text2Ref.current, { opacity: 0, y: 40, filter: "blur(8px)" }); + gsap.set(text3Ref.current, { opacity: 0, y: 40, filter: "blur(8px)" }); + gsap.set(progressBarRef.current, { width: "0%" }); + gsap.set(".fill-line", { backgroundPosition: "100% 0%" }); - splitRef.current = SplitText.create(text1Ref.current, { type: "chars" }); - - gsap.from(splitRef.current.chars, { - x: 150, - opacity: 0, - duration: 0.7, - ease: "power4", - stagger: 0.04, + const tl = gsap.timeline({ + scrollTrigger: { + trigger: sectionRef.current, + start: "top top", + end: "+=2600", + scrub: 1.2, + pin: true, + anticipatePin: 1, + }, }); - }; - setupText(); + tl.to(bgRef.current, { width: "100vw", height: "100vh", borderRadius: 0, ease: "none" }, 0) + .to(bgRef.current, { scale: 1.08, ease: "none" }, 0) + .to(progressBarRef.current, { width: "100%", ease: "none" }, 0) + .to(".fill-line", { backgroundPosition: "0% 0%", ease: "none", stagger: 0.12, duration: 1.2 }, 0.08) + + .to(text1Ref.current, { opacity: 0, y: -40, filter: "blur(8px)", ease: "none" }, 1.35) - gsap.set(hero2Ref.current, { opacity: 0 }); - gsap.set(text2Ref.current, { opacity: 0, y: 30 }); + .to(hero2Ref.current, { opacity: 1, scale: 1.03, filter: "blur(0px)", ease: "none" }, 1.15) + .fromTo(text2Ref.current, { opacity: 0, y: 40, filter: "blur(8px)" }, { opacity: 1, y: 0, filter: "blur(0px)", ease: "none" }, 1.22) - const tl = gsap.timeline({ - scrollTrigger: { + .to(hero2Ref.current, { scale: 1.1, ease: "none" }, 1.72) + .to(text2Ref.current, { opacity: 0, y: -40, filter: "blur(8px)", ease: "none" }, 2.02) + + .to(hero3Ref.current, { opacity: 1, scale: 1.03, filter: "blur(0px)", ease: "none" }, 2.14) + .fromTo(text3Ref.current, { opacity: 0, y: 40, filter: "blur(8px)" }, { opacity: 1, y: 0, filter: "blur(0px)", ease: "none" }, 2.28) + .to(hero3Ref.current, { scale: 1.08, ease: "none" }, 2.82); + + ScrollTrigger.create({ trigger: sectionRef.current, start: "top top", - end: "+=100%", - scrub: 1, - }, - }); - - tl.to( - bgRef.current, - { - scale: 1.18, - borderRadius: 0, - ease: "none", - }, - 0, - ) - .to( - text1Ref.current, - { - opacity: 0, - y: -30, - ease: "none", - }, - 0.2, - ) - .to( - hero2Ref.current, - { - opacity: 1, - ease: "none", - }, - 0.35, - ) - .to( - text2Ref.current, - { - opacity: 1, - y: 0, - ease: "none", - }, - 0.45, - ); - - window.addEventListener("resize", setupText); + end: "+=2600", + onEnter: () => document.body.classList.add("is-dark-hero"), + onEnterBack: () => document.body.classList.add("is-dark-hero"), + onLeave: () => document.body.classList.remove("is-dark-hero"), + onLeaveBack: () => document.body.classList.remove("is-dark-hero"), + }); + }, sectionRef); return () => { - window.removeEventListener("resize", setupText); - tl.kill(); - ScrollTrigger.getAll().forEach((trigger) => trigger.kill()); - if (splitRef.current) splitRef.current.revert(); + document.body.classList.remove("is-dark-hero"); + ctx.revert(); }; }, []); return (
-
-
+
+
+
+
+
+ +
+
+ Technology Partner for +
+ Advanced Air Mobility +
+ +
+ 항공 데이터와 통합 관제 기술로 +
+ 안전한 하늘길을 설계합니다 +
+ +
+ 공역관리와 드론 교통관리(UTM)를 기반으로 +
+ 실제 운영 가능한 항공 시스템을 구축합니다 +
+
-
-
- 미래 항공 모빌리티를 구현하는 기술 파트너 +
+
-
- 공역관리와 드론 교통관리(UTM)를 기반으로 -
- 실제 운영 가능한 항공 시스템을 구축합니다 +
+ + SCROLL