Skip to content

Commit d1349a8

Browse files
authored
feat: 메인페이지 구현
* feat: 메인 화면 레이아웃 구현 * fix: 텍스트 미세 조정 * feat: 애니메이션 및 배경 추가 * docs: 필요 없는 주석 제거 * refactor: 메인 컴포넌트화 * feat: 활동 기록 불러오기 기능 구현 * fix: 모바일에서 카드 레이아웃 깨지는 현상 수정 * design: 배경 투명도 조절 * feat: 로드 애니메이션 추가 * design: 폰트 미적용 부분 수정
1 parent e490fde commit d1349a8

File tree

13 files changed

+483
-74
lines changed

13 files changed

+483
-74
lines changed

public/coreback.svg

Lines changed: 18 additions & 0 deletions
Loading

public/dare.svg

Lines changed: 3 additions & 0 deletions
Loading

public/dasomMain.png

1.85 MB
Loading

public/share.svg

Lines changed: 3 additions & 0 deletions
Loading

public/someday.svg

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React from 'react'
2+
3+
interface CoreValueCardProps {
4+
index: string
5+
title: string
6+
mobileSubtitle: string
7+
mobileBody: React.ReactNode
8+
hoverSubtitle: string
9+
hoverBody: React.ReactNode
10+
imageSrc: string
11+
mobileImageClass: string
12+
defaultImageClass: string
13+
hoverImageClass: string
14+
defaultBgClass?: string
15+
hoverBgClass?: string
16+
mobileSubtitleClass?: string
17+
hoverSubtitleClass?: string
18+
titleDefaultClass?: string
19+
titleHoverClass?: string
20+
}
21+
22+
const CoreValueCard: React.FC<CoreValueCardProps> = ({
23+
index,
24+
title,
25+
mobileSubtitle,
26+
mobileBody,
27+
hoverSubtitle,
28+
hoverBody,
29+
imageSrc,
30+
mobileImageClass,
31+
defaultImageClass,
32+
hoverImageClass,
33+
defaultBgClass = 'md:w-[352px] lg:w-[400px] h-[560px] md:left-0 lg:left-0 top-[63px]',
34+
hoverBgClass = 'md:w-[352px] lg:w-[400px] h-[560px] md:left-0 lg:left-0 top-[54px]',
35+
mobileSubtitleClass = 'text-base',
36+
hoverSubtitleClass = 'text-3xl',
37+
titleDefaultClass = 'text-5xl',
38+
titleHoverClass = 'text-5xl',
39+
}) => {
40+
return (
41+
<div className="mx-auto">
42+
<div className="md:hidden w-[340px] sm:w-[360px] rounded-2xl bg-zinc-800 p-6 relative overflow-hidden min-h-[200px]">
43+
<div className="flex items-start justify-between gap-4">
44+
<div>
45+
<div className="text-white text-3xl font-bold font-mono">{index}</div>
46+
<div className="mt-3 text-white text-2xl font-semibold">{title}</div>
47+
<div className={`mt-2 text-white/80 ${mobileSubtitleClass}`}>{mobileSubtitle}</div>
48+
</div>
49+
<img src={imageSrc} alt={title} className={mobileImageClass} />
50+
</div>
51+
<div className="mt-4 text-white text-sm">{mobileBody}</div>
52+
</div>
53+
54+
<div className="hidden md:block group md:w-[352px] lg:w-[400px] h-[685px] relative overflow-hidden">
55+
<div className="absolute inset-0 transition-opacity duration-300 ease-out group-hover:opacity-0">
56+
<div className={`${defaultBgClass} absolute bg-zinc-800 rounded-2xl`} />
57+
<div className={`left-12 top-[543px] absolute text-center text-white font-normal ${titleDefaultClass}`}>{title}</div>
58+
<div className="left-12 top-[105px] absolute text-center text-white text-3xl font-bold font-mono">{index}</div>
59+
<img src={imageSrc} alt={`${title} illustration`} className={defaultImageClass} />
60+
</div>
61+
62+
<div className="absolute inset-0 opacity-0 transition-opacity duration-300 ease-out group-hover:opacity-100">
63+
<div className={`${hoverBgClass} absolute bg-neutral-100 rounded-2xl`} />
64+
<div className={`left-12 top-[343px] absolute text-center text-zinc-900 font-semibold ${titleHoverClass}`}>{title}</div>
65+
<div className={`left-12 top-[415px] absolute text-center text-black font-normal ${hoverSubtitleClass}`}>{hoverSubtitle}</div>
66+
<div className="left-12 top-[98px] absolute text-center text-zinc-900 text-3xl font-bold font-mono">{index}</div>
67+
<div className="left-12 top-[505px] absolute text-black text-2xl font-normal">{hoverBody}</div>
68+
<img src={imageSrc} alt={`${title} illustration`} className={hoverImageClass} />
69+
</div>
70+
</div>
71+
</div>
72+
)
73+
}
74+
75+
export default CoreValueCard

src/components/UI/ParticlesBackground.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const ParticlesBackground: React.FC = () => {
1616
}}
1717
animate={{ opacity: [0.9, 1, 0.9] }}
1818
transition={{ duration: 4, repeat: Infinity, ease: 'easeInOut' }}
19-
className='absolute w-full h-[700px]'
19+
className='absolute inset-0 w-full h-full'
2020
>
2121
<SparklesCore
2222
background='transparent'

src/components/UI/Reveal.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { useEffect, useRef, useState } from 'react'
2+
3+
interface RevealProps {
4+
children: React.ReactNode
5+
/** delay in ms before revealing once intersected */
6+
delayMs?: number
7+
/** additional classes applied to the wrapper */
8+
className?: string
9+
/** threshold for IntersectionObserver */
10+
threshold?: number
11+
}
12+
13+
const Reveal: React.FC<RevealProps> = ({ children, delayMs = 0, className = '', threshold = 0.15 }) => {
14+
const ref = useRef<HTMLDivElement | null>(null)
15+
const [visible, setVisible] = useState(false)
16+
17+
useEffect(() => {
18+
const node = ref.current
19+
if (!node) return
20+
21+
const observer = new IntersectionObserver(
22+
entries => {
23+
entries.forEach(entry => {
24+
if (entry.isIntersecting) {
25+
if (delayMs > 0) {
26+
setTimeout(() => setVisible(true), delayMs)
27+
} else {
28+
setVisible(true)
29+
}
30+
observer.unobserve(entry.target)
31+
}
32+
})
33+
},
34+
{ threshold }
35+
)
36+
37+
observer.observe(node)
38+
return () => observer.disconnect()
39+
}, [delayMs, threshold])
40+
41+
return (
42+
<div
43+
ref={ref}
44+
className={
45+
`${className} transition-all duration-700 ease-out ` +
46+
(visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2')
47+
}
48+
>
49+
{children}
50+
</div>
51+
)
52+
}
53+
54+
export default Reveal
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React, { useEffect, useMemo, useRef, useState } from 'react'
2+
import { Link } from 'react-router-dom'
3+
import { NewsItem } from '../../pages/news/Newstype'
4+
import { NewsService } from '../../pages/news/NewsService'
5+
import { convertToBase64Url } from '../../utils/imageUtils'
6+
import Reveal from '../UI/Reveal'
7+
8+
const ActivitiesSection: React.FC = () => {
9+
const [news, setNews] = useState<NewsItem[]>([])
10+
const fetched = useRef(false)
11+
12+
useEffect(() => {
13+
if (fetched.current) return
14+
const load = async () => {
15+
try {
16+
const list = await NewsService.getNewsList()
17+
setNews(list)
18+
} catch (e) {
19+
console.error('최근 소식 불러오기 실패:', e)
20+
}
21+
}
22+
load()
23+
fetched.current = true
24+
}, [])
25+
26+
const latestThree = useMemo(() => {
27+
const top3 = news.slice(0, 3)
28+
return top3.map(n => ({
29+
...n,
30+
imageUrl: convertToBase64Url(n.image),
31+
}))
32+
}, [news])
33+
34+
return (
35+
<section className="max-w-screen-xl mx-auto px-4 py-16 md:py-24">
36+
<Reveal>
37+
<div className="text-center">
38+
<p className="text-xl md:text-2xl">Activities</p>
39+
<h2 className="mt-1 text-3xl md:text-4xl font-pretendardBold">활동 기록</h2>
40+
<p className="mt-4 text-base md:text-xl text-white/80 font-pretendardRegular">
41+
튜터링, 스터디뿐만 아니라 팀 프로젝트, 솜커톤과 같은 대내 행사를 진행하고 있습니다.
42+
<br className="hidden md:block" />
43+
이뿐만 아니라 외부 세미나 참여, EXPO 출품, MT, 할로윈 파티 등 다양한 행사 또한 진행하고 있습니다.
44+
</p>
45+
</div>
46+
47+
<div className="mt-10 grid grid-cols-1 md:grid-cols-3 gap-6">
48+
{latestThree.map(item => (
49+
<Link
50+
key={item.id}
51+
to={`/news/${item.id}`}
52+
className="rounded-3xl bg-neutral-100 text-zinc-900 overflow-hidden border border-zinc-900/10 hover:shadow-md transition-shadow"
53+
>
54+
{item.imageUrl ? (
55+
<img src={item.imageUrl} alt={item.title} className="w-full h-36 object-cover" />
56+
) : (
57+
<div className="w-full h-36 bg-zinc-200 flex items-center justify-center text-zinc-500 text-sm">이미지 없음</div>
58+
)}
59+
<div className="p-5">
60+
<h3 className="text-xl font-pretendardBold line-clamp-2">{item.title}</h3>
61+
<p className="mt-3 text-xs text-zinc-500">{new Date(item.createdAt).toLocaleDateString()}</p>
62+
</div>
63+
</Link>
64+
))}
65+
{latestThree.length === 0 && (
66+
<div className="col-span-full text-center text-white/70">최근 소식을 불러오는 중입니다...</div>
67+
)}
68+
</div>
69+
70+
<div className="mt-10 flex justify-center">
71+
<Link to="/news" className="px-6 py-3 rounded-[30px] bg-mainColor shadow-[2px_6px_11px_0px_rgba(0,0,0,0.25)] font-pretendardSemiBold">
72+
더 알아보기
73+
</Link>
74+
</div>
75+
</Reveal>
76+
</section>
77+
)
78+
}
79+
80+
export default ActivitiesSection

0 commit comments

Comments
 (0)