@ -0,0 +1,24 @@
|
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
pnpm-debug.log* |
||||
lerna-debug.log* |
||||
|
||||
node_modules |
||||
dist |
||||
dist-ssr |
||||
*.local |
||||
|
||||
# Editor directories and files |
||||
.vscode/* |
||||
!.vscode/extensions.json |
||||
.idea |
||||
.DS_Store |
||||
*.suo |
||||
*.ntvs* |
||||
*.njsproj |
||||
*.sln |
||||
*.sw? |
||||
@ -1,2 +1,18 @@
|
||||
# PALNetworks |
||||
NEW PALNetworks |
||||
# React + Vite |
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. |
||||
|
||||
Currently, two official plugins are available: |
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) |
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) |
||||
|
||||
## React Compiler |
||||
|
||||
The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information. |
||||
|
||||
Note: This will impact Vite dev & build performances. |
||||
|
||||
## Expanding the ESLint configuration |
||||
|
||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. |
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import js from '@eslint/js' |
||||
import globals from 'globals' |
||||
import reactHooks from 'eslint-plugin-react-hooks' |
||||
import reactRefresh from 'eslint-plugin-react-refresh' |
||||
import { defineConfig, globalIgnores } from 'eslint/config' |
||||
|
||||
export default defineConfig([ |
||||
globalIgnores(['dist']), |
||||
{ |
||||
files: ['**/*.{js,jsx}'], |
||||
extends: [ |
||||
js.configs.recommended, |
||||
reactHooks.configs.flat.recommended, |
||||
reactRefresh.configs.vite, |
||||
], |
||||
languageOptions: { |
||||
ecmaVersion: 2020, |
||||
globals: globals.browser, |
||||
parserOptions: { |
||||
ecmaVersion: 'latest', |
||||
ecmaFeatures: { jsx: true }, |
||||
sourceType: 'module', |
||||
}, |
||||
}, |
||||
rules: { |
||||
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], |
||||
}, |
||||
}, |
||||
]) |
||||
@ -0,0 +1,71 @@
|
||||
<!doctype html> |
||||
<html lang="ko"> |
||||
<head> |
||||
<meta charset="UTF-8" /> |
||||
<!-- 기본 SEO 설명: 검색 결과 제목 아래 요약문으로 사용될 수 있음 --> |
||||
<meta |
||||
name="description" |
||||
content="팔네트웍스는 소프트웨어 개발 및 공급, 항공 솔루션 구축을 수행하는 IT 전문 기업입니다." |
||||
/> |
||||
<!-- 검색 키워드: 최신 SEO 영향은 크지 않지만 브랜드/서비스 키워드 정리용으로 사용 가능 --> |
||||
<meta |
||||
name="keywords" |
||||
content="팔네트웍스, PAL Networks, 소프트웨어개발, 응용소프트웨어, IT, 관제솔루션, 항공솔루션, 솔루션구축, SI, 네트워크, 항공산업, 공역관리" |
||||
/> |
||||
<!-- 오픈그래프 제목: 카카오톡, 슬랙, 페이스북 등 링크 공유 시 제목으로 노출 --> |
||||
<meta property="og:title" content="팔네트웍스" /> |
||||
|
||||
<!-- 오픈그래프 설명: 카카오톡, 슬랙, 페이스북 등 링크 공유 시 설명문으로 노출 --> |
||||
<meta |
||||
property="og:description" |
||||
content="항공산업의 기술혁신을 선도하는 소프트웨어·IT 솔루션 파트너" |
||||
/> |
||||
|
||||
<!-- 오픈그래프 이미지: 카카오톡, 슬랙, 페이스북, 디스코드 등 링크 공유 시 썸네일 이미지로 사용 --> |
||||
<!-- 권장 이미지 사이즈: 1200x630px / JPG 또는 PNG / 1MB 이하 권장 --> |
||||
<!-- 로고만 있는 작은 이미지보다 브랜드 메시지가 포함된 대표 썸네일 이미지 권장 --> |
||||
<meta property="og:image" content="https://도메인주소/img/og-image.jpg" /> |
||||
|
||||
<!-- 오픈그래프 타입: 기업/브랜드 홈페이지 메인은 보통 website 사용 --> |
||||
<meta property="og:type" content="website" /> |
||||
|
||||
<!-- 공유 기준 대표 URL --> |
||||
<meta property="og:url" content="https://도메인주소" /> |
||||
|
||||
<!-- 사이트명 --> |
||||
<meta property="og:site_name" content="팔네트웍스" /> |
||||
|
||||
<!-- 트위터/X 공유 카드 형식: 큰 대표 이미지 형식 --> |
||||
<!-- 보통 og:image와 함께 사용되며, X(트위터) 공유 시 썸네일 크게 표시 --> |
||||
<meta name="twitter:card" content="summary_large_image" /> |
||||
|
||||
<!-- 트위터/X 제목 --> |
||||
<meta name="twitter:title" content="팔네트웍스" /> |
||||
|
||||
<!-- 트위터/X 설명 --> |
||||
<meta |
||||
name="twitter:description" |
||||
content="항공산업의 기술혁신을 선도하는 소프트웨어·IT 솔루션 파트너" |
||||
/> |
||||
|
||||
<!-- 트위터/X 공유 이미지 --> |
||||
<!-- 권장 사이즈: 1200x628px 정도 / og:image와 동일 이미지 사용해도 무방 --> |
||||
<meta name="twitter:image" content="https://도메인주소/img/og-image.jpg" /> |
||||
|
||||
<link rel="icon" type="image/png" href="favicon/favicon-96x96.png" sizes="96x96" /> |
||||
<link rel="icon" type="image/svg+xml" href="favicon/favicon.svg" /> |
||||
<link rel="shortcut icon" href="favicon/favicon.ico" /> |
||||
<link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png" /> |
||||
<meta name="apple-mobile-web-app-title" content="PAL Networks" /> |
||||
<link rel="manifest" href="favicon/site.webmanifest" /> |
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<meta name="format-detection" content="telephone=no" /> |
||||
|
||||
<title>PAL Networks</title> |
||||
</head> |
||||
<body> |
||||
<div id="root"></div> |
||||
<script type="module" src="/src/main.jsx"></script> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,30 @@
|
||||
{ |
||||
"name": "test", |
||||
"private": true, |
||||
"version": "0.0.0", |
||||
"type": "module", |
||||
"scripts": { |
||||
"dev": "vite", |
||||
"build": "vite build", |
||||
"lint": "eslint .", |
||||
"preview": "vite preview" |
||||
}, |
||||
"dependencies": { |
||||
"react": "^19.2.4", |
||||
"react-dom": "^19.2.4" |
||||
}, |
||||
"devDependencies": { |
||||
"@babel/core": "^7.29.0", |
||||
"@eslint/js": "^9.39.4", |
||||
"@rolldown/plugin-babel": "^0.2.2", |
||||
"@types/react": "^19.2.14", |
||||
"@types/react-dom": "^19.2.3", |
||||
"@vitejs/plugin-react": "^6.0.1", |
||||
"babel-plugin-react-compiler": "^1.0.0", |
||||
"eslint": "^9.39.4", |
||||
"eslint-plugin-react-hooks": "^7.0.1", |
||||
"eslint-plugin-react-refresh": "^0.5.2", |
||||
"globals": "^17.4.0", |
||||
"vite": "^8.0.4" |
||||
} |
||||
} |
||||
@ -0,0 +1,15 @@
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8" /> |
||||
<script> |
||||
var path = window.location.pathname.replace('/PALNetworks', '') |
||||
var search = window.location.search || '' |
||||
var hash = window.location.hash || '' |
||||
window.location.replace( |
||||
'/PALNetworks/' + '?redirect=' + encodeURIComponent(path + search + hash) |
||||
) |
||||
</script> |
||||
</head> |
||||
<body></body> |
||||
</html> |
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 73 KiB |
@ -0,0 +1,21 @@
|
||||
{ |
||||
"name": "PAL Networks", |
||||
"short_name": "PAL", |
||||
"icons": [ |
||||
{ |
||||
"src": "/web-app-manifest-192x192.png", |
||||
"sizes": "192x192", |
||||
"type": "image/png", |
||||
"purpose": "maskable" |
||||
}, |
||||
{ |
||||
"src": "/web-app-manifest-512x512.png", |
||||
"sizes": "512x512", |
||||
"type": "image/png", |
||||
"purpose": "maskable" |
||||
} |
||||
], |
||||
"theme_color": "#ffffff", |
||||
"background_color": "#ffffff", |
||||
"display": "standalone" |
||||
} |
||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 129 KiB |
@ -0,0 +1,23 @@
|
||||
import { Routes, Route, Navigate } from "react-router-dom"; |
||||
import MainLayout from "./components/MainLayout"; |
||||
import SubLayout from "./components/SubLayout"; |
||||
import MainPage from "./pages/MainPage"; |
||||
import AboutPage from "./pages/AboutPage"; |
||||
|
||||
function App() { |
||||
return ( |
||||
<Routes> |
||||
<Route path="/" element={<Navigate to="/Main" replace />} /> |
||||
|
||||
<Route element={<MainLayout />}> |
||||
<Route path="/Main" element={<MainPage />} /> |
||||
</Route> |
||||
|
||||
<Route element={<SubLayout />}> |
||||
<Route path="/About" element={<AboutPage />} /> |
||||
</Route> |
||||
</Routes> |
||||
); |
||||
} |
||||
|
||||
export default App; |
||||
@ -0,0 +1,19 @@
|
||||
import { Routes, Route, Navigate } from 'react-router-dom' |
||||
|
||||
import MainLayout from './components/MainLayout' |
||||
import MainPage from './pages/MainPage' |
||||
import AboutPage from './pages/AboutPage' |
||||
|
||||
function Router() { |
||||
return ( |
||||
<Routes> |
||||
<Route element={<MainLayout />}> |
||||
<Route index element={<Navigate to="/Main" replace />} /> |
||||
<Route path="/Main" element={<MainPage />} /> |
||||
<Route path="/About" element={<AboutPage />} /> |
||||
</Route> |
||||
</Routes> |
||||
) |
||||
} |
||||
|
||||
export default Router |
||||
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
@ -0,0 +1,23 @@
|
||||
function Footer() { |
||||
return ( |
||||
<footer className="site-footer"> |
||||
<div className="footer-inner"> |
||||
<div className="footer-left"> |
||||
<strong className="footer-logo">PALNETWORKS</strong> |
||||
<p className="footer-copy">© 2026 PALNETWORKS. All rights reserved.</p> |
||||
</div> |
||||
|
||||
<div className="footer-right"> |
||||
<a href="#none" className="footer-link"> |
||||
Privacy Policy |
||||
</a> |
||||
<a href="#none" className="footer-link"> |
||||
Terms of Use |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</footer> |
||||
) |
||||
} |
||||
|
||||
export default Footer |
||||
@ -0,0 +1,458 @@
|
||||
import { useEffect, useRef, useState } from "react"; |
||||
import { Link, NavLink } from "react-router-dom"; |
||||
|
||||
const menuData = [ |
||||
{ |
||||
key: "about", |
||||
label: "Company", |
||||
panelTitle: "PAL Networks", |
||||
panelDesc: "회사 소개와 비전, 연혁, 인증 및 조직 정보를 확인할 수 있습니다.", |
||||
sections: [ |
||||
{ |
||||
title: "회사 소개", |
||||
items: [ |
||||
{ label: "회사소개", to: "/About", desc: "기업 철학과 핵심 가치" }, |
||||
{ label: "연혁", to: "/history", desc: "주요 실적과 성장 과정" }, |
||||
{ label: "조직도", to: "/organization", desc: "조직 구성과 역할" }, |
||||
], |
||||
}, |
||||
{ |
||||
title: "신뢰 정보", |
||||
items: [ |
||||
{ label: "인증 및 특허", to: "/certification", desc: "기술력과 공인 인증 현황" }, |
||||
{ label: "파트너십", to: "/partners", desc: "협력사 및 네트워크" }, |
||||
{ label: "오시는 길", to: "/location", desc: "위치 및 연락처 안내" }, |
||||
], |
||||
}, |
||||
], |
||||
featured: { |
||||
eyebrow: "About Us", |
||||
title: "신뢰를 기반으로\n항공·플랫폼 기술을 확장합니다.", |
||||
text: "기업 소개 영역은 가볍게 보이지 않도록, 핵심 메시지와 신뢰 요소를 함께 노출하는 구성이 좋습니다.", |
||||
cta: { label: "회사소개 보기", to: "/company" }, |
||||
}, |
||||
}, |
||||
{ |
||||
key: "business", |
||||
label: "Business", |
||||
panelTitle: "Business Area", |
||||
panelDesc: "팔네트웍스의 주요 사업 영역과 구축 역량을 한눈에 볼 수 있습니다.", |
||||
sections: [ |
||||
{ |
||||
title: "사업 분야", |
||||
items: [ |
||||
{ |
||||
label: "System Integration", |
||||
to: "/business/system-integration", |
||||
desc: "맞춤형 정보시스템 구축", |
||||
}, |
||||
{ label: "플랫폼 구축", to: "/business/platform", desc: "서비스 플랫폼 기획·개발" }, |
||||
{ label: "R&D", to: "/business/rnd", desc: "연구 개발 및 기술 고도화" }, |
||||
], |
||||
}, |
||||
{ |
||||
title: "특화 영역", |
||||
items: [ |
||||
{ label: "항공/관제", to: "/business/aviation", desc: "항공·관제 특화 서비스" }, |
||||
{ label: "스마트 관광", to: "/business/tourism", desc: "관광·예약 플랫폼 구축" }, |
||||
{ |
||||
label: "공공/기업 SI", |
||||
to: "/business/public-enterprise", |
||||
desc: "공공·기업 대상 구축 경험", |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
featured: { |
||||
eyebrow: "Core Capability", |
||||
title: "기획부터 구축, 운영까지\n실무형 역량으로 연결합니다.", |
||||
text: "사업영역은 단순 나열보다 “무엇을 잘하는 회사인지” 바로 이해되게 보여주는 게 중요합니다.", |
||||
cta: { label: "사업영역 보기", to: "/business" }, |
||||
}, |
||||
}, |
||||
{ |
||||
key: "solution", |
||||
label: "Solution", |
||||
panelTitle: "Solution & Service", |
||||
panelDesc: "산업별 솔루션과 서비스 포트폴리오를 확장 가능한 구조로 구성합니다.", |
||||
sections: [ |
||||
{ |
||||
title: "주요 솔루션", |
||||
items: [ |
||||
{ label: "항공예약 플랫폼", to: "/solution/air-booking", desc: "예약·발권·운영 플랫폼" }, |
||||
{ |
||||
label: "스마트 관광 플랫폼", |
||||
to: "/solution/smart-tour", |
||||
desc: "관광 서비스 통합 운영", |
||||
}, |
||||
{ label: "상황관제 시스템", to: "/solution/control", desc: "실시간 상황 모니터링" }, |
||||
], |
||||
}, |
||||
{ |
||||
title: "확장 서비스", |
||||
items: [ |
||||
{ label: "VR/AR 시뮬레이터", to: "/solution/simulator", desc: "훈련·체험형 시뮬레이션" }, |
||||
{ label: "클라우드 서비스", to: "/solution/cloud", desc: "운영 인프라 및 클라우드 연계" }, |
||||
{ label: "커스텀 솔루션", to: "/solution/custom", desc: "고객 맞춤형 확장 개발" }, |
||||
], |
||||
}, |
||||
], |
||||
featured: { |
||||
eyebrow: "Scalable Navigation", |
||||
title: "지금은 기업 사이트,\n나중에는 솔루션 허브까지.", |
||||
text: "메가패널 구조를 미리 잡아두면 추후 메뉴가 늘어나도 헤더를 다시 뜯지 않아도 됩니다.", |
||||
cta: { label: "솔루션 보기", to: "/solution" }, |
||||
}, |
||||
}, |
||||
{ |
||||
key: "reference", |
||||
label: "Reference", |
||||
panelTitle: "Reference", |
||||
panelDesc: "구축 사례와 주요 프로젝트를 통해 신뢰도를 높일 수 있습니다.", |
||||
sections: [ |
||||
{ |
||||
title: "프로젝트", |
||||
items: [ |
||||
{ label: "구축 사례", to: "/reference/case", desc: "주요 구축 사례 소개" }, |
||||
{ label: "프로젝트 실적", to: "/reference/project", desc: "산업별 수행 실적" }, |
||||
{ label: "고객사", to: "/reference/client", desc: "주요 고객 및 협력 기관" }, |
||||
], |
||||
}, |
||||
{ |
||||
title: "콘텐츠", |
||||
items: [ |
||||
{ label: "뉴스", to: "/news", desc: "기업 소식 및 업데이트" }, |
||||
{ label: "홍보자료", to: "/media", desc: "브로슈어 및 홍보 콘텐츠" }, |
||||
{ label: "공지사항", to: "/notice", desc: "공지 및 안내 정보" }, |
||||
], |
||||
}, |
||||
], |
||||
featured: { |
||||
eyebrow: "Trust Point", |
||||
title: "결국 회사 사이트는\n“무엇을 해왔는지”가 보여야 합니다.", |
||||
text: "레퍼런스와 뉴스는 단순 보조 메뉴가 아니라 신뢰를 만드는 핵심 축으로 잡는 게 좋습니다.", |
||||
cta: { label: "레퍼런스 보기", to: "/reference" }, |
||||
}, |
||||
}, |
||||
{ |
||||
key: "contact", |
||||
label: "Contact", |
||||
to: "/contact", |
||||
simple: true, |
||||
}, |
||||
]; |
||||
|
||||
export default function PalRenewalHeader() { |
||||
const [activeMenu, setActiveMenu] = useState(null); |
||||
const [isHeaderHover, setIsHeaderHover] = useState(false); |
||||
const [isScrolled, setIsScrolled] = useState(false); |
||||
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); |
||||
const [mobileOpenKey, setMobileOpenKey] = useState(null); |
||||
|
||||
const closeTimer = useRef(null); |
||||
const navRefs = useRef({}); |
||||
const mobileFirstFocusableRef = useRef(null); |
||||
|
||||
const activeData = menuData.find((item) => item.key === activeMenu); |
||||
const showPanel = Boolean(activeData && !activeData.simple && isHeaderHover); |
||||
|
||||
useEffect(() => { |
||||
const onScroll = () => { |
||||
setIsScrolled(window.scrollY > 10); |
||||
}; |
||||
|
||||
onScroll(); |
||||
window.addEventListener("scroll", onScroll); |
||||
|
||||
return () => window.removeEventListener("scroll", onScroll); |
||||
}, []); |
||||
|
||||
useEffect(() => { |
||||
return () => { |
||||
if (closeTimer.current) clearTimeout(closeTimer.current); |
||||
}; |
||||
}, []); |
||||
|
||||
useEffect(() => { |
||||
if (isMobileMenuOpen) { |
||||
document.body.style.overflow = "hidden"; |
||||
|
||||
const timer = setTimeout(() => { |
||||
mobileFirstFocusableRef.current?.focus(); |
||||
}, 50); |
||||
|
||||
return () => { |
||||
clearTimeout(timer); |
||||
document.body.style.overflow = ""; |
||||
}; |
||||
} |
||||
|
||||
document.body.style.overflow = ""; |
||||
|
||||
return () => { |
||||
document.body.style.overflow = ""; |
||||
}; |
||||
}, [isMobileMenuOpen]); |
||||
|
||||
const clearCloseTimer = () => { |
||||
if (closeTimer.current) clearTimeout(closeTimer.current); |
||||
}; |
||||
|
||||
const openMenu = (key) => { |
||||
clearCloseTimer(); |
||||
setIsHeaderHover(true); |
||||
setActiveMenu(key); |
||||
}; |
||||
|
||||
const scheduleClose = () => { |
||||
clearCloseTimer(); |
||||
closeTimer.current = setTimeout(() => { |
||||
setIsHeaderHover(false); |
||||
setActiveMenu(null); |
||||
}, 120); |
||||
}; |
||||
|
||||
const closeDesktopMenu = () => { |
||||
setIsHeaderHover(false); |
||||
setActiveMenu(null); |
||||
}; |
||||
|
||||
const closeMobileMenu = () => { |
||||
setIsMobileMenuOpen(false); |
||||
setMobileOpenKey(null); |
||||
}; |
||||
|
||||
const closeAllMenus = () => { |
||||
closeDesktopMenu(); |
||||
closeMobileMenu(); |
||||
}; |
||||
|
||||
const toggleMobileMenu = () => { |
||||
setIsMobileMenuOpen((prev) => !prev); |
||||
}; |
||||
|
||||
const handleMobileAccordion = (key) => { |
||||
setMobileOpenKey((prev) => (prev === key ? null : key)); |
||||
}; |
||||
|
||||
const handleNavKeyDown = (e, index, item) => { |
||||
if (e.key === "ArrowRight") { |
||||
const nextItem = menuData[index + 1] || menuData[0]; |
||||
navRefs.current[nextItem.key]?.focus(); |
||||
} |
||||
|
||||
if (e.key === "ArrowLeft") { |
||||
const prevItem = menuData[index - 1] || menuData[menuData.length - 1]; |
||||
navRefs.current[prevItem.key]?.focus(); |
||||
} |
||||
|
||||
if (e.key === "ArrowDown" && !item.simple) { |
||||
e.preventDefault(); |
||||
openMenu(item.key); |
||||
} |
||||
|
||||
if (e.key === "Escape") { |
||||
scheduleClose(); |
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<> |
||||
<header className={`pal-header ${isScrolled || showPanel || isMobileMenuOpen ? "is-scrolled" : ""} ${showPanel ? "is-open" : ""}`} onMouseEnter={clearCloseTimer} onMouseLeave={scheduleClose}> |
||||
<div className="pal-header-inner"> |
||||
<h1 className="pal-header-logo"> |
||||
<Link to="/Main" onClick={closeAllMenus}> |
||||
<img src="/images/pal_logo.png" /> |
||||
</Link> |
||||
</h1> |
||||
|
||||
<nav className="pal-gnb" aria-label="Primary Navigation"> |
||||
<ul className="pal-gnb-depth1"> |
||||
{menuData.map((item, index) => { |
||||
const isActive = activeMenu === item.key; |
||||
|
||||
return ( |
||||
<li |
||||
className={`pal-gnb-item ${isActive ? "is-active" : ""}`} |
||||
key={item.key} |
||||
onMouseEnter={() => { |
||||
if (!item.simple) { |
||||
openMenu(item.key); |
||||
} else { |
||||
clearCloseTimer(); |
||||
closeDesktopMenu(); |
||||
} |
||||
}} |
||||
> |
||||
{item.simple ? ( |
||||
<NavLink |
||||
to={item.to} |
||||
className="pal-gnb-link" |
||||
ref={(el) => { |
||||
navRefs.current[item.key] = el; |
||||
}} |
||||
onClick={closeAllMenus} |
||||
onFocus={() => { |
||||
closeDesktopMenu(); |
||||
}} |
||||
onKeyDown={(e) => handleNavKeyDown(e, index, item)} |
||||
> |
||||
<span className="pal-gnb-link-text">{item.label}</span> |
||||
<span className="pal-gnb-link-line"></span> |
||||
</NavLink> |
||||
) : ( |
||||
<button |
||||
type="button" |
||||
className="pal-gnb-link" |
||||
ref={(el) => { |
||||
navRefs.current[item.key] = el; |
||||
}} |
||||
aria-expanded={isActive} |
||||
aria-haspopup="true" |
||||
onFocus={() => openMenu(item.key)} |
||||
onClick={() => openMenu(item.key)} |
||||
onKeyDown={(e) => handleNavKeyDown(e, index, item)} |
||||
> |
||||
<span className="pal-gnb-link-text">{item.label}</span> |
||||
<span className="pal-gnb-link-line"></span> |
||||
</button> |
||||
)} |
||||
</li> |
||||
); |
||||
})} |
||||
</ul> |
||||
</nav> |
||||
|
||||
<div className="pal-header-util"> |
||||
<Link to="/contact" className="pal-header-contact" onClick={closeAllMenus}> |
||||
Contact |
||||
</Link> |
||||
|
||||
<button type="button" className={`pal-header-hamburger ${isMobileMenuOpen ? "is-active" : ""}`} aria-label={isMobileMenuOpen ? "모바일 메뉴 닫기" : "모바일 메뉴 열기"} aria-expanded={isMobileMenuOpen} aria-controls="pal-mobile-menu" onClick={toggleMobileMenu}> |
||||
<span></span> |
||||
<span></span> |
||||
<span></span> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
|
||||
<div |
||||
className={`pal-mega-panel ${showPanel ? "is-visible" : ""}`} |
||||
onMouseEnter={() => { |
||||
clearCloseTimer(); |
||||
setIsHeaderHover(true); |
||||
}} |
||||
onMouseLeave={scheduleClose} |
||||
> |
||||
{activeData && !activeData.simple && ( |
||||
<div className="pal-mega-panel-inner"> |
||||
<div className="pal-mega-panel-intro"> |
||||
<span className="pal-mega-panel-eyebrow">{activeData.panelTitle}</span> |
||||
<h2>{activeData.featured.title}</h2> |
||||
<p>{activeData.featured.text}</p> |
||||
<Link to={activeData.featured.cta.to} className="pal-mega-panel-cta" onClick={closeAllMenus}> |
||||
{activeData.featured.cta.label} |
||||
</Link> |
||||
</div> |
||||
|
||||
<div className="pal-mega-panel-content"> |
||||
<div className="pal-mega-panel-top"> |
||||
<strong>{activeData.panelTitle}</strong> |
||||
<p>{activeData.panelDesc}</p> |
||||
</div> |
||||
|
||||
<div className="pal-mega-panel-grid"> |
||||
{activeData.sections.map((section) => ( |
||||
<div className="pal-mega-section" key={section.title}> |
||||
<h3>{section.title}</h3> |
||||
<ul> |
||||
{section.items.map((item) => ( |
||||
<li key={item.label}> |
||||
<Link to={item.to} className="pal-mega-item" onClick={closeAllMenus}> |
||||
<span className="pal-mega-item-title">{item.label}</span> |
||||
<span className="pal-mega-item-desc">{item.desc}</span> |
||||
</Link> |
||||
</li> |
||||
))} |
||||
</ul> |
||||
</div> |
||||
))} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
)} |
||||
</div> |
||||
</header> |
||||
|
||||
<button type="button" className={`pal-header-dim ${showPanel ? "is-visible" : ""}`} aria-label="메뉴 닫기" onClick={closeDesktopMenu}></button> |
||||
|
||||
<div className={`pal-mobile-dim ${isMobileMenuOpen ? "is-visible" : ""}`} onClick={closeAllMenus}></div> |
||||
|
||||
<aside id="pal-mobile-menu" className={`pal-mobile-menu ${isMobileMenuOpen ? "is-open" : ""}`} aria-hidden={!isMobileMenuOpen}> |
||||
<div className="pal-mobile-menu-head"> |
||||
<strong>MENU</strong> |
||||
<button type="button" className="pal-mobile-menu-close" aria-label="모바일 메뉴 닫기" onClick={closeAllMenus} ref={mobileFirstFocusableRef}> |
||||
<span></span> |
||||
<span></span> |
||||
</button> |
||||
</div> |
||||
|
||||
<div className="pal-mobile-menu-body"> |
||||
<ul className="pal-mobile-nav"> |
||||
{menuData.map((menu) => { |
||||
const isOpen = mobileOpenKey === menu.key; |
||||
|
||||
return ( |
||||
<li className={`pal-mobile-nav-item ${isOpen ? "is-open" : ""}`} key={menu.key}> |
||||
{menu.simple ? ( |
||||
<Link to={menu.to} className="pal-mobile-nav-link" onClick={closeAllMenus}> |
||||
<span>{menu.label}</span> |
||||
</Link> |
||||
) : ( |
||||
<> |
||||
<button type="button" className="pal-mobile-nav-toggle" onClick={() => handleMobileAccordion(menu.key)} aria-expanded={isOpen}> |
||||
<span>{menu.label}</span> |
||||
<i className="pal-mobile-nav-arrow"></i> |
||||
</button> |
||||
|
||||
<div className="pal-mobile-submenu"> |
||||
{menu.sections.map((section) => ( |
||||
<div className="pal-mobile-submenu-group" key={section.title}> |
||||
<h3>{section.title}</h3> |
||||
<ul> |
||||
{section.items.map((item) => ( |
||||
<li key={item.label}> |
||||
<Link to={item.to} className="pal-mobile-submenu-link" onClick={closeAllMenus}> |
||||
<strong>{item.label}</strong> |
||||
<p>{item.desc}</p> |
||||
</Link> |
||||
</li> |
||||
))} |
||||
</ul> |
||||
</div> |
||||
))} |
||||
|
||||
<Link to={menu.featured.cta.to} className="pal-mobile-featured-link" onClick={closeAllMenus}> |
||||
<span>{menu.featured.eyebrow}</span> |
||||
<strong>{menu.featured.cta.label}</strong> |
||||
</Link> |
||||
</div> |
||||
</> |
||||
)} |
||||
</li> |
||||
); |
||||
})} |
||||
</ul> |
||||
|
||||
<div className="pal-mobile-contact-box"> |
||||
<p>프로젝트 문의 및 협업 상담이 필요하시면 연락해 주세요.</p> |
||||
<Link to="/contact" className="pal-mobile-contact-link" onClick={closeAllMenus}> |
||||
Contact Us |
||||
</Link> |
||||
</div> |
||||
</div> |
||||
</aside> |
||||
</> |
||||
); |
||||
} |
||||
@ -0,0 +1,17 @@
|
||||
import { Outlet } from "react-router-dom"; |
||||
import Header from "./Header"; |
||||
import Footer from "./Footer"; |
||||
|
||||
function MainLayout() { |
||||
return ( |
||||
<> |
||||
<Header /> |
||||
<main className="main-layout"> |
||||
<Outlet /> |
||||
</main> |
||||
<Footer /> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
export default MainLayout; |
||||
@ -0,0 +1,17 @@
|
||||
import { Outlet } from "react-router-dom"; |
||||
import Header from "./Header"; |
||||
import Footer from "./Footer"; |
||||
|
||||
function SubLayout() { |
||||
return ( |
||||
<> |
||||
<Header /> |
||||
<main className="sub-layout"> |
||||
<Outlet /> |
||||
</main> |
||||
<Footer /> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
export default SubLayout; |
||||
@ -0,0 +1,298 @@
|
||||
:root{ |
||||
--header-height:96px; |
||||
--color-primary:#3A4081; |
||||
--color-primary-hover:#2F5DAA; |
||||
--color-primary-light:#6B78B5; |
||||
--color-primary-soft:rgba(58,64,129,.08); |
||||
--color-primary-soft-strong:rgba(58,64,129,.12); |
||||
--color-primary-soft-border:rgba(58,64,129,.16); |
||||
--color-primary-border:rgba(58,64,129,.2); |
||||
--color-primary-border-strong:rgba(58,64,129,.24); |
||||
--color-primary-shadow:rgba(58,64,129,.18); |
||||
} |
||||
|
||||
html{scrollbar-gutter:inherit;} |
||||
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);} |
||||
|
||||
/*header.*/ |
||||
.pal-header{position:fixed;top:0;left:0;right:0;z-index:1000;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;transition:opacity .35s ease;} |
||||
.pal-header.is-scrolled::before,.pal-header.is-open::before{opacity:1;} |
||||
.pal-header.is-open{box-shadow:0 18px 40px rgba(15,23,42,.08);} |
||||
|
||||
.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;} |
||||
.pal-header-logo a{display:inline-flex;align-items:center;font-size:28px;font-weight:800;line-height:1;color:#111;text-decoration:none;letter-spacing:-0.03em;} |
||||
.pal-header-logo img{width:180px} |
||||
|
||||
.pal-gnb{flex:1 1 auto;display:flex;justify-content:center;height:100%;} |
||||
.pal-gnb-depth1{display:flex;align-items:center;gap:4px;height:100%;margin:0;padding:0;list-style:none;} |
||||
.pal-gnb-item{position:relative;height:100%;} |
||||
.pal-gnb-link{position:relative;display:inline-flex;align-items:center;justify-content:center;height:100%;padding:0 20px;border:0;background:none;color:#111;text-decoration:none;cursor:pointer;transition:color .3s ease,transform .3s ease;} |
||||
.pal-gnb-link:hover,.pal-gnb-link:focus-visible{color:var(--color-primary);transform:translateY(-2px);outline:none;} |
||||
.pal-gnb-link-text{position:relative;z-index:1;font-size:16px;font-weight:700;line-height:1;letter-spacing:-0.02em;} |
||||
.pal-gnb-link-line{position:absolute;left:20px;right:20px;bottom:26px;height:2px;border-radius:999px;background:var(--color-primary);transform:scaleX(0);transform-origin:center;transition:transform .32s ease;} |
||||
.pal-gnb-item.is-active .pal-gnb-link{color:var(--color-primary);transform:translateY(-2px);} |
||||
.pal-gnb-item.is-active .pal-gnb-link-line{transform:scaleX(1);} |
||||
|
||||
.pal-header-util{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:12px;min-width:120px;} |
||||
.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: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;} |
||||
.pal-header-hamburger span:nth-child(2){top:22px;} |
||||
.pal-header-hamburger span:nth-child(3){top:29px;} |
||||
.pal-header-hamburger.is-active span:nth-child(1){top:22px;transform:translateX(-50%) rotate(45deg);background:var(--color-primary);} |
||||
.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);} |
||||
|
||||
.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-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;} |
||||
|
||||
.pal-mega-panel-intro{display:flex;flex-direction:column;justify-content:space-between;min-height:340px;padding:32px;border:1px solid rgba(17,17,17,.06);border-radius:32px;background:linear-gradient(180deg,var(--color-primary-soft-strong) 0%,rgba(42,145,201,.05) 100%);box-shadow:0 24px 50px rgba(15,23,42,.08);} |
||||
.pal-mega-panel-eyebrow{display:inline-block;margin-bottom:14px;font-size:13px;font-weight:700;line-height:1.2;color:var(--color-primary);letter-spacing:.08em;text-transform:uppercase;} |
||||
.pal-mega-panel-intro h2{margin:0 0 16px;font-size:34px;font-weight:800;line-height:1.25;color:#111;letter-spacing:-0.03em;white-space:pre-line;} |
||||
.pal-mega-panel-intro p{margin:0 0 24px;font-size:15px;font-weight:400;line-height:1.7;color:#555;word-break:keep-all;} |
||||
.pal-mega-panel-cta{display:inline-flex;align-items:center;justify-content:center;align-self:flex-start;height:46px;padding:0 18px;border-radius:999px;background:var(--color-primary);color:#fff;font-size:14px;font-weight:700;line-height:1;text-decoration:none;transition:background .3s ease,transform .3s ease,box-shadow .3s ease;} |
||||
.pal-mega-panel-cta:hover,.pal-mega-panel-cta:focus-visible{background:var(--color-primary-light);transform:translateY(-2px);box-shadow:0 12px 24px rgba(42,145,201,.22);outline:none;} |
||||
|
||||
.pal-mega-panel-content{padding:16px 0 0;} |
||||
.pal-mega-panel-top{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;padding:0 8px 24px;border-bottom:1px solid rgba(17,17,17,.08);} |
||||
.pal-mega-panel-top strong{font-size:28px;font-weight:800;line-height:1.2;color:#111;letter-spacing:-0.03em;} |
||||
.pal-mega-panel-top p{max-width:520px;margin:0;font-size:15px;line-height:1.7;color:#666;word-break:keep-all;text-align:right;} |
||||
|
||||
.pal-mega-panel-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px;padding-top:24px;} |
||||
.pal-mega-section{padding:8px;} |
||||
.pal-mega-section h3{margin:0 0 14px;font-size:15px;font-weight:800;line-height:1.4;color:#111;letter-spacing:-0.02em;} |
||||
.pal-mega-section ul{margin:0;padding:0;list-style:none;} |
||||
.pal-mega-section li + li{margin-top:10px;} |
||||
|
||||
.pal-mega-item{display:block;padding:18px 20px;border:1px solid rgba(17,17,17,.06);border-radius:22px;background:#fff;text-decoration:none;transition:border-color .28s ease,transform .28s ease,box-shadow .28s ease,background .28s ease;} |
||||
.pal-mega-item:hover,.pal-mega-item:focus-visible{border-color:var(--color-primary-border);background:#fff;transform:translateY(-3px);box-shadow:0 18px 36px rgba(15,23,42,.08);outline:none;} |
||||
.pal-mega-item-title{display:block;margin-bottom:6px;font-size:16px;font-weight:700;line-height:1.4;color:#111;letter-spacing:-0.02em;transition:color .28s ease;} |
||||
.pal-mega-item-desc{display:block;font-size:14px;line-height:1.6;color:#666;word-break:keep-all;} |
||||
.pal-mega-item:hover .pal-mega-item-title,.pal-mega-item:focus-visible .pal-mega-item-title{color:var(--color-primary);} |
||||
|
||||
.pal-header-dim{position:fixed;left:0;top:96px;width:100%;height:calc(100vh - 96px);z-index:999;border:0;background:rgba(17,17,17,.32);opacity:0;visibility:hidden;pointer-events:none;transition:opacity .28s ease,visibility .28s ease;} |
||||
.pal-header-dim.is-visible{opacity:1;visibility:visible;pointer-events:auto;} |
||||
|
||||
.pal-mobile-dim{position:fixed;inset:0;z-index:1198;background:rgba(17,17,17,.42);opacity:0;visibility:hidden;pointer-events:none;transition:opacity .28s ease,visibility .28s ease;} |
||||
.pal-mobile-dim.is-visible{opacity:1;visibility:visible;pointer-events:auto;} |
||||
|
||||
.pal-mobile-menu{position:fixed;top:0;right:0;z-index:1199;width:min(92vw,420px);height:100vh;background:#fff;box-shadow:-24px 0 60px rgba(15,23,42,.14);transform:translateX(100%);transition:transform .34s ease;display:flex;flex-direction:column;} |
||||
.pal-mobile-menu.is-open{transform:translateX(0);} |
||||
|
||||
.pal-mobile-menu-head{display:flex;align-items:center;justify-content:space-between;min-height:88px;padding:24px 24px 20px;border-bottom:1px solid rgba(17,17,17,.08);} |
||||
.pal-mobile-menu-head strong{font-size:14px;font-weight:800;line-height:1;color:var(--color-primary);letter-spacing:.12em;} |
||||
.pal-mobile-menu-close{position:relative;display:inline-flex;align-items:center;justify-content:center;width:42px;height:42px;padding:0;border:1px solid rgba(17,17,17,.08);border-radius:14px;background:#fff;cursor:pointer;transition:border-color .28s ease,transform .28s ease;} |
||||
.pal-mobile-menu-close:hover,.pal-mobile-menu-close:focus-visible{border-color:var(--color-primary-border-strong);transform:translateY(-2px);outline:none;} |
||||
.pal-mobile-menu-close span{position:absolute;width:18px;height:2px;border-radius:999px;background:#111;} |
||||
.pal-mobile-menu-close span:first-child{transform:rotate(45deg);} |
||||
.pal-mobile-menu-close span:last-child{transform:rotate(-45deg);} |
||||
|
||||
.pal-mobile-menu-body{flex:1 1 auto;overflow-y:auto;padding:10px 20px 24px;} |
||||
.pal-mobile-nav{margin:0;padding:0;list-style:none;} |
||||
.pal-mobile-nav-item{border-bottom:1px solid rgba(17,17,17,.06);} |
||||
.pal-mobile-nav-link,.pal-mobile-nav-toggle{display:flex;align-items:center;justify-content:space-between;width:100%;padding:20px 4px;border:0;background:none;color:#111;text-align:left;text-decoration:none;cursor:pointer;} |
||||
.pal-mobile-nav-link span,.pal-mobile-nav-toggle span{font-size:20px;font-weight:800;line-height:1.3;letter-spacing:-0.03em;} |
||||
.pal-mobile-nav-toggle:focus-visible,.pal-mobile-nav-link:focus-visible{outline:none;} |
||||
.pal-mobile-nav-arrow{position:relative;display:block;flex:0 0 auto;width:18px;height:18px;} |
||||
.pal-mobile-nav-arrow::before,.pal-mobile-nav-arrow::after{content:"";position:absolute;top:8px;width:10px;height:2px;border-radius:999px;background:#111;transition:transform .28s ease,background .28s ease;} |
||||
.pal-mobile-nav-arrow::before{left:0;transform:rotate(45deg);} |
||||
.pal-mobile-nav-arrow::after{right:0;transform:rotate(-45deg);} |
||||
.pal-mobile-nav-item.is-open .pal-mobile-nav-toggle span{color:var(--color-primary);} |
||||
.pal-mobile-nav-item.is-open .pal-mobile-nav-arrow::before{background:var(--color-primary);transform:rotate(-45deg);} |
||||
.pal-mobile-nav-item.is-open .pal-mobile-nav-arrow::after{background:var(--color-primary);transform:rotate(45deg);} |
||||
|
||||
.pal-mobile-submenu{max-height:0;overflow:hidden;transition:max-height .36s ease;padding:0 2px;} |
||||
.pal-mobile-nav-item.is-open .pal-mobile-submenu{max-height:1200px;padding-bottom:20px;} |
||||
|
||||
.pal-mobile-submenu-group + .pal-mobile-submenu-group{margin-top:20px;} |
||||
.pal-mobile-submenu-group h3{margin:0 0 10px;font-size:13px;font-weight:800;line-height:1.4;color:var(--color-primary);letter-spacing:.08em;text-transform:uppercase;} |
||||
.pal-mobile-submenu-group ul{margin:0;padding:0;list-style:none;} |
||||
.pal-mobile-submenu-group li + li{margin-top:10px;} |
||||
|
||||
.pal-mobile-submenu-link{display:block;padding:16px 16px 15px;border:1px solid rgba(17,17,17,.06);border-radius:18px;background:#fafafa;text-decoration:none;transition:border-color .28s ease,transform .28s ease,box-shadow .28s ease,background .28s ease;} |
||||
.pal-mobile-submenu-link:hover,.pal-mobile-submenu-link:focus-visible{border-color:var(--color-primary-soft-border);background:#fff;transform:translateY(-2px);box-shadow:0 12px 24px rgba(15,23,42,.06);outline:none;} |
||||
.pal-mobile-submenu-link strong{display:block;font-size:15px;font-weight:700;line-height:1.4;color:#111;letter-spacing:-0.02em;} |
||||
.pal-mobile-submenu-link p{margin:4px 0 0;font-size:13px;line-height:1.6;color:#666;word-break:keep-all;} |
||||
|
||||
.pal-mobile-featured-link{display:block;margin-top:18px;padding:18px;border-radius:22px;background:linear-gradient(180deg,var(--color-primary-soft-strong) 0%,rgba(42,145,201,.05) 100%);text-decoration:none;} |
||||
.pal-mobile-featured-link span{display:block;margin-bottom:8px;font-size:12px;font-weight:800;line-height:1.2;color:var(--color-primary);letter-spacing:.08em;text-transform:uppercase;} |
||||
.pal-mobile-featured-link strong{display:block;font-size:16px;font-weight:800;line-height:1.5;color:#111;letter-spacing:-0.02em;} |
||||
|
||||
.pal-mobile-contact-box{margin-top:28px;padding:20px;border:1px solid rgba(17,17,17,.06);border-radius:24px;background:#fff;} |
||||
.pal-mobile-contact-box p{margin:0 0 14px;font-size:14px;line-height:1.7;color:#555;word-break:keep-all;} |
||||
.pal-mobile-contact-link{display:inline-flex;align-items:center;justify-content:center;height:44px;padding:0 16px;border-radius:999px;background:var(--color-primary);color:#fff;font-size:14px;font-weight:700;line-height:1;text-decoration:none;transition:background .28s ease,transform .28s ease,box-shadow .28s ease;} |
||||
.pal-mobile-contact-link:hover,.pal-mobile-contact-link:focus-visible{background:var(--color-primary-light);transform:translateY(-2px);box-shadow:0 12px 24px rgba(42,145,201,.22);outline:none;} |
||||
|
||||
@media (max-width:1440px){ |
||||
.pal-header-inner{padding:0 24px;} |
||||
.pal-mega-panel-inner{padding:24px 24px 24px;} |
||||
} |
||||
|
||||
@media (max-width:1200px){ |
||||
.pal-mega-panel-inner{grid-template-columns:320px 1fr;gap:24px;} |
||||
.pal-mega-panel-intro h2{font-size:28px;} |
||||
.pal-mega-panel-top{flex-direction:column;align-items:flex-start;} |
||||
.pal-mega-panel-top p{max-width:none;text-align:left;} |
||||
} |
||||
|
||||
@media (max-width:1024px){ |
||||
.pal-header-inner{height:82px;padding:0 20px;} |
||||
.pal-header-logo a{font-size:24px;} |
||||
.pal-gnb{display:none;} |
||||
.pal-header-contact{display:none;} |
||||
.pal-header-hamburger{display:inline-flex;} |
||||
.pal-header-util{min-width:auto;} |
||||
.pal-mega-panel{display:none;} |
||||
.pal-header-dim{display:none;} |
||||
} |
||||
|
||||
@media (max-width:767px){ |
||||
.pal-mobile-menu{width:100vw;} |
||||
.pal-mobile-menu-head{min-height:82px;padding:20px 20px 16px;} |
||||
.pal-mobile-menu-body{padding:8px 16px 20px;} |
||||
.pal-mobile-nav-link span,.pal-mobile-nav-toggle span{font-size:18px;} |
||||
.pal-mobile-submenu-link{padding:14px 14px 13px;border-radius:16px;} |
||||
.pal-mobile-contact-box{padding:18px;border-radius:20px;} |
||||
} |
||||
|
||||
/*main visual*/ |
||||
|
||||
.main-page{position:relative;background:linear-gradient(180deg,#f7f9ff 0%,#eef2fb 52%,#f8faff 100%);color:#111827;overflow:hidden;} |
||||
.main-section{position:relative;width:100%;} |
||||
.section-inner{position:relative;width:100%;max-width:1400px;margin:0 auto;padding:0 56px;} |
||||
.section-head,.split-head,.showcase-head{margin-bottom:72px;} |
||||
.section-kicker{display:inline-block;margin-bottom:18px;font-size:12px;font-weight:700;letter-spacing:.22em;text-transform:uppercase;color:rgba(58,64,129,.58);} |
||||
.section-title{margin:0;font-size:clamp(38px,5vw,72px);line-height:.98;font-weight:700;letter-spacing:-.04em;color:#111827;} |
||||
.section-desc{margin:20px 0 0;font-size:17px;line-height:1.8;color:rgba(17,24,39,.68);} |
||||
|
||||
.section-hero{position:relative;min-height:calc(100vh - var(--header-height));display:flex;align-items:center;overflow:hidden;background:linear-gradient(135deg,rgba(58,64,129,.06) 0%,rgba(58,64,129,.02) 36%,rgba(255,255,255,.85) 100%);} |
||||
.hero-inner{position:relative;display:grid;grid-template-columns:minmax(0,1.15fr) minmax(320px,.85fr);align-items:center;gap:40px;min-height:calc(100vh - var(--header-height));padding-top:40px;padding-bottom:60px;} |
||||
.hero-title-block{position:relative;z-index:3;max-width:900px;} |
||||
.hero-mini{display:block;margin-bottom:22px;font-size:12px;font-weight:700;letter-spacing:.22em;text-transform:uppercase;color:rgba(58,64,129,.72);} |
||||
.hero-title{margin:0;} |
||||
.hero-title-fill{display:flex;flex-direction:column;gap:2px;} |
||||
.hero-title-line{position:relative;display:block;font-size:clamp(64px,10vw,168px);line-height:.9;font-weight:800;letter-spacing:-.07em;} |
||||
.hero-title-outline{display:block;color:transparent;-webkit-text-stroke:1px rgba(58,64,129,.26);} |
||||
.hero-title-solid{position:absolute;left:0;top:0;display:block;color:var(--color-primary);white-space:nowrap;overflow:hidden;} |
||||
.hero-sub{max-width:640px;margin:30px 0 0;font-size:18px;line-height:1.8;color:rgba(17,24,39,.72);} |
||||
.hero-meta{display:flex;flex-wrap:wrap;gap:10px;margin-top:30px;} |
||||
.hero-meta span{display:inline-flex;align-items:center;height:38px;padding:0 16px;border:1px solid var(--color-primary-border);border-radius:999px;background:rgba(255,255,255,.72);backdrop-filter:blur(8px);font-size:12px;font-weight:700;letter-spacing:.14em;color:var(--color-primary);} |
||||
.hero-side-visual{position:relative;z-index:2;display:flex;flex-direction:column;align-items:flex-end;gap:16px;perspective:1000px;} |
||||
.hero-side-card{width:min(100%,360px);padding:24px 24px 26px;border:1px solid rgba(58,64,129,.12);border-radius:28px;background:rgba(255,255,255,.68);box-shadow:0 24px 60px rgba(58,64,129,.10);backdrop-filter:blur(14px);} |
||||
.hero-side-label{display:block;margin-bottom:16px;font-size:12px;font-weight:700;letter-spacing:.18em;color:rgba(58,64,129,.55);} |
||||
.hero-side-card strong{display:block;font-size:26px;line-height:1.04;letter-spacing:-.04em;color:#111827;} |
||||
.hero-side-card-a{margin-right:72px;} |
||||
.hero-side-card-b{margin-right:8px;} |
||||
.hero-side-card-c{margin-right:96px;} |
||||
.hero-grid{position:absolute;inset:0;z-index:0;background-image:linear-gradient(rgba(58,64,129,.06) 1px,transparent 1px),linear-gradient(90deg,rgba(58,64,129,.06) 1px,transparent 1px);background-size:56px 56px;mask-image:radial-gradient(circle at 50% 50%,rgba(0,0,0,1) 0%,rgba(0,0,0,.34) 70%,rgba(0,0,0,.08) 100%);} |
||||
.hero-orb{position:absolute;border-radius:50%;filter:blur(10px);pointer-events:none;z-index:1;} |
||||
.hero-orb-a{right:10%;top:12%;width:420px;height:420px;background:radial-gradient(circle,rgba(58,64,129,.18) 0%,rgba(58,64,129,.06) 42%,rgba(58,64,129,0) 72%);} |
||||
.hero-orb-b{left:-4%;bottom:-10%;width:520px;height:520px;background:radial-gradient(circle,rgba(107,120,181,.18) 0%,rgba(107,120,181,.08) 38%,rgba(107,120,181,0) 70%);} |
||||
|
||||
.section-showcase{height:360vh;background:linear-gradient(180deg,#f8faff 0%,#eef2fb 100%);} |
||||
.showcase-sticky{position:relative;height:100vh;} |
||||
.showcase-shell{display:flex;flex-direction:column;justify-content:center;height:100vh;overflow:hidden;} |
||||
.showcase-head{margin-bottom:48px;} |
||||
.showcase-rail-wrap{position:relative;padding-left:56px;padding-right:56px;} |
||||
.showcase-rail{display:flex;align-items:stretch;gap:24px;width:max-content;will-change:transform;} |
||||
.showcase-card-item{position:relative;flex:0 0 480px;min-height:360px;padding:32px;border:1px solid rgba(58,64,129,.12);border-radius:32px;background:linear-gradient(180deg,rgba(255,255,255,.92) 0%,rgba(244,247,255,.86) 100%);box-shadow:0 26px 70px rgba(58,64,129,.10);overflow:hidden;} |
||||
.showcase-card-item::before{content:'';position:absolute;right:-40px;top:-30px;width:180px;height:180px;border-radius:50%;background:radial-gradient(circle,rgba(58,64,129,.12) 0%,rgba(58,64,129,0) 72%);} |
||||
.showcase-index{display:block;margin-bottom:52px;font-size:12px;font-weight:700;letter-spacing:.2em;color:rgba(58,64,129,.5);} |
||||
.showcase-card-item strong{display:block;max-width:280px;font-size:34px;line-height:1.04;font-weight:700;letter-spacing:-.04em;color:#111827;} |
||||
.showcase-card-item p{max-width:300px;margin:22px 0 0;font-size:16px;line-height:1.8;color:rgba(17,24,39,.68);} |
||||
|
||||
.section-immersive{height:280vh;background:linear-gradient(180deg,#eef2fb 0%,#e8eef9 100%);} |
||||
.immersive-sticky{position:relative;height:100vh;} |
||||
.immersive-sticky-inner{position:relative;height:100vh;padding:80px 56px;display:grid;grid-template-columns:1fr 1.2fr 1fr;align-items:center;gap:32px;max-width:1600px;margin:0 auto;} |
||||
.immersive-copy{position:relative;z-index:2;} |
||||
.immersive-label{display:block;margin-bottom:18px;font-size:12px;font-weight:700;letter-spacing:.22em;color:rgba(58,64,129,.5);} |
||||
.immersive-copy h2{margin:0;font-size:clamp(34px,4.2vw,68px);line-height:1.05;font-weight:700;letter-spacing:-.04em;color:#111827;} |
||||
.immersive-copy p{margin:0;font-size:17px;line-height:1.9;color:rgba(17,24,39,.7);} |
||||
.immersive-visual{display:flex;align-items:center;justify-content:center;} |
||||
.visual-frame{position:relative;width:min(42vw,700px);aspect-ratio:1/1.08;border-radius:36px;border:1px solid rgba(58,64,129,.12);background:linear-gradient(180deg,rgba(255,255,255,.72) 0%,rgba(241,245,255,.92) 100%);overflow:hidden;box-shadow:0 40px 120px rgba(58,64,129,.14);} |
||||
.visual-grid{position:absolute;inset:0;background-image:linear-gradient(rgba(58,64,129,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(58,64,129,.08) 1px,transparent 1px);background-size:40px 40px;mask-image:linear-gradient(180deg,rgba(0,0,0,1) 0%,rgba(0,0,0,.35) 100%);} |
||||
.visual-core{position:absolute;left:50%;top:50%;display:flex;flex-direction:column;align-items:center;gap:14px;transform:translate(-50%,-50%);text-align:center;} |
||||
.visual-core span{font-size:clamp(28px,4vw,64px);font-weight:800;letter-spacing:-.05em;color:var(--color-primary);} |
||||
.visual-core small{font-size:12px;font-weight:700;letter-spacing:.18em;color:rgba(58,64,129,.58);} |
||||
|
||||
.section-split{padding:200px 0 240px;background:linear-gradient(180deg,#f8faff 0%,#eef2fb 100%);} |
||||
.split-stage{position:relative;display:grid;grid-template-columns:1fr minmax(320px,420px) 1fr;align-items:center;gap:28px;max-width:1400px;margin:0 auto;padding:0 56px;} |
||||
.split-panel{min-height:360px;padding:34px;border:1px solid rgba(58,64,129,.12);border-radius:36px;background:linear-gradient(180deg,rgba(255,255,255,.92) 0%,rgba(241,245,255,.84) 100%);box-shadow:0 24px 70px rgba(58,64,129,.10);} |
||||
.split-label{display:block;margin-bottom:24px;font-size:12px;font-weight:700;letter-spacing:.2em;color:rgba(58,64,129,.52);} |
||||
.split-panel strong{display:block;margin-bottom:18px;font-size:clamp(38px,4vw,64px);line-height:.95;font-weight:800;letter-spacing:-.05em;color:#111827;} |
||||
.split-panel p{margin:0;max-width:360px;font-size:16px;line-height:1.8;color:rgba(17,24,39,.68);} |
||||
.split-center-card{position:relative;z-index:2;min-height:300px;padding:30px;border:1px solid rgba(58,64,129,.16);border-radius:32px;background:linear-gradient(180deg,rgba(58,64,129,.98) 0%,rgba(47,93,170,.96) 100%);box-shadow:0 30px 90px rgba(58,64,129,.22);color:#fff;} |
||||
.split-center-kicker{display:block;margin-bottom:18px;font-size:12px;font-weight:700;letter-spacing:.18em;color:rgba(255,255,255,.66);} |
||||
.split-center-card strong{display:block;margin-bottom:18px;font-size:34px;line-height:1.02;font-weight:700;letter-spacing:-.04em;} |
||||
.split-center-card p{margin:0;font-size:15px;line-height:1.8;color:rgba(255,255,255,.82);} |
||||
|
||||
.section-ending{padding:220px 0 240px;background:linear-gradient(180deg,#eef2fb 0%,#f8faff 100%);} |
||||
.ending-inner{text-align:center;} |
||||
.ending-label{display:block;margin-bottom:22px;font-size:12px;font-weight:700;letter-spacing:.24em;color:rgba(58,64,129,.46);} |
||||
.ending-title{display:flex;flex-wrap:wrap;justify-content:center;gap:14px;margin:0;font-size:clamp(42px,6vw,96px);line-height:.95;font-weight:800;letter-spacing:-.06em;color:#111827;} |
||||
.ending-title .word{display:inline-block;} |
||||
.ending-desc{margin:30px 0 0;font-size:18px;line-height:1.9;color:rgba(17,24,39,.68);} |
||||
|
||||
@media (max-width:1280px){ |
||||
.hero-inner{grid-template-columns:1fr;gap:28px;padding-top:80px;padding-bottom:80px;} |
||||
.hero-side-visual{align-items:flex-start;} |
||||
.hero-side-card-a,.hero-side-card-b,.hero-side-card-c{margin-right:0;} |
||||
.showcase-rail-wrap{padding-left:56px;} |
||||
.immersive-sticky-inner{grid-template-columns:1fr;grid-template-rows:auto auto auto;align-content:center;text-align:center;} |
||||
.immersive-copy-top,.immersive-copy-bottom{max-width:820px;margin:0 auto;} |
||||
.visual-frame{width:min(78vw,760px);} |
||||
.split-stage{grid-template-columns:1fr;max-width:880px;} |
||||
.split-center-card{order:2;} |
||||
} |
||||
|
||||
@media (max-width:767px){ |
||||
.section-inner{padding:0 20px;} |
||||
.section-head,.split-head,.showcase-head{margin-bottom:40px;} |
||||
.section-desc{font-size:15px;line-height:1.7;} |
||||
.hero-inner{padding-top:72px;padding-bottom:56px;} |
||||
.hero-title-line{font-size:clamp(52px,15vw,88px);} |
||||
.hero-sub{font-size:15px;line-height:1.7;} |
||||
.hero-meta{gap:8px;} |
||||
.hero-meta span{height:34px;padding:0 12px;font-size:11px;} |
||||
.hero-side-card{width:100%;padding:20px 20px 22px;border-radius:22px;} |
||||
.hero-side-card strong{font-size:22px;} |
||||
.hero-grid{background-size:34px 34px;} |
||||
.hero-orb-a{right:-10%;top:8%;width:260px;height:260px;} |
||||
.hero-orb-b{left:-18%;bottom:-12%;width:320px;height:320px;} |
||||
|
||||
.section-showcase{height:320vh;} |
||||
.showcase-rail-wrap{padding-left:20px;padding-right:20px;} |
||||
.showcase-card-item{flex:0 0 84vw;min-height:300px;padding:24px;border-radius:24px;} |
||||
.showcase-index{margin-bottom:28px;} |
||||
.showcase-card-item strong{font-size:28px;} |
||||
.showcase-card-item p{font-size:15px;line-height:1.7;} |
||||
|
||||
.section-immersive{height:220vh;} |
||||
.immersive-sticky-inner{padding:56px 20px;} |
||||
.visual-frame{width:100%;border-radius:28px;} |
||||
.visual-core small{font-size:11px;} |
||||
|
||||
.section-split{padding:120px 0 140px;} |
||||
.split-stage{padding:0 20px;} |
||||
.split-panel{min-height:auto;padding:24px;border-radius:24px;} |
||||
.split-panel strong{font-size:34px;} |
||||
.split-panel p{font-size:15px;line-height:1.7;} |
||||
.split-center-card{min-height:auto;padding:24px;border-radius:24px;} |
||||
.split-center-card strong{font-size:28px;} |
||||
.split-center-card p{font-size:14px;line-height:1.7;} |
||||
|
||||
.section-ending{padding:140px 0 160px;} |
||||
.ending-desc{font-size:15px;line-height:1.8;} |
||||
} |
||||
@ -0,0 +1,372 @@
|
||||
@charset "UTF-8"; |
||||
|
||||
html { |
||||
-webkit-text-size-adjust: 100%; |
||||
-moz-text-size-adjust: 100%; |
||||
text-size-adjust: 100%; |
||||
scroll-behavior: smooth; |
||||
} |
||||
|
||||
body, |
||||
div, |
||||
dl, |
||||
dt, |
||||
dd, |
||||
ul, |
||||
ol, |
||||
li, |
||||
h1, |
||||
h2, |
||||
h3, |
||||
h4, |
||||
h5, |
||||
h6, |
||||
pre, |
||||
code, |
||||
form, |
||||
fieldset, |
||||
legend, |
||||
input, |
||||
textarea, |
||||
p, |
||||
blockquote, |
||||
th, |
||||
td, |
||||
figure, |
||||
figcaption, |
||||
hr { |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
html, |
||||
body { |
||||
width: 100%; |
||||
min-height: 100%; |
||||
} |
||||
|
||||
body { |
||||
min-height: 100vh; |
||||
font-family: 'Pretendard', 'Noto Sans KR', 'Malgun Gothic', 'Apple SD Gothic Neo', sans-serif; |
||||
font-size: 16px; |
||||
font-weight: 400; |
||||
line-height: 1.5; |
||||
color: #111; |
||||
background: #fff; |
||||
word-break: keep-all; |
||||
word-wrap: break-word; |
||||
-webkit-font-smoothing: antialiased; |
||||
-moz-osx-font-smoothing: grayscale; |
||||
text-rendering: optimizeLegibility; |
||||
} |
||||
|
||||
article, |
||||
aside, |
||||
details, |
||||
figcaption, |
||||
figure, |
||||
footer, |
||||
header, |
||||
hgroup, |
||||
main, |
||||
menu, |
||||
nav, |
||||
section { |
||||
display: block; |
||||
} |
||||
|
||||
*, |
||||
*::before, |
||||
*::after { |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
ul, |
||||
ol, |
||||
li { |
||||
list-style: none; |
||||
} |
||||
|
||||
img, |
||||
picture, |
||||
video, |
||||
canvas, |
||||
svg { |
||||
display: block; |
||||
max-width: 100%; |
||||
height: auto; |
||||
border: 0; |
||||
vertical-align: top; |
||||
} |
||||
|
||||
audio, |
||||
canvas, |
||||
iframe, |
||||
img, |
||||
svg, |
||||
video { |
||||
vertical-align: top; |
||||
} |
||||
|
||||
img { |
||||
image-rendering: auto; |
||||
} |
||||
|
||||
button, |
||||
input, |
||||
select, |
||||
textarea { |
||||
font: inherit; |
||||
color: inherit; |
||||
letter-spacing: inherit; |
||||
} |
||||
|
||||
input, |
||||
select, |
||||
textarea, |
||||
button { |
||||
margin: 0; |
||||
} |
||||
|
||||
button { |
||||
border: 0; |
||||
background: transparent; |
||||
cursor: pointer; |
||||
appearance: none; |
||||
-webkit-appearance: none; |
||||
-moz-appearance: none; |
||||
} |
||||
|
||||
button:disabled { |
||||
cursor: default; |
||||
} |
||||
|
||||
input { |
||||
border: 0; |
||||
border-radius: 0; |
||||
appearance: none; |
||||
-webkit-appearance: none; |
||||
-moz-appearance: none; |
||||
background: transparent; |
||||
} |
||||
|
||||
textarea { |
||||
border: 0; |
||||
resize: none; |
||||
appearance: none; |
||||
-webkit-appearance: none; |
||||
-moz-appearance: none; |
||||
background: transparent; |
||||
} |
||||
|
||||
select { |
||||
border: 0; |
||||
border-radius: 0; |
||||
appearance: none; |
||||
-webkit-appearance: none; |
||||
-moz-appearance: none; |
||||
background: transparent; |
||||
} |
||||
|
||||
input::-ms-clear, |
||||
input::-ms-reveal { |
||||
display: none; |
||||
} |
||||
|
||||
input[type='search']::-webkit-search-decoration, |
||||
input[type='search']::-webkit-search-cancel-button, |
||||
input[type='search']::-webkit-search-results-button, |
||||
input[type='search']::-webkit-search-results-decoration { |
||||
display: none; |
||||
} |
||||
|
||||
input[type='number']::-webkit-outer-spin-button, |
||||
input[type='number']::-webkit-inner-spin-button { |
||||
-webkit-appearance: none; |
||||
margin: 0; |
||||
} |
||||
|
||||
input[type='number'] { |
||||
-moz-appearance: textfield; |
||||
} |
||||
|
||||
input[type='checkbox'], |
||||
input[type='radio'] { |
||||
appearance: auto; |
||||
-webkit-appearance: auto; |
||||
-moz-appearance: auto; |
||||
} |
||||
|
||||
textarea::placeholder, |
||||
input::placeholder { |
||||
color: #999; |
||||
opacity: 1; |
||||
} |
||||
|
||||
input:focus, |
||||
select:focus, |
||||
textarea:focus, |
||||
button:focus { |
||||
outline: none; |
||||
} |
||||
|
||||
a { |
||||
color: inherit; |
||||
text-decoration: none; |
||||
background-color: transparent; |
||||
} |
||||
|
||||
table { |
||||
width: 100%; |
||||
border-collapse: collapse; |
||||
border-spacing: 0; |
||||
table-layout: fixed; |
||||
} |
||||
|
||||
caption { |
||||
overflow: hidden; |
||||
position: absolute; |
||||
width: 1px; |
||||
height: 1px; |
||||
margin: -1px; |
||||
padding: 0; |
||||
border: 0; |
||||
clip: rect(0, 0, 0, 0); |
||||
clip-path: inset(50%); |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
fieldset { |
||||
min-width: 0; |
||||
border: 0; |
||||
} |
||||
|
||||
legend { |
||||
overflow: hidden; |
||||
position: absolute; |
||||
width: 1px; |
||||
height: 1px; |
||||
margin: -1px; |
||||
padding: 0; |
||||
border: 0; |
||||
clip: rect(0, 0, 0, 0); |
||||
clip-path: inset(50%); |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
address, |
||||
caption, |
||||
cite, |
||||
code, |
||||
dfn, |
||||
em, |
||||
strong, |
||||
th, |
||||
var { |
||||
font-style: normal; |
||||
font-weight: inherit; |
||||
} |
||||
|
||||
strong, |
||||
b { |
||||
font-weight: 700; |
||||
} |
||||
|
||||
em, |
||||
i { |
||||
font-style: normal; |
||||
} |
||||
|
||||
hr { |
||||
border: 0; |
||||
border-top: 1px solid #ddd; |
||||
} |
||||
|
||||
pre, |
||||
code, |
||||
kbd, |
||||
samp { |
||||
font-family: inherit; |
||||
} |
||||
|
||||
abbr[title] { |
||||
text-decoration: none; |
||||
} |
||||
|
||||
sub, |
||||
sup { |
||||
position: relative; |
||||
font-size: 75%; |
||||
line-height: 0; |
||||
vertical-align: baseline; |
||||
} |
||||
|
||||
sub { |
||||
bottom: -0.25em; |
||||
} |
||||
|
||||
sup { |
||||
top: -0.5em; |
||||
} |
||||
|
||||
[hidden] { |
||||
display: none !important; |
||||
} |
||||
|
||||
summary { |
||||
display: list-item; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
progress { |
||||
vertical-align: baseline; |
||||
} |
||||
|
||||
template { |
||||
display: none; |
||||
} |
||||
|
||||
[role='button'] { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
:disabled { |
||||
cursor: default; |
||||
} |
||||
|
||||
#root { |
||||
min-height: 100vh; |
||||
} |
||||
|
||||
.blind { |
||||
overflow: hidden; |
||||
position: absolute; |
||||
width: 1px; |
||||
height: 1px; |
||||
margin: -1px; |
||||
padding: 0; |
||||
border: 0; |
||||
clip: rect(0, 0, 0, 0); |
||||
clip-path: inset(50%); |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
.clearfix::after { |
||||
content: ''; |
||||
display: block; |
||||
clear: both; |
||||
} |
||||
|
||||
@media (prefers-reduced-motion:reduce) { |
||||
html { |
||||
scroll-behavior: auto; |
||||
} |
||||
|
||||
*, |
||||
*::before, |
||||
*::after { |
||||
animation-duration: 0.01ms !important; |
||||
animation-iteration-count: 1 !important; |
||||
transition-duration: 0.01ms !important; |
||||
} |
||||
} |
||||
@ -0,0 +1,14 @@
|
||||
import React from "react"; |
||||
import ReactDOM from "react-dom/client"; |
||||
import { BrowserRouter } from "react-router-dom"; |
||||
import App from "./App"; |
||||
import "./css/reset.css"; |
||||
import "./css/common.css"; |
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render( |
||||
<React.StrictMode> |
||||
<BrowserRouter> |
||||
<App /> |
||||
</BrowserRouter> |
||||
</React.StrictMode>, |
||||
); |
||||
@ -0,0 +1,21 @@
|
||||
function AboutPage() { |
||||
return ( |
||||
<main className="sub-page"> |
||||
<section className="sub-visual"> |
||||
<div className="inner"> |
||||
<h2>About</h2> |
||||
<p>서브페이지 테스트 영역</p> |
||||
</div> |
||||
</section> |
||||
|
||||
<section className="sub-content-section"> |
||||
<div className="inner"> |
||||
<h3>About Content</h3> |
||||
<p>라우터 연결 확인용 페이지입니다.</p> |
||||
</div> |
||||
</section> |
||||
</main> |
||||
) |
||||
} |
||||
|
||||
export default AboutPage |
||||
@ -0,0 +1,717 @@
|
||||
import { useLayoutEffect, useRef } from "react"; |
||||
import { gsap } from "gsap"; |
||||
import { ScrollTrigger } from "gsap/ScrollTrigger"; |
||||
|
||||
gsap.registerPlugin(ScrollTrigger); |
||||
|
||||
function MainPage() { |
||||
const pageRef = useRef(null); |
||||
|
||||
useLayoutEffect(() => { |
||||
const ctx = gsap.context(() => { |
||||
ScrollTrigger.getAll().forEach((trigger) => trigger.kill()); |
||||
|
||||
const heroTrigger = ".section-hero"; |
||||
const showcaseTrigger = ".section-showcase"; |
||||
const immersiveTrigger = ".section-immersive"; |
||||
const splitTrigger = ".section-split"; |
||||
const endingTrigger = ".section-ending"; |
||||
|
||||
/* hero - 진입할 때마다 다시 보이도록 fromTo + reverse */ |
||||
gsap.fromTo( |
||||
".hero-title-line", |
||||
{ |
||||
y: 80, |
||||
opacity: 0, |
||||
}, |
||||
{ |
||||
y: 0, |
||||
opacity: 1, |
||||
duration: 0.95, |
||||
stagger: 0.12, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top 70%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".hero-title-solid", |
||||
{ |
||||
clipPath: "inset(0 100% 0 0)", |
||||
}, |
||||
{ |
||||
clipPath: "inset(0 0% 0 0)", |
||||
duration: 1, |
||||
stagger: 0.1, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top 68%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".hero-mini", |
||||
{ |
||||
y: 18, |
||||
opacity: 0, |
||||
}, |
||||
{ |
||||
y: 0, |
||||
opacity: 1, |
||||
duration: 0.65, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top 72%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".hero-sub", |
||||
{ |
||||
y: 28, |
||||
opacity: 0, |
||||
}, |
||||
{ |
||||
y: 0, |
||||
opacity: 1, |
||||
duration: 0.8, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top 70%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".hero-meta span", |
||||
{ |
||||
y: 18, |
||||
opacity: 0, |
||||
}, |
||||
{ |
||||
y: 0, |
||||
opacity: 1, |
||||
duration: 0.55, |
||||
stagger: 0.08, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top 68%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".hero-side-card", |
||||
{ |
||||
y: 42, |
||||
opacity: 0, |
||||
rotateY: 8, |
||||
}, |
||||
{ |
||||
y: 0, |
||||
opacity: 1, |
||||
rotateY: 0, |
||||
duration: 0.8, |
||||
stagger: 0.1, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top 66%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".hero-orb", |
||||
{ |
||||
scale: 0.72, |
||||
opacity: 0, |
||||
}, |
||||
{ |
||||
scale: 1, |
||||
opacity: 1, |
||||
duration: 1.1, |
||||
stagger: 0.12, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top 72%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.to(".hero-title-block", { |
||||
yPercent: -10, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top top", |
||||
end: "bottom top", |
||||
scrub: 0.8, |
||||
}, |
||||
}); |
||||
|
||||
gsap.to(".hero-side-visual", { |
||||
yPercent: -14, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top top", |
||||
end: "bottom top", |
||||
scrub: 1, |
||||
}, |
||||
}); |
||||
|
||||
gsap.to(".hero-grid", { |
||||
yPercent: -8, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top top", |
||||
end: "bottom top", |
||||
scrub: 1.1, |
||||
}, |
||||
}); |
||||
|
||||
gsap.to(".hero-orb-a", { |
||||
xPercent: 10, |
||||
yPercent: -10, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top top", |
||||
end: "bottom top", |
||||
scrub: 1, |
||||
}, |
||||
}); |
||||
|
||||
gsap.to(".hero-orb-b", { |
||||
xPercent: -8, |
||||
yPercent: -12, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: heroTrigger, |
||||
start: "top top", |
||||
end: "bottom top", |
||||
scrub: 1.1, |
||||
}, |
||||
}); |
||||
|
||||
/* showcase - pinned horizontal */ |
||||
const showcaseTl = gsap.timeline({ |
||||
scrollTrigger: { |
||||
trigger: showcaseTrigger, |
||||
start: "top top", |
||||
end: "+=260%", |
||||
scrub: 1, |
||||
pin: ".showcase-sticky", |
||||
anticipatePin: 1, |
||||
}, |
||||
}); |
||||
|
||||
showcaseTl |
||||
.fromTo( |
||||
".showcase-head", |
||||
{ |
||||
y: 40, |
||||
opacity: 0, |
||||
}, |
||||
{ |
||||
y: 0, |
||||
opacity: 1, |
||||
duration: 0.35, |
||||
}, |
||||
) |
||||
.fromTo( |
||||
".showcase-card-item", |
||||
{ |
||||
opacity: 0.35, |
||||
scale: 0.88, |
||||
y: 60, |
||||
}, |
||||
{ |
||||
opacity: 1, |
||||
scale: 1, |
||||
y: 0, |
||||
duration: 0.4, |
||||
stagger: 0.08, |
||||
}, |
||||
0, |
||||
) |
||||
.to( |
||||
".showcase-rail", |
||||
{ |
||||
xPercent: -48, |
||||
duration: 1, |
||||
ease: "none", |
||||
}, |
||||
0, |
||||
); |
||||
|
||||
gsap.to(".showcase-card-item", { |
||||
yPercent: -10, |
||||
stagger: 0.04, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: showcaseTrigger, |
||||
start: "top top", |
||||
end: "+=260%", |
||||
scrub: 1, |
||||
}, |
||||
}); |
||||
|
||||
/* immersive - 기존 무드 유지 */ |
||||
gsap.fromTo( |
||||
".immersive-sticky-inner", |
||||
{ |
||||
scale: 0.88, |
||||
opacity: 0.45, |
||||
}, |
||||
{ |
||||
scale: 1, |
||||
opacity: 1, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: immersiveTrigger, |
||||
start: "top top", |
||||
end: "+=180%", |
||||
scrub: 1, |
||||
pin: ".immersive-sticky", |
||||
anticipatePin: 1, |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".immersive-copy-top", |
||||
{ |
||||
y: 80, |
||||
opacity: 0, |
||||
}, |
||||
{ |
||||
y: -36, |
||||
opacity: 1, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: immersiveTrigger, |
||||
start: "top top", |
||||
end: "+=180%", |
||||
scrub: 1, |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".immersive-copy-bottom", |
||||
{ |
||||
y: 110, |
||||
opacity: 0, |
||||
}, |
||||
{ |
||||
y: -18, |
||||
opacity: 1, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: immersiveTrigger, |
||||
start: "top top", |
||||
end: "+=180%", |
||||
scrub: 1, |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.to(".visual-grid", { |
||||
yPercent: -8, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: immersiveTrigger, |
||||
start: "top top", |
||||
end: "+=180%", |
||||
scrub: 1, |
||||
}, |
||||
}); |
||||
|
||||
/* split - 등장 + 스크롤 중 역동성 */ |
||||
gsap.fromTo( |
||||
".split-head", |
||||
{ |
||||
y: 36, |
||||
opacity: 0, |
||||
}, |
||||
{ |
||||
y: 0, |
||||
opacity: 1, |
||||
duration: 0.8, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: splitTrigger, |
||||
start: "top 76%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".split-panel-left", |
||||
{ |
||||
xPercent: -12, |
||||
opacity: 0, |
||||
rotate: -2, |
||||
}, |
||||
{ |
||||
xPercent: 0, |
||||
opacity: 1, |
||||
rotate: 0, |
||||
duration: 1, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: ".split-stage", |
||||
start: "top 75%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".split-panel-right", |
||||
{ |
||||
xPercent: 12, |
||||
opacity: 0, |
||||
rotate: 2, |
||||
}, |
||||
{ |
||||
xPercent: 0, |
||||
opacity: 1, |
||||
rotate: 0, |
||||
duration: 1, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: ".split-stage", |
||||
start: "top 75%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".split-center-card", |
||||
{ |
||||
y: 60, |
||||
opacity: 0, |
||||
scale: 0.9, |
||||
}, |
||||
{ |
||||
y: 0, |
||||
opacity: 1, |
||||
scale: 1, |
||||
duration: 0.9, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: ".split-stage", |
||||
start: "top 72%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.to(".split-panel-left", { |
||||
yPercent: -10, |
||||
xPercent: -3, |
||||
rotate: -2, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: splitTrigger, |
||||
start: "top bottom", |
||||
end: "bottom top", |
||||
scrub: 1, |
||||
}, |
||||
}); |
||||
|
||||
gsap.to(".split-panel-right", { |
||||
yPercent: 10, |
||||
xPercent: 3, |
||||
rotate: 2, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: splitTrigger, |
||||
start: "top bottom", |
||||
end: "bottom top", |
||||
scrub: 1, |
||||
}, |
||||
}); |
||||
|
||||
gsap.to(".split-center-card", { |
||||
yPercent: -8, |
||||
scale: 1.04, |
||||
ease: "none", |
||||
scrollTrigger: { |
||||
trigger: splitTrigger, |
||||
start: "top bottom", |
||||
end: "bottom top", |
||||
scrub: 1, |
||||
}, |
||||
}); |
||||
|
||||
/* ending - 초기 렌더 말고 스크롤 진입 기준 */ |
||||
gsap.fromTo( |
||||
".ending-label", |
||||
{ |
||||
opacity: 0, |
||||
y: 26, |
||||
}, |
||||
{ |
||||
opacity: 1, |
||||
y: 0, |
||||
duration: 0.6, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: endingTrigger, |
||||
start: "top 78%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".ending-title .word", |
||||
{ |
||||
opacity: 0, |
||||
y: 60, |
||||
}, |
||||
{ |
||||
opacity: 1, |
||||
y: 0, |
||||
duration: 0.85, |
||||
ease: "power4.out", |
||||
stagger: 0.08, |
||||
scrollTrigger: { |
||||
trigger: endingTrigger, |
||||
start: "top 74%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
gsap.fromTo( |
||||
".ending-desc", |
||||
{ |
||||
opacity: 0, |
||||
y: 22, |
||||
}, |
||||
{ |
||||
opacity: 1, |
||||
y: 0, |
||||
duration: 0.7, |
||||
ease: "power3.out", |
||||
scrollTrigger: { |
||||
trigger: endingTrigger, |
||||
start: "top 72%", |
||||
toggleActions: "play none none reverse", |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
ScrollTrigger.refresh(); |
||||
}, pageRef); |
||||
|
||||
return () => { |
||||
ctx.revert(); |
||||
}; |
||||
}, []); |
||||
|
||||
return ( |
||||
<main className="main-page" ref={pageRef}> |
||||
<section className="main-section section-hero"> |
||||
<div className="hero-grid"></div> |
||||
<div className="hero-orb hero-orb-a"></div> |
||||
<div className="hero-orb hero-orb-b"></div> |
||||
|
||||
<div className="section-inner hero-inner"> |
||||
<div className="hero-title-block"> |
||||
<span className="hero-mini">PALNETWORKS RENEWAL</span> |
||||
|
||||
<h1 className="hero-title hero-title-fill"> |
||||
<span className="hero-title-line"> |
||||
<span className="hero-title-outline">STRUCTURED</span> |
||||
<span className="hero-title-solid">STRUCTURED</span> |
||||
</span> |
||||
|
||||
<span className="hero-title-line"> |
||||
<span className="hero-title-outline">MOTION</span> |
||||
<span className="hero-title-solid">MOTION</span> |
||||
</span> |
||||
</h1> |
||||
|
||||
<p className="hero-sub"> |
||||
정적인 소개보다 인터랙션과 패럴랙스로 |
||||
<br /> |
||||
리뉴얼 방향성과 무드를 먼저 보여주는 메인 비주얼 |
||||
</p> |
||||
|
||||
<div className="hero-meta"> |
||||
<span>PARALLAX</span> |
||||
<span>INTERACTION</span> |
||||
<span>VISUAL FLOW</span> |
||||
</div> |
||||
</div> |
||||
|
||||
<div className="hero-side-visual"> |
||||
<div className="hero-side-card hero-side-card-a"> |
||||
<span className="hero-side-label">01</span> |
||||
<strong>Visual Depth</strong> |
||||
</div> |
||||
|
||||
<div className="hero-side-card hero-side-card-b"> |
||||
<span className="hero-side-label">02</span> |
||||
<strong>Scroll Rhythm</strong> |
||||
</div> |
||||
|
||||
<div className="hero-side-card hero-side-card-c"> |
||||
<span className="hero-side-label">03</span> |
||||
<strong>Brand Direction</strong> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
|
||||
<section className="main-section section-showcase"> |
||||
<div className="showcase-sticky"> |
||||
<div className="showcase-shell"> |
||||
<div className="section-inner showcase-head"> |
||||
<span className="section-kicker">SECTION 01</span> |
||||
<h2 className="section-title">Dynamic Scroll Showcase</h2> |
||||
<p className="section-desc">단순한 카드 나열이 아니라, 스크롤 흐름 자체가 메인 비주얼의 연장처럼 느껴지도록 구성한 구간</p> |
||||
</div> |
||||
|
||||
<div className="showcase-rail-wrap"> |
||||
<div className="showcase-rail"> |
||||
<article className="showcase-card-item"> |
||||
<span className="showcase-index">01</span> |
||||
<strong>Refined Direction</strong> |
||||
<p>과하게 복잡하지 않지만 첫 화면에서 분명한 인상을 남기는 인터랙션 구조</p> |
||||
</article> |
||||
|
||||
<article className="showcase-card-item"> |
||||
<span className="showcase-index">02</span> |
||||
<strong>Layered Visual Flow</strong> |
||||
<p>텍스트, 패널, 배경이 각기 다른 속도로 반응하며 화면에 깊이감을 더합니다.</p> |
||||
</article> |
||||
|
||||
<article className="showcase-card-item"> |
||||
<span className="showcase-index">03</span> |
||||
<strong>Clean Motion</strong> |
||||
<p>빠르고 자극적인 효과보다 정돈된 모션 리듬으로 브랜드 톤을 만듭니다.</p> |
||||
</article> |
||||
|
||||
<article className="showcase-card-item"> |
||||
<span className="showcase-index">04</span> |
||||
<strong>White Space First</strong> |
||||
<p>여백과 구조를 먼저 잡고 포인트 컬러는 필요한 부분에만 사용합니다.</p> |
||||
</article> |
||||
|
||||
<article className="showcase-card-item"> |
||||
<span className="showcase-index">05</span> |
||||
<strong>Interactive Mood</strong> |
||||
<p>리뉴얼 방향성 제안용 샘플로도 충분히 설득력 있게 보일 수 있는 무드 중심 섹션</p> |
||||
</article> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
|
||||
<section className="main-section section-immersive"> |
||||
<div className="immersive-sticky"> |
||||
<div className="immersive-sticky-inner"> |
||||
<div className="immersive-copy immersive-copy-top"> |
||||
<span className="immersive-label">SECTION 02</span> |
||||
<h2> |
||||
스크롤에 반응하는 |
||||
<br /> |
||||
집중도 높은 구간 |
||||
</h2> |
||||
</div> |
||||
|
||||
<div className="immersive-visual"> |
||||
<div className="visual-frame"> |
||||
<div className="visual-grid"></div> |
||||
<div className="visual-core"> |
||||
<span>INTERACTION</span> |
||||
<small>VISUAL SYSTEM / SCROLL / RHYTHM</small> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div className="immersive-copy immersive-copy-bottom"> |
||||
<p>이 섹션은 추후 실제 이미지나 UI 캡처, 숫자 데이터, 솔루션 키워드 등으로 교체하기 좋은 구조입니다. 지금은 비주얼 없이도 무드가 살아 있도록 최소한의 구성만 유지했습니다.</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
|
||||
<section className="main-section section-split"> |
||||
<div className="section-inner split-head"> |
||||
<span className="section-kicker">SECTION 03</span> |
||||
<h2 className="section-title">Split Motion Panels</h2> |
||||
<p className="section-desc">의미 없는 겹침 박스 대신, 서로 다른 방향의 패널과 중앙 카드가 한 화면 안에서 움직이며 역동적인 인상을 만드는 섹션</p> |
||||
</div> |
||||
|
||||
<div className="split-stage"> |
||||
<div className="split-panel split-panel-left"> |
||||
<span className="split-label">LEFT PANEL</span> |
||||
<strong>STRUCTURE</strong> |
||||
<p>정돈된 레이아웃과 여백을 기반으로 모션이 들어가도 과해 보이지 않는 화면 구조</p> |
||||
</div> |
||||
|
||||
<div className="split-center-card"> |
||||
<span className="split-center-kicker">CENTER FLOW</span> |
||||
<strong>Balanced Motion</strong> |
||||
<p>패널은 좌우로 긴장감을 만들고, 중앙 카드는 정보의 중심축처럼 보이도록 구성합니다.</p> |
||||
</div> |
||||
|
||||
<div className="split-panel split-panel-right"> |
||||
<span className="split-label">RIGHT PANEL</span> |
||||
<strong>RHYTHM</strong> |
||||
<p>스크롤에 따라 요소마다 반응 속도를 달리 주어 지루하지 않은 인터랙션을 만듭니다.</p> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
|
||||
<section className="main-section section-ending"> |
||||
<div className="section-inner ending-inner"> |
||||
<span className="ending-label">FINAL SECTION</span> |
||||
|
||||
<h2 className="ending-title"> |
||||
<span className="word">SIMPLE.</span> |
||||
<span className="word">MODERN.</span> |
||||
<span className="word">IMMERSIVE.</span> |
||||
</h2> |
||||
|
||||
<p className="ending-desc"> |
||||
메인 비주얼은 과하지 않게, |
||||
<br /> |
||||
이후 섹션에서는 스크롤과 인터랙션으로 브랜드의 감도를 보여주는 구조입니다. |
||||
</p> |
||||
</div> |
||||
</section> |
||||
</main> |
||||
); |
||||
} |
||||
|
||||
export default MainPage; |
||||
@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'vite' |
||||
import react, { reactCompilerPreset } from '@vitejs/plugin-react' |
||||
import babel from '@rolldown/plugin-babel' |
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({ |
||||
plugins: [ |
||||
react(), |
||||
babel({ presets: [reactCompilerPreset()] }) |
||||
], |
||||
}) |
||||