Browse Source

sub

remotes/origin/main
김지은 1 month ago
parent
commit
fdd58e4428
  1. 136
      src/components/SubHero.jsx
  2. 36
      src/components/SubLayout.jsx
  3. 81
      src/css/common.css
  4. 2
      src/css/header.css

136
src/components/SubHero.jsx

@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef } from "react";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import { motion } from "framer-motion";
const menuMap = { const menuMap = {
"/company": { label: "Company" }, "/company": { label: "Company" },
@ -9,106 +10,65 @@ const menuMap = {
"/contact": { label: "Contact Us" }, "/contact": { label: "Contact Us" },
}; };
export default function SubHero({ title, desc, navItems, bgImage }) { export default function SubHero({ title, desc, navItems }) {
const { pathname } = useLocation(); const { pathname } = useLocation();
const topPath = "/" + pathname.split("/")[1];
const topLabel = menuMap[topPath]?.label || "";
const curLabel = navItems?.find((n) => n.to === pathname)?.label || "";
const [dropOpen, setDropOpen] = useState(false);
const [scrollY, setScrollY] = useState(0);
const dropRef = useRef(null);
useEffect(() => {
const onScroll = () => setScrollY(window.scrollY);
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
useEffect(() => {
const handler = (e) => {
if (!dropRef.current?.contains(e.target)) setDropOpen(false);
};
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
const parallaxY = scrollY * 0.45;
const fadeOpacity = Math.max(0, 1 - scrollY / 420);
const titleLines = typeof title === "string" ? title.split("\n") : [title]; const titleLines = typeof title === "string" ? title.split("\n") : [title];
return ( return (
<> <>
<section className="sh2"> <section className="sh4">
<div <div className="sh4-inner">
className="sh2-bg" <motion.span className="sh4-label" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}>
style={{ {menuMap["/" + pathname.split("/")[1]]?.label}
backgroundImage: bgImage ? `url(${bgImage})` : "none", </motion.span>
transform: `translateY(${parallaxY}px) scale(1.18)`,
}}
/>
<div className="sh2-dim" />
<div className="sh2-grain" />
<div className="sh2-glow" />
<div className="sh2-vline sh2-vline--l" />
<div className="sh2-vline sh2-vline--r" />
<div className="sh2-inner" style={{ opacity: fadeOpacity }}>
<nav className="sh2-bc" aria-label="breadcrumb">
<Link to="/main" className="sh2-bc-item">
Home
</Link>
<span className="sh2-bc-sep"></span>
<div className="sh2-bc-drop" ref={dropRef}>
<button className="sh2-bc-btn" onClick={() => setDropOpen((v) => !v)} aria-expanded={dropOpen}>
{topLabel}
<svg className={`sh2-bc-arrow${dropOpen ? " sh2-bc-arrow--open" : ""}`} width="10" height="6" viewBox="0 0 10 6" fill="none">
<path d="M1 1l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
{dropOpen && (
<div className="sh2-bc-menu" role="listbox">
{navItems?.map((item) => (
<Link key={item.to} to={item.to} role="option" className={`sh2-bc-menu-item${item.to === pathname ? " sh2-bc-menu-item--cur" : ""}`} onClick={() => setDropOpen(false)}>
{item.label}
</Link>
))}
</div>
)}
</div>
<span className="sh2-bc-sep"></span>
<span className="sh2-bc-cur">{curLabel}</span>
</nav>
<div className="sh2-title-wrap"> <h1 className="sh4-title">
{titleLines.map((line, i) => ( {titleLines.map((line, li) => (
<h1 key={i} className="sh2-title" style={{ animationDelay: `${i * 0.14}s` }}> <span key={li} className="sh4-title-line">
{line} {line.split("").map((char, ci) => (
</h1> <motion.span
key={ci}
className="sh4-char"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
duration: 0.7,
delay: 0.25 + li * 0.18 + ci * 0.03,
ease: [0.16, 1, 0.3, 1],
}}
>
{char === " " ? "\u00A0" : char}
</motion.span>
))}
</span>
))} ))}
</div> </h1>
{desc && <p className="sh2-desc">{desc}</p>} {desc && (
<div className="sh2-accent-line" /> <motion.p className="sh4-desc" initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.7, delay: 0.8, ease: [0.16, 1, 0.3, 1] }}>
{desc}
</motion.p>
)}
</div> </div>
<div className="sh2-scroll-hint"> <div className="sh4-deco sh4-deco--1" />
<span>scroll</span> <div className="sh4-deco sh4-deco--2" />
<div className="sh2-scroll-bar" />
</div>
</section> </section>
{navItems?.length > 1 && ( {navItems?.length > 1 && (
<nav className="sh2-nav" aria-label="Sub Navigation"> <div className="sh4-nav-wrap">
<div className="sh2-nav-inner"> <nav className="sh4-nav">
{navItems.map((item) => ( {navItems.map((item) => {
<Link key={item.to} to={item.to} className={`sh2-nav-tab${pathname === item.to ? " sh2-nav-tab--active" : ""}`}> const active = pathname === item.to;
{item.label} return (
{pathname === item.to && <span className="sh2-nav-pip" />} <Link key={item.to} to={item.to} className={`sh4-nav-tab${active ? " sh4-nav-tab--active" : ""}`}>
</Link> {active && <motion.span className="sh4-nav-bg" layoutId="sh4-pill" transition={{ type: "spring", stiffness: 380, damping: 34 }} />}
))} <span className="sh4-nav-label">{item.label}</span>
</div> </Link>
</nav> );
})}
</nav>
</div>
)} )}
</> </>
); );

36
src/components/SubLayout.jsx

@ -1,29 +1,29 @@
import { useEffect } from "react"; // import { useEffect } from "react";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import Header from "./Header"; import Header from "./Header";
import Footer from "./Footer"; import Footer from "./Footer";
function SubLayout() { function SubLayout() {
useEffect(() => { // useEffect(() => {
// // //
document.body.classList.add("is-dark-hero"); // document.body.classList.add("is-dark-hero");
// (520px) // // (520px)
const handleScroll = () => { // const handleScroll = () => {
if (window.scrollY > 400) { // if (window.scrollY > 400) {
document.body.classList.remove("is-dark-hero"); // document.body.classList.remove("is-dark-hero");
} else { // } else {
document.body.classList.add("is-dark-hero"); // document.body.classList.add("is-dark-hero");
} // }
}; // };
window.addEventListener("scroll", handleScroll, { passive: true }); // window.addEventListener("scroll", handleScroll, { passive: true });
return () => { // return () => {
window.removeEventListener("scroll", handleScroll); // window.removeEventListener("scroll", handleScroll);
document.body.classList.remove("is-dark-hero"); // document.body.classList.remove("is-dark-hero");
}; // };
}, []); // }, []);
return ( return (
<> <>

81
src/css/common.css

@ -32,4 +32,83 @@ body{overflow-x:hidden;}
.sub-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);}
/*sub common*/ /*sub common*//*sh3 subhero canvas*/
.sh3{position:relative;overflow:hidden;margin-top:calc(-1 * var(--header-height));height:560px;display:flex;align-items:center;}
.sh3-canvas{position:absolute;inset:0;width:100%;height:100%;}
.sh3-inner{position:relative;z-index:2;max-width:1440px;width:100%;margin:0 auto;padding:0 80px;padding-top:var(--header-height);}
.sh3-bc{display:flex;align-items:center;margin-bottom:28px;}
.sh3-bc-item{font-size:12px;font-weight:600;letter-spacing:.08em;color:rgba(30,30,80,.45);text-decoration:none;transition:color .2s;}
.sh3-bc-item:hover{color:var(--color-primary);}
.sh3-bc-sep{margin:0 8px;font-size:12px;color:rgba(30,30,80,.25);}
.sh3-bc-drop{position:relative;}
.sh3-bc-btn{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:rgba(30,30,80,.55);cursor:pointer;padding:4px 10px;border-radius:6px;border:1px solid rgba(58,64,129,.18);background:rgba(255,255,255,.55);backdrop-filter:blur(8px);transition:background .2s,color .2s;}
.sh3-bc-btn:hover{background:rgba(255,255,255,.82);color:var(--color-primary);}
.sh3-bc-arrow{transition:transform .22s ease;color:rgba(58,64,129,.4);}
.sh3-bc-arrow--open{transform:rotate(180deg);}
.sh3-bc-menu{position:absolute;top:calc(100% + 8px);left:0;min-width:180px;background:rgba(255,255,255,.96);backdrop-filter:blur(24px);border:1px solid rgba(58,64,129,.1);border-radius:12px;padding:6px;z-index:200;box-shadow:0 12px 40px rgba(58,64,129,.12);animation:sh3MenuIn .18s ease;}
.sh3-bc-menu-item{display:block;padding:9px 14px;font-size:13px;font-weight:500;color:rgba(58,64,129,.55);border-radius:8px;text-decoration:none;transition:background .15s,color .15s;}
.sh3-bc-menu-item:hover{background:rgba(58,64,129,.06);color:var(--color-primary);}
.sh3-bc-menu-item--cur{color:var(--color-primary);font-weight:700;background:rgba(58,64,129,.08);}
.sh3-bc-cur{font-size:12px;font-weight:600;color:var(--color-primary);}
.sh3-title-wrap{margin-bottom:16px;}
.sh3-title{display:block;font-size:clamp(36px,5vw,64px);font-weight:900;line-height:1.05;letter-spacing:-.055em;color:#1a1a3e;opacity:0;transform:translateY(22px);animation:sh3TitleIn .7s cubic-bezier(.16,1,.3,1) forwards;}
.sh3-desc{font-size:clamp(14px,1.2vw,16px);line-height:1.75;color:rgba(26,26,62,.48);max-width:500px;word-break:keep-all;opacity:0;animation:sh3FadeUp .7s .3s cubic-bezier(.16,1,.3,1) forwards;}
.sh3-nav{position:sticky;top:var(--header-height);z-index:50;background:rgba(255,255,255,.88);backdrop-filter:blur(16px) saturate(180%);border-bottom:1px solid rgba(58,64,129,.08);box-shadow:0 2px 16px rgba(58,64,129,.06);}
.sh3-nav-inner{max-width:1440px;margin:0 auto;padding:0 80px;display:flex;align-items:center;}
.sh3-nav-tab{position:relative;display:inline-flex;align-items:center;height:52px;padding:0 20px;font-size:14px;font-weight:600;color:rgba(17,17,17,.38);text-decoration:none;transition:color .25s;white-space:nowrap;letter-spacing:-.01em;}
.sh3-nav-tab:hover{color:var(--color-primary);}
.sh3-nav-tab--active{color:var(--color-primary);}
.sh3-nav-pip{position:absolute;bottom:0;left:20px;right:20px;height:2px;background:var(--grad-brand-h);border-radius:999px;}
@keyframes sh3MenuIn{
from{opacity:0;transform:translateY(-6px) scale(.97);}
to{opacity:1;transform:translateY(0) scale(1);}
}
@keyframes sh3TitleIn{
to{opacity:1;transform:translateY(0);}
}
@keyframes sh3FadeUp{
from{opacity:0;transform:translateY(12px);}
to{opacity:1;transform:translateY(0);}
}
@media (max-width:1280px){
.sh3-inner{padding-left:48px;padding-right:48px;}
.sh3-nav-inner{padding-left:48px;padding-right:48px;}
}
@media (max-width:1024px){
.sh3-inner{padding-left:32px;padding-right:32px;}
.sh3-nav-inner{padding-left:32px;padding-right:32px;}
}
@media (max-width:768px){
.sh3{height:440px;}
.sh3-inner{padding-left:20px;padding-right:20px;}
.sh3-nav-inner{padding-left:20px;padding-right:20px;}
.sh3-nav-tab{padding:0 14px;font-size:13px;}
}/*sh4 subhero*/
.sh4{position:relative;overflow:hidden;margin-top:calc(-1 * var(--header-height));padding:calc(var(--header-height) + 80px) 80px 100px;background:#fff;}
.sh4-inner{position:relative;z-index:2;max-width:1440px;margin:0 auto;}
.sh4-deco{position:absolute;border-radius:50%;pointer-events:none;filter:blur(80px);z-index:0;}
.sh4-deco--1{width:560px;height:560px;top:-200px;right:-100px;background:radial-gradient(circle,rgba(123,63,160,.08) 0%,transparent 70%);}
.sh4-deco--2{width:400px;height:400px;bottom:-160px;left:-80px;background:radial-gradient(circle,rgba(217,72,137,.07) 0%,transparent 70%);}
.sh4-label{display:inline-block;font-size:12px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;color:rgba(58,64,129,.5);margin-bottom:20px;}
.sh4-title{margin:0 0 24px;font-size:clamp(40px,6vw,80px);font-weight:900;line-height:1.05;letter-spacing:-.055em;}
.sh4-title-line{display:block;}
.sh4-char{display:inline-block;background:linear-gradient(90deg,#3A4081 0%,#7b3fa0 45%,#d94889 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
.sh4-desc{font-size:clamp(14px,1.3vw,17px);line-height:1.8;color:rgba(26,26,58,.45);max-width:540px;word-break:keep-all;margin:0;}
/*sh4 floating pill nav*/
.sh4-nav-wrap{position:sticky;top:var(--header-height);z-index:50;display:flex;justify-content:center;padding:14px 0;background:rgba(255,255,255,.82);backdrop-filter:blur(16px);border-bottom:1px solid rgba(58,64,129,.06);}
.sh4-nav{display:inline-flex;align-items:center;gap:4px;background:rgba(58,64,129,.06);border-radius:999px;padding:4px;}
.sh4-nav-tab{position:relative;display:inline-flex;align-items:center;height:36px;padding:0 20px;font-size:13px;font-weight:600;color:rgba(17,17,17,.42);text-decoration:none;border-radius:999px;transition:color .25s;white-space:nowrap;letter-spacing:-.01em;}
.sh4-nav-tab:hover{color:var(--color-primary);}
.sh4-nav-tab--active{color:#fff;}
.sh4-nav-bg{position:absolute;inset:0;border-radius:999px;background:var(--grad-brand-h);z-index:-1;}
.sh4-nav-label{position:relative;z-index:1;}
@media (max-width:1280px){.sh4{padding-left:48px;padding-right:48px;}}
@media (max-width:1024px){.sh4{padding-left:32px;padding-right:32px;}}
@media (max-width:768px){
.sh4{padding:calc(var(--header-height) + 52px) 20px 72px;}
.sh4-nav-tab{padding:0 14px;font-size:12px;}
}

2
src/css/header.css

@ -5,7 +5,7 @@
.pal-header.is-scrolled,.pal-header.is-open,.pal-header.is-mobile-open{box-shadow:0 10px 30px rgba(15,23,42,.04);} .pal-header.is-scrolled,.pal-header.is-open,.pal-header.is-mobile-open{box-shadow:0 10px 30px rgba(15,23,42,.04);}
.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-inner{position:relative;display:flex;align-items:center;justify-content:space-between;max-width:1440px;height:96px;margin:0 auto;z-index:2;}
.pal-header-logo{flex:0 0 auto;margin:0;font-size:0;line-height:1;} .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 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;display:block;} .pal-header-logo img{width:180px;display:block;}

Loading…
Cancel
Save