Browse Source

feat : main 수정

remotes/origin/main
이시연 1 month ago
parent
commit
ca656b666d
  1. 167
      src/components/main/MainContact.jsx
  2. 175
      src/components/main/MainUam.jsx
  3. 69
      src/css/main.css

167
src/components/main/MainContact.jsx

@ -1,4 +1,4 @@
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
@ -8,6 +8,7 @@ function MainContact() {
const sectionRef = useRef(null);
const headRef = useRef(null);
const formRef = useRef(null);
const [isPrivacyOpen, setIsPrivacyOpen] = useState(false);
useEffect(() => {
const ctx = gsap.context(() => {
@ -56,6 +57,9 @@ function MainContact() {
}, []);
return (
<section className="main-contact-section" ref={sectionRef}>
<div className="contact-orb contact-orb--1" />
<div className="contact-orb contact-orb--2" />
<div className="contact-orb contact-orb--3" />
<div className="main-contact-inner">
<div className="main-contact-head" ref={headRef}>
<p className="main-contact-eyebrow">CONTACT US</p>
@ -65,9 +69,99 @@ function MainContact() {
<br />
남겨주세요.
</h2>
</div>
<form className="main-contact-form" ref={formRef}>
<div className="main-contact-grid">
<label>
<span>
이름 <em>*</em>
</span>
<input type="text" placeholder="이름을 입력해 주세요." required />
</label>
<label>
<span>
이메일 <em>*</em>
</span>
<input
type="email"
placeholder="이메일을 입력해 주세요."
required
/>
</label>
<label>
<span>연락처</span>
<input type="tel" placeholder="연락처를 입력해 주세요." />
</label>
<label>
<span>홈페이지</span>
<input type="url" placeholder="홈페이지 주소를 입력해 주세요." />
</label>
<label className="main-contact-full">
<span>
제목 <em>*</em>
</span>
<input
type="text"
placeholder="문의 제목을 입력해 주세요."
required
/>
</label>
<label className="main-contact-full">
<span>
내용 <em>*</em>
</span>
<textarea placeholder="문의 내용을 입력해 주세요." required />
</label>
</div>
<div className="main-contact-form-bottom">
<label className="main-contact-check">
<input type="checkbox" required />
<span>개인정보처리방침에 동의합니다.</span>
</label>
<button
type="button"
className="main-contact-privacy-open"
onClick={() => setIsPrivacyOpen(true)}
>
개인정보처리방침 보기
</button>
</div>
<button type="submit" className="main-contact-submit">
문의하기
</button>
</form>
</div>
{isPrivacyOpen && (
<div className="main-contact-modal">
<div
className="main-contact-modal-dim"
onClick={() => setIsPrivacyOpen(false)}
/>
<div className="main-contact-privacy">
<div className="main-contact-privacy-box">
<div className="main-contact-modal-card">
<div className="main-contact-modal-head">
<h3>개인정보처리방침</h3>
<button type="button" onClick={() => setIsPrivacyOpen(false)}>
×
</button>
</div>
<div className="main-contact-modal-body">
<p>
'주식회사 팔네트웍스'(이하 '회사') 고객님의 개인정보를
중요시하며, 개인정보보호법 "정보통신망 이용촉진
@ -376,74 +470,9 @@ function MainContact() {
시행일자: 2022 05 26
</p>
</div>
<label className="main-contact-check">
<input type="checkbox" required />
<span>개인정보처리방침에 동의합니다.</span>
</label>
</div>
</div>
<form className="main-contact-form" ref={formRef}>
<div className="main-contact-grid">
<label>
<span>
이름 <em>*</em>
</span>
<input type="text" placeholder="이름을 입력해 주세요." required />
</label>
<label>
<span>
이메일 <em>*</em>
</span>
<input
type="email"
placeholder="이메일을 입력해 주세요."
required
/>
</label>
<label>
<span>연락처</span>
<input type="tel" placeholder="연락처를 입력해 주세요." />
</label>
<label>
<span>홈페이지</span>
<input type="url" placeholder="홈페이지 주소를 입력해 주세요." />
</label>
<label className="main-contact-full">
<span>
제목 <em>*</em>
</span>
<input
type="text"
placeholder="문의 제목을 입력해 주세요."
required
/>
</label>
<label className="main-contact-full">
<span>
내용 <em>*</em>
</span>
<textarea placeholder="문의 내용을 입력해 주세요." required />
</label>
</div>
<button type="submit" className="main-contact-submit">
문의하기
</button>
</form>
</div>
)}
</section>
);
}

175
src/components/main/MainUam.jsx

@ -10,22 +10,143 @@ function MainUam() {
const rightRef = useRef(null);
const lineRef = useRef([]);
const dotRef = useRef(null);
const uamImageRef = useRef(null); //
const uamImageRef = useRef(null);
const canvasRef = useRef(null);
const isMobile = window.innerWidth <= 768;
useEffect(() => {
//
const canvas = canvasRef.current;
const section = sectionRef.current;
const c = canvas.getContext("2d");
let raf;
let scrollP = 0;
const waves = [
{ freq: 1.3, speed: 0.0001, phase: 0.0, yRatio: 0.28, amp: 0.18 },
{ freq: 0.9, speed: 0.00007, phase: 2.6, yRatio: 0.55, amp: 0.15 },
{ freq: 1.6, speed: 0.00013, phase: 5.1, yRatio: 0.78, amp: 0.12 },
];
const dots = waves.map((wave) => ({
wave,
t: Math.random(),
speed: 0.00018 + Math.random() * 0.0001,
}));
const resize = () => {
canvas.width = section.offsetWidth;
canvas.height = section.offsetHeight;
};
const draw = (ts) => {
const W = canvas.width;
const H = canvas.height;
const mobile = W <= 768;
c.clearRect(0, 0, W, H);
// 2, 3
waves.forEach((w, wi) => {
if (mobile && wi === 2) return;
const steps = 200;
const ampScale = mobile ? 0.35 : 0.5 + scrollP * 0.7;
const pts = Array.from({ length: steps + 1 }, (_, s) => [
(s / steps) * W,
canvas.height * w.yRatio +
Math.sin((s / steps) * Math.PI * w.freq + ts * w.speed + w.phase) *
w.amp *
canvas.height *
ampScale,
]);
const alphaCenter = scrollP * (mobile ? 0.12 : 0.18);
const grad = c.createLinearGradient(0, 0, W, 0);
grad.addColorStop(0, `rgba(58,64,129,0)`);
grad.addColorStop(0.2, `rgba(58,64,129,${alphaCenter})`);
grad.addColorStop(0.5, `rgba(58,64,129,${alphaCenter * 1.2})`);
grad.addColorStop(0.8, `rgba(58,64,129,${alphaCenter})`);
grad.addColorStop(1, `rgba(58,64,129,0)`);
c.beginPath();
c.moveTo(pts[0][0], pts[0][1]);
for (let s = 1; s < pts.length - 1; s++) {
const cx = (pts[s][0] + pts[s + 1][0]) / 2;
const cy = (pts[s][1] + pts[s + 1][1]) / 2;
c.quadraticCurveTo(pts[s][0], pts[s][1], cx, cy);
}
c.lineTo(pts[pts.length - 1][0], pts[pts.length - 1][1]);
c.strokeStyle = grad;
c.lineWidth = mobile ? 0.8 : 1.2;
c.stroke();
});
// ( )
dots.forEach((d, di) => {
if (mobile && di === 2) return;
d.t += d.speed * 16;
if (d.t > 1) d.t -= 1;
const ampScale = mobile ? 0.35 : 0.5 + scrollP * 0.7;
const x = d.t * W;
const y =
canvas.height * d.wave.yRatio +
Math.sin(
d.t * Math.PI * d.wave.freq + ts * d.wave.speed + d.wave.phase,
) *
d.wave.amp *
canvas.height *
ampScale;
const edgeFade = Math.sin(d.t * Math.PI);
const alpha = edgeFade * scrollP * 0.45;
if (alpha < 0.01) return;
const outerR = mobile ? 3.5 : 5.5;
const innerR = mobile ? 1.8 : 2.8;
//
c.beginPath();
c.arc(x, y, outerR, 0, Math.PI * 2);
c.strokeStyle = `rgba(58,64,129,${alpha * 0.35})`;
c.lineWidth = 1;
c.stroke();
//
c.beginPath();
c.arc(x, y, innerR, 0, Math.PI * 2);
c.fillStyle = `rgba(58,64,129,${alpha})`;
c.fill();
});
raf = requestAnimationFrame(draw);
};
// ScrollTrigger progress ( )
const st = ScrollTrigger.create({
trigger: section,
start: "top top",
end: "+=2100",
onUpdate: (self) => {
scrollP = self.progress;
},
});
resize();
window.addEventListener("resize", resize);
raf = requestAnimationFrame(draw);
// GSAP
const ctx = gsap.context(() => {
gsap.set(rightRef.current, { xPercent: 100 });
gsap.set(lineRef.current, { scaleX: 0, transformOrigin: "left center" });
gsap.set(dotRef.current, { x: -320, opacity: 0, scale: 1 });
gsap.set(".airspace-uam-content", { opacity: 0, y: 36 });
gsap.set(uamImageRef.current, { opacity: 0 }); //
// dot x
gsap.set(dotRef.current, {
x: isMobile ? -160 : -320,
opacity: 0,
scale: 1,
});
gsap.set(".airspace-uam-content", { opacity: 0, y: 36 });
gsap.set(uamImageRef.current, { opacity: 0 });
const tl = gsap.timeline({
scrollTrigger: {
trigger: sectionRef.current,
@ -46,30 +167,12 @@ function MainUam() {
tl.to(
dotRef.current,
{
x: 0,
opacity: 1,
duration: 0.7,
ease: "none",
},
{ x: 0, opacity: 1, duration: 0.7, ease: "none" },
"<0.1",
);
tl.to(leftRef.current, {
xPercent: -100,
duration: 1,
ease: "none",
});
tl.to(
rightRef.current,
{
xPercent: 0,
duration: 1,
ease: "none",
},
"<",
);
tl.to(leftRef.current, { xPercent: -100, duration: 1, ease: "none" });
tl.to(rightRef.current, { xPercent: 0, duration: 1, ease: "none" }, "<");
tl.call(() => {
dotRef.current?.classList.add("is-zooming");
@ -82,24 +185,27 @@ function MainUam() {
duration: 1.2,
ease: "power2.inOut",
});
// airspace-uam-content + UAM
tl.to(
[".airspace-uam-content", uamImageRef.current],
{
opacity: 1,
y: 0,
duration: 0.8,
ease: "power2.out",
},
{ opacity: 1, y: 0, duration: 0.8, ease: "power2.out" },
"-=0.35",
);
}, sectionRef);
return () => ctx.revert();
return () => {
ctx.revert();
st.kill();
cancelAnimationFrame(raf);
window.removeEventListener("resize", resize);
};
}, []);
return (
<section className="airspace-transition-section" ref={sectionRef}>
{/* 배경 캔버스 */}
<canvas ref={canvasRef} className="aurora-canvas" />
<div className="airspace-panel airspace-panel--utm" ref={leftRef}>
<p>UTM SYSTEM</p>
<h2>드론 하늘길에서</h2>
@ -126,7 +232,6 @@ function MainUam() {
))}
</div>
{/* 동그라미 + UAM 이미지 */}
<div className="airspace-moving-dot" ref={dotRef}>
<div className="airspace-dot-image" ref={uamImageRef}>
<img src={uamImg} alt="UAM Aircraft" />

69
src/css/main.css

@ -213,6 +213,14 @@ body{overflow-x:hidden;}
@keyframes utmWaveMove{0%{transform:translateX(-120%);}100%{transform:translateX(120%);}}
/* ─── uam ─── */
.aurora-canvas {position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; }
.airspace-panel,
.airspace-lines,
.airspace-moving-dot,
.airspace-uam-content {position: relative; z-index: 1; }
.airspace-transition-section{position:relative;min-height:100vh;overflow:hidden;background:radial-gradient(circle at 12% 8%,rgba(58,64,129,.04),transparent 30%),radial-gradient(circle at 88% 10%,rgba(58,64,129,.035),transparent 32%),linear-gradient(180deg,#ffffff 0%,#f7f9ff 48%,#ffffff 100%);}
.airspace-transition-section::before{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(255,255,255,.72) 0%,rgba(255,255,255,.24) 20%,rgba(255,255,255,.08) 50%,rgba(255,255,255,.24) 80%,rgba(255,255,255,.72) 100%);pointer-events:none;}
.airspace-panel{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;padding:0 8vw;}
@ -268,7 +276,13 @@ body{overflow-x:hidden;}
.main-solution-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;}
.main-solution-card{position:relative;min-height:250px;padding:28px 26px;border-radius:28px;background:rgba(255,255,255,.72);border:1px solid rgba(255,255,255,.72);box-shadow:0 20px 60px rgba(58,64,129,.08),inset 0 1px 0 rgba(255,255,255,.9);backdrop-filter:blur(18px);transition:transform .42s cubic-bezier(.22,1,.36,1),box-shadow .42s cubic-bezier(.22,1,.36,1),border-color .3s ease,background .3s ease;will-change:transform;overflow:hidden;cursor:pointer;}
.main-solution-card:hover{transform:translateY(-14px);background:rgba(255,255,255,.88);border-color:rgba(120,130,255,.24);box-shadow:0 42px 100px rgba(58,64,129,.16),inset 0 1px 0 rgba(255,255,255,.96);}
.main-solution-card:hover{transform:translateY(-14px);background:#3a4081;border-color:#3a4081;box-shadow:0 42px 100px rgba(58,64,129,.22),inset 0 1px 0 rgba(255,255,255,.08);}
.main-solution-card:hover .main-solution-card-body span{color:rgba(255,255,255,.55);}
.main-solution-card:hover .main-solution-card-body h3{color:#fff;}
.main-solution-card:hover .main-solution-card-body p{color:rgba(255,255,255,.6);}
.main-solution-card:hover .main-solution-card-icon{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.18);box-shadow:none;}
.main-solution-card:hover .main-solution-card-icon img{filter:brightness(0) invert(1);}
.main-solution-card-icon{width:58px;height:58px;border-radius:18px;display:flex;align-items:center;justify-content:center;margin-bottom:34px;background:linear-gradient(180deg,rgba(255,255,255,.92),rgba(245,247,255,.82));border:1px solid rgba(95,110,255,.12);box-shadow:0 10px 24px rgba(58,64,129,.08),inset 0 1px 0 rgba(255,255,255,.95);transition:transform .38s cubic-bezier(.22,1,.36,1),border-color .3s ease,box-shadow .3s ease,background .3s ease;}
.main-solution-card-icon img{width:28px;height:28px;object-fit:contain;display:block;}
.main-solution-card-body{position:relative;z-index:2;}
@ -316,52 +330,61 @@ body{overflow-x:hidden;}
}
/* MainContact */
.main-contact-section{position:relative;height:100vh;min-height:100vh;padding:0 8vw;background:linear-gradient(135deg,#3a4081 0%,#454b96 42%,#31366f 100%);overflow:hidden;color:#fff;display:flex;align-items:center;}.main-contact-section::before{content:"";position:absolute;right:-18vw;top:-22vw;width:58vw;height:58vw;border-radius:50%;background:radial-gradient(circle,rgba(255,255,255,.16),transparent 68%);filter:blur(10px);pointer-events:none;}
.main-contact-section{position:relative;height:100vh;min-height:100vh;padding:0 8vw;background:#0e0f1f;overflow:hidden;color:#fff;display:flex;align-items:center;}
.main-contact-section::before,.main-contact-section::after{display:none;}
.main-contact-section::after{content:"";position:absolute;left:-12vw;bottom:-18vw;width:42vw;height:42vw;border-radius:50%;background:radial-gradient(circle,rgba(255,255,255,.08),transparent 72%);filter:blur(20px);pointer-events:none;}
.main-contact-inner{position:relative;z-index:2;width:100%;max-width:1440px;margin:0 auto;display:grid;grid-template-columns:.78fr 1.22fr;gap:64px;align-items:center;}
.contact-orb{position:absolute;border-radius:50%;pointer-events:none;z-index:0;}
.contact-orb--1{width:70%;height:70%;background:radial-gradient(circle,#1a1c3a 0%,transparent 70%);filter:blur(40px);animation:contact-drift1 12s ease-in-out infinite;}
.contact-orb--2{width:55%;height:55%;background:radial-gradient(circle,#1e2250 0%,transparent 70%);filter:blur(50px);animation:contact-drift2 17s ease-in-out infinite;}
.contact-orb--3{width:45%;height:45%;background:radial-gradient(circle,#0c0e28 0%,transparent 70%);filter:blur(35px);animation:contact-drift3 22s ease-in-out infinite;}
.main-contact-head{display:flex;flex-direction:column;justify-content:center;}
.main-contact-inner{position:relative;z-index:2;width:100%;max-width:1440px;margin:0 auto;display:grid;grid-template-columns:.78fr 1.22fr;gap:64px;align-items:center;}
.main-contact-head{display:flex;flex-direction:column;justify-content:center;}
.main-contact-eyebrow{margin:0 0 16px;font-size:12px;font-weight:800;letter-spacing:.24em;color:rgba(255,255,255,.72);}
.main-contact-title{margin:0;font-size:clamp(42px,5vw,72px);font-weight:800;line-height:1.04;letter-spacing:-.07em;color:#fff;}
.main-contact-privacy{margin-top:34px;max-width:520px;}
.main-contact-check{display:flex;align-items:center;gap:10px;margin-bottom:14px;font-size:14px;font-weight:700;color:rgba(255,255,255,.84);cursor:pointer;text-align:left;margin-top: 20px;}
.main-contact-check input{width:18px;height:18px;accent-color:#ffffff;cursor:pointer;flex-shrink:0;}
.main-contact-form-bottom{margin-top:20px;display:flex;justify-content:flex-end;align-items:center;gap:14px;flex-wrap:wrap;}
.main-contact-form-bottom .main-contact-check{margin:0;}
.main-contact-privacy-open{border:0;background:transparent;color:rgba(255,255,255,.7);font-size:13px;font-weight:700;text-decoration:underline;text-underline-offset:4px;cursor:pointer;transition:color .3s ease;}
.main-contact-privacy-open:hover{color:#fff;}
.main-contact-modal{position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;padding:24px;}
.main-contact-modal-dim{position:absolute;inset:0;background:rgba(10,12,32,.62);backdrop-filter:blur(8px);}
.main-contact-modal-card{position:relative;z-index:2;width:min(720px,100%);max-height:78vh;border-radius:24px;background:#fff;color:#222;box-shadow:0 30px 90px rgba(0,0,0,.32);overflow:hidden;}
.main-contact-modal-head{display:flex;align-items:center;justify-content:space-between;padding:22px 26px;border-bottom:1px solid #edf0f5;}
.main-contact-modal-head h3{margin:0;font-size:20px;font-weight:800;color:#31366f;}
.main-contact-modal-head button{width:36px;height:36px;border:0;border-radius:50%;background:#f2f4fb;color:#31366f;font-size:24px;cursor:pointer;transition:background .3s ease;}
.main-contact-modal-head button:hover{background:#e6ebff;}
.main-contact-modal-body{max-height:calc(78vh - 82px);overflow:auto;padding:26px;font-size:13px;line-height:1.85;color:#555;}
.main-contact-modal-body::-webkit-scrollbar{width:6px;}
.main-contact-modal-body::-webkit-scrollbar-thumb{background:rgba(49,54,111,.25);border-radius:999px;}
.main-contact-privacy-box{width:100%;max-height:180px;overflow:auto;padding:18px;border-radius:16px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);font-size:11px;line-height:1.75;color:rgba(255,255,255,.56);text-align:left;}
.main-contact-privacy-box p{margin:0;}
@media (max-width:768px){
.main-contact-form-bottom{justify-content:flex-start;gap:10px;}
.main-contact-modal-card{border-radius:20px;}
.main-contact-modal-head{padding:18px 20px;}
.main-contact-modal-body{padding:20px;font-size:12px;}
}
.main-contact-form{padding:32px;border-radius:30px;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.18);box-shadow:0 28px 80px rgba(0,0,0,.16),inset 0 1px 0 rgba(255,255,255,.16);backdrop-filter:blur(18px);}
.main-contact-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;}
.main-contact-grid label{display:block;}
.main-contact-grid span{display:block;margin-bottom:8px;font-size:12px;font-weight:700;color:rgba(255,255,255,.82);}
.main-contact-grid em{font-style:normal;color:#dbe3ff;}
.main-contact-grid input,.main-contact-grid textarea{width:100%;border:1px solid rgba(255,255,255,.16);border-radius:14px;background:rgba(255,255,255,.12);padding:14px 16px;font-size:14px;font-weight:500;color:#fff;outline:none;transition:border-color .3s ease,background .3s ease,box-shadow .3s ease;}
.main-contact-grid input::placeholder,.main-contact-grid textarea::placeholder{color:rgba(255,255,255,.38);}
.main-contact-grid input:focus,.main-contact-grid textarea:focus{border-color:rgba(255,255,255,.46);background:rgba(255,255,255,.16);box-shadow:0 0 0 4px rgba(255,255,255,.08);}
.main-contact-grid textarea{height:110px;resize:none;line-height:1.65;}
.main-contact-full{grid-column:1/-1;}
.main-contact-submit{margin-top:22px;width:100%;height:52px;border:0;border-radius:16px;background:#ffffff;color:#3a4081;font-size:15px;font-weight:800;cursor:pointer;transition:transform .35s ease,box-shadow .35s ease,background .35s ease;}
.main-contact-submit:hover{transform:translateY(-4px);box-shadow:0 18px 44px rgba(0,0,0,.22);background:#f4f6ff;}
@media (max-width:1024px){.main-contact-section{height:auto;min-height:auto;padding:100px 6vw;}.main-contact-inner{grid-template-columns:1fr;gap:46px;}.main-contact-title{font-size:clamp(38px,8vw,60px);}.main-contact-privacy{max-width:100%;}.main-contact-form{padding:28px;}}
@media (max-width:768px){.main-contact-section{padding:82px 6vw;}.main-contact-title{font-size:clamp(34px,10vw,50px);}.main-contact-privacy{margin-top:28px;}.main-contact-privacy-box{max-height:150px;padding:15px;font-size:10.5px;}.main-contact-form{padding:24px;border-radius:26px;}.main-contact-grid{grid-template-columns:1fr;gap:15px;}.main-contact-grid input,.main-contact-grid textarea{padding:14px 15px;font-size:14px;}.main-contact-grid textarea{height:130px;}.main-contact-submit{height:52px;}}
@keyframes contact-drift1{0%{top:-10%;left:20%;}33%{top:20%;left:55%;}66%{top:40%;left:10%;}100%{top:-10%;left:20%;}}
@keyframes contact-drift2{0%{top:30%;left:60%;}50%{top:10%;left:20%;}100%{top:30%;left:60%;}}
@keyframes contact-drift3{0%{top:50%;left:30%;}40%{top:15%;left:65%;}70%{top:55%;left:50%;}100%{top:50%;left:30%;}}
Loading…
Cancel
Save