Browse Source

feat : 메인 반응형 수정

remotes/origin/main
이시연 2 weeks ago
parent
commit
6566417a57
  1. BIN
      public/images/main_utm_mobile.png
  2. 74
      src/components/SubHero.jsx
  3. 18
      src/components/main/MainUtm.jsx
  4. 7
      src/css/main.css

BIN
public/images/main_utm_mobile.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

74
src/components/SubHero.jsx

@ -105,7 +105,8 @@ function NetworkGlobe() {
const d = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (d < 0.55) {
const depth = (a.sz + b.sz) * 0.5;
const alpha = Math.max(0, 0.05 + (depth + 1) * 0.1) * (1 - d / 0.55);
const alpha =
Math.max(0, 0.05 + (depth + 1) * 0.1) * (1 - d / 0.55);
const aHex = Math.round(Math.min(255, alpha * 255))
.toString(16)
.padStart(2, "0");
@ -134,7 +135,14 @@ function NetworkGlobe() {
.toString(16)
.padStart(2, "0");
const glow = ctx.createRadialGradient(n.sx, n.sy, 0, n.sx, n.sy, r * 3.5);
const glow = ctx.createRadialGradient(
n.sx,
n.sy,
0,
n.sx,
n.sy,
r * 3.5,
);
glow.addColorStop(0, n.color + gHex);
glow.addColorStop(1, n.color + "00");
ctx.beginPath();
@ -172,7 +180,12 @@ function NetworkGlobe() {
function TitleLine({ children, delay }) {
return (
<span className="sh4-title-line">
<motion.span className="sh4-title-line-inner" initial={{ y: "105%" }} animate={{ y: "0%" }} transition={{ duration: 1.1, delay, ease: [0.16, 1, 0.3, 1] }}>
<motion.span
className="sh4-title-line-inner"
initial={{ y: "105%" }}
animate={{ y: "0%" }}
transition={{ duration: 1.1, delay, ease: [0.16, 1, 0.3, 1] }}
>
{children}
</motion.span>
</span>
@ -202,7 +215,9 @@ export default function SubHero({ title, desc, navItems, rightSlot }) {
))
: // JSX: <br/> TitleLine
(() => {
const children = Array.isArray(title.props?.children) ? title.props.children : [title];
const children = Array.isArray(title.props?.children)
? title.props.children
: [title];
const lines = [];
let current = [];
children.forEach((child, i) => {
@ -229,21 +244,39 @@ export default function SubHero({ title, desc, navItems, rightSlot }) {
<div className="sh4-inner">
<div className="sh4-left">
{/* // 브레드크럼 */}
<motion.nav className="sh4-breadcrumb" initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }} aria-label="breadcrumb">
<motion.nav
className="sh4-breadcrumb"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
aria-label="breadcrumb"
>
<Link to="/" className="sh4-breadcrumb-item">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
</Link>
<span className="sh4-breadcrumb-sep">/</span>
<Link to={"/" + pathname.split("/")[1]} className="sh4-breadcrumb-item">
<Link
to={"/" + pathname.split("/")[1]}
className="sh4-breadcrumb-item"
>
{menuMap["/" + pathname.split("/")[1]]?.label}
</Link>
{pathname.split("/")[2] && (
<>
<span className="sh4-breadcrumb-sep">/</span>
<span className="sh4-breadcrumb-item sh4-breadcrumb-item--active">{navItems?.find((n) => n.to === pathname)?.label}</span>
<span className="sh4-breadcrumb-item sh4-breadcrumb-item--active">
{navItems?.find((n) => n.to === pathname)?.label}
</span>
</>
)}
</motion.nav>
@ -255,7 +288,11 @@ export default function SubHero({ title, desc, navItems, rightSlot }) {
animate={{ opacity: 1, y: 0 }}
transition={{
duration: 0.6,
delay: 0.1 + (typeof title === "string" ? title.split("\n").length : 2) * 0.1 + 0.15,
delay:
0.1 +
(typeof title === "string" ? title.split("\n").length : 2) *
0.1 +
0.15,
ease: [0.16, 1, 0.3, 1],
}}
>
@ -265,7 +302,12 @@ export default function SubHero({ title, desc, navItems, rightSlot }) {
</div>
{rightSlot && (
<motion.div className="sh4-right" initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 1, delay: 0.3 }}>
<motion.div
className="sh4-right"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1, delay: 0.3 }}
>
{rightSlot}
</motion.div>
)}
@ -273,10 +315,18 @@ export default function SubHero({ title, desc, navItems, rightSlot }) {
</section>
{navItems?.length > 1 && (
<nav ref={navRef} className={`sh4-nav-wrap${isPill ? " is-pill" : ""}`} aria-label="Sub Navigation">
<nav
ref={navRef}
className={`sh4-nav-wrap${isPill ? " is-pill" : ""}`}
aria-label="Sub Navigation"
>
<div className="sh4-nav">
{navItems.map((item) => (
<Link key={item.to} to={item.to} className={`sh4-nav-tab${pathname === item.to ? " sh4-nav-tab--active" : ""}`}>
<Link
key={item.to}
to={item.to}
className={`sh4-nav-tab${pathname === item.to ? " sh4-nav-tab--active" : ""}`}
>
{item.label}
</Link>
))}

18
src/components/main/MainUtm.jsx

@ -1,6 +1,5 @@
import { useRef, useEffect, useState } from "react";
import { motion, useInView, AnimatePresence } from "framer-motion";
import utmMapImg from "../../../public/images/main_utm_img.png";
import { Link } from "react-router-dom";
const ease = [0.22, 1, 0.36, 1];
@ -339,21 +338,30 @@ function MainUtm() {
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.9, delay: 0.32, ease }}
>
{/* PC 전용 — 애니메이션 */}
<div className="utm-hero__pc-only">
<div className="utm-hero__map-wrap">
<img
src={utmMapImg}
src={`${import.meta.env.BASE_URL}images/main_utm_img.png`}
alt="UTM 관제 지도"
className="utm-hero__map-img"
draggable="false"
/>
</div>
<UtmSystemPanel phase={phase} activeRow={activeRow} />
{/* 커서 */}
<div className="utm-cursor" ref={cursorRef}>
<div className="utm-cursor__dot" />
</div>
</div>
{/* 모바일 전용 — 이미지 */}
<div className="utm-hero__mobile-only">
<img
src={`${import.meta.env.BASE_URL}images/main_utm_mobile.png`}
alt="UTM 관제 시스템"
className="utm-hero__mobile-img"
/>
</div>
</motion.div>
</div>
</section>

7
src/css/main.css

@ -172,6 +172,8 @@ body{overflow-x:hidden;}
.utm-hero__chips { list-style: none; display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin: 0 0 16px; padding: 0; }
.utm-hero__link { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 700; color: #fff; background: #1a1f5e; padding: 7px 16px; border-radius: 999px; text-decoration: none; transition: gap 0.2s; }
.utm-hero__link:hover { gap: 12px; }
.utm-hero__pc-only { display: block; }
.utm-hero__mobile-only { display: none; }
/* UTM 반응형 */
@media (max-width: 1280px) {
.utm-panel { left: -80px; width: 320px; }
@ -219,6 +221,11 @@ body{overflow-x:hidden;}
.utm-confirm__title { font-size: 13px; }
.utm-confirm__cancel, .utm-confirm__ok { font-size: 12px; padding: 8px; }
.utm-cursor { display: none; }
.utm-hero__mobile-img { width: 100%; height: 100%; border-radius: 12px; }
.utm-hero__mobile-only { display: block; width: 100%; aspect-ratio: 16/9; overflow: hidden; border-radius: 12px; }
.utm-hero__pc-only { display: none; }
.utm-hero__showcase { box-shadow: none; }
}

Loading…
Cancel
Save