first commit
This commit is contained in:
@@ -0,0 +1,691 @@
|
||||
/* eslint-disable */
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// searchgg · 메인 홈 (레트로 구조형 · 올리브 포인트 · 각진 패널 레이아웃)
|
||||
// 공지/업데이트/이벤트 전면 · 이벤트 카드 이미지 슬롯
|
||||
// window.Home
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const { useState: useStateH } = React;
|
||||
const PT = '#48515f'; // 올리브/카키 포인트
|
||||
const PT_SOFT = 'rgba(72,81,95,.12)';
|
||||
const HEAD = '#f1f3f5'; // 패널 헤더바 배경
|
||||
const CH = "'Chakra Petch',sans-serif";
|
||||
|
||||
const panel = { border: '1px solid var(--line2)', background: '#fff', borderRadius: 5 };
|
||||
|
||||
// 패널 (채워진 헤더바 + 보더)
|
||||
function Panel({ title, en, right, children, bodyPad = '8px 14px 12px' }) {
|
||||
return (
|
||||
<section style={panel}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', height: 40, padding: '0 12px 0 0', background: HEAD, borderBottom: '1px solid var(--line2)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 9, minWidth: 0 }}>
|
||||
<span style={{ width: 4, alignSelf: 'stretch', background: PT, margin: '8px 0', marginRight: 2 }} />
|
||||
<span style={{ fontSize: 14.5, fontWeight: 800, color: 'var(--ink)', letterSpacing: '-.2px' }}>{title}</span>
|
||||
{en && <span style={{ fontFamily: CH, fontSize: 10, fontWeight: 600, color: 'var(--t3)', letterSpacing: '1.2px' }}>{en}</span>}
|
||||
</div>
|
||||
{right}
|
||||
</div>
|
||||
<div style={{ padding: bodyPad }}>{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// 세그먼트 탭 (각진)
|
||||
function Seg({ tabs, value, onChange }) {
|
||||
return (
|
||||
<div style={{ display: 'flex', border: '1px solid var(--line2)', borderRadius: 4, overflow: 'hidden', background: '#fff' }}>
|
||||
{tabs.map((t, i) => {
|
||||
const on = value === t;
|
||||
return (
|
||||
<button key={t} onClick={() => onChange(t)} style={{ padding: '5px 11px', border: 'none', borderLeft: i ? '1px solid var(--line2)' : 'none', cursor: 'pointer',
|
||||
fontFamily: "'Pretendard',sans-serif", fontWeight: 700, fontSize: 12, background: on ? PT : '#fff', color: on ? '#fff' : 'var(--t2)' }}>{t}</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 상단 유틸 바 ──
|
||||
function TopBar() {
|
||||
return (
|
||||
<div style={{ height: 30, background: '#23262b', display: 'flex', alignItems: 'center', justifyContent: 'flex-end', padding: '0 28px', gap: 0 }}>
|
||||
{['로그인', '회원가입', '쿠폰등록', '고객센터'].map((t, i) => (
|
||||
<React.Fragment key={t}>
|
||||
{i > 0 && <span style={{ width: 1, height: 11, background: 'rgba(255,255,255,.18)', margin: '0 12px' }} />}
|
||||
<a style={{ fontSize: 12, color: 'rgba(255,255,255,.72)', cursor: 'pointer', fontWeight: 500 }}>{t}</a>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 메인 네비(GNB) ──
|
||||
function Nav({ onLogin, onClan, onCommunity, active = '홈' }) {
|
||||
const items = ['홈', '전적검색', '커뮤니티', '클랜'];
|
||||
return (
|
||||
<div style={{ background: '#fff', borderBottom: '2px solid #23262b', position: 'sticky', top: 0, zIndex: 30 }}>
|
||||
<div style={{ maxWidth: 1180, margin: '0 auto', height: 60, display: 'flex', alignItems: 'center', padding: '0 28px' }}>
|
||||
<a href="searchgg 메인 홈.html" style={{ textDecoration: 'none', display: 'flex' }}><Logo size={22} /></a>
|
||||
<nav style={{ display: 'flex', marginLeft: 34, height: '100%' }}>
|
||||
{items.map((t) => {
|
||||
const on = t === active;
|
||||
const style = { display: 'flex', alignItems: 'center', padding: '0 18px', height: '100%', fontSize: 15, fontWeight: on ? 800 : 600, textDecoration: 'none',
|
||||
color: on ? PT : '#3a4049', cursor: 'pointer', borderBottom: on ? `3px solid ${PT}` : '3px solid transparent', background: 'transparent', border: 'none', borderBottomWidth: 3, borderBottomStyle: 'solid', borderBottomColor: on ? PT : 'transparent', fontFamily: "'Pretendard',sans-serif" };
|
||||
if (t === '클랜' && onClan) {
|
||||
return <button key={t} onClick={onClan} style={style}>{t}</button>;
|
||||
}
|
||||
if (t === '커뮤니티' && onCommunity) {
|
||||
return <button key={t} onClick={onCommunity} style={style}>{t}</button>;
|
||||
}
|
||||
return (
|
||||
<a key={t} href="searchgg 메인 홈.html" style={style}>{t}</a>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<button onClick={onLogin} style={{ marginLeft: 'auto', border: 'none', background: 'transparent', cursor: 'pointer',
|
||||
color: '#3a4049', fontWeight: 700, fontSize: 14, padding: '6px 4px', fontFamily: "'Pretendard',sans-serif" }}>
|
||||
로그인
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 검색 히어로(구조형 밴드) ──
|
||||
function Hero({ onPick }) {
|
||||
const SA = window.SA, me = SA.me;
|
||||
return (
|
||||
<div style={{ background: '#fff', borderBottom: '1px solid var(--line2)' }}>
|
||||
<div style={{ maxWidth: 1180, margin: '0 auto', padding: '34px 28px 30px' }}>
|
||||
<div style={{ maxWidth: 720, margin: '0 auto', textAlign: 'center' }}>
|
||||
<div style={{ fontFamily: CH, fontSize: 11, fontWeight: 700, letterSpacing: '3px', color: PT, marginBottom: 10 }}>SUDDEN ATTACK RECORD SEARCH</div>
|
||||
<h1 style={{ margin: '0 0 6px', fontWeight: 800, fontSize: 30, lineHeight: 1.2, letterSpacing: '-1px', color: 'var(--ink)' }}>전적 검색</h1>
|
||||
<div style={{ fontSize: 13.5, color: 'var(--t2)', marginBottom: 18 }}>어서와요, <b style={{ color: PT }}>{me.name}</b> 님</div>
|
||||
<SearchBar big onPick={onPick} />
|
||||
<div style={{ marginTop: 13, display: 'flex', justifyContent: 'center' }}><RecentChips onPick={onPick} /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 섹션 헤더(채워진 바) ──
|
||||
function BarHead({ title, en, more }) {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', background: '#23262b', color: '#fff', padding: '0 14px', height: 38, borderRadius: '5px 5px 0 0' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 9 }}>
|
||||
<span style={{ fontSize: 14.5, fontWeight: 800, letterSpacing: '-.2px' }}>{title}</span>
|
||||
{en && <span style={{ fontFamily: CH, fontSize: 10, fontWeight: 600, color: 'rgba(255,255,255,.5)', letterSpacing: '1.2px' }}>{en}</span>}
|
||||
</div>
|
||||
{more && <a style={{ fontSize: 12, color: 'rgba(255,255,255,.7)', cursor: 'pointer', fontWeight: 600 }}>전체보기 +</a>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 진행중인 이벤트 (캐러셀) ──
|
||||
function EventStrip() {
|
||||
const SA = window.SA;
|
||||
const PER = 4;
|
||||
const pages = Math.ceil(SA.events.length / PER);
|
||||
const [page, setPage] = useStateH(0);
|
||||
const [hover, setHover] = useStateH(false);
|
||||
React.useEffect(() => {
|
||||
if (hover || pages <= 1) return;
|
||||
const t = setInterval(() => setPage((p) => (p + 1) % pages), 5000);
|
||||
return () => clearInterval(t);
|
||||
}, [hover, pages]);
|
||||
const go = (d) => setPage((p) => (p + d + pages) % pages);
|
||||
const arrow = (dir) => (
|
||||
<button onClick={() => go(dir === 'L' ? -1 : 1)} style={{ width: 26, height: 26, borderRadius: 4, border: 'none', background: 'rgba(255,255,255,.14)', color: '#fff', cursor: 'pointer', display: 'grid', placeItems: 'center', fontSize: 13 }}>
|
||||
{dir === 'L' ? '‹' : '›'}
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<div style={{ border: '1px solid var(--line2)', borderRadius: 5, marginBottom: 18, background: '#fff', overflow: 'hidden' }}
|
||||
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', background: '#23262b', color: '#fff', padding: '0 12px 0 14px', height: 38 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 9 }}>
|
||||
<span style={{ fontSize: 14.5, fontWeight: 800, letterSpacing: '-.2px' }}>진행중인 이벤트</span>
|
||||
<span style={{ fontFamily: CH, fontSize: 10, fontWeight: 600, color: 'rgba(255,255,255,.5)', letterSpacing: '1.2px' }}>ON-GOING EVENT</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontFamily: CH, fontSize: 11.5, fontWeight: 700, color: 'rgba(255,255,255,.7)' }}>{page + 1} / {pages}</span>
|
||||
{arrow('L')}{arrow('R')}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ overflow: 'hidden' }}>
|
||||
<div style={{ display: 'flex', width: `${pages * 100}%`, transform: `translateX(-${page * (100 / pages)}%)`, transition: 'transform .45s cubic-bezier(.4,0,.2,1)' }}>
|
||||
{Array.from({ length: pages }).map((_, pi) => (
|
||||
<div key={pi} style={{ width: `${100 / pages}%`, display: 'grid', gridTemplateColumns: 'repeat(4,1fr)' }}>
|
||||
{SA.events.slice(pi * PER, pi * PER + PER).map((e, i) => {
|
||||
const gi = pi * PER + i;
|
||||
const urgent = e.dday.startsWith('D-') && +e.dday.slice(2) <= 3;
|
||||
return (
|
||||
<div key={e.title} style={{ borderLeft: i ? '1px solid var(--line)' : 'none', cursor: 'pointer', display: 'flex', flexDirection: 'column' }}>
|
||||
<image-slot id={`evt-${gi}`} shape="rect" placeholder={`${e.tag} 배너 이미지를 끌어다 놓으세요`} style={{ display: 'block', width: '100%', height: 112, borderBottom: '1px solid var(--line)' }}></image-slot>
|
||||
<div style={{ padding: '11px 13px 13px', display: 'flex', flexDirection: 'column', flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 8 }}>
|
||||
<span style={{ fontFamily: "'Pretendard',sans-serif", fontSize: 11, fontWeight: 700, color: '#fff', background: PT, padding: '2px 9px', whiteSpace: 'nowrap', flexShrink: 0 }}>{e.tag}</span>
|
||||
<span style={{ marginLeft: 'auto', fontFamily: CH, fontSize: 11.5, fontWeight: 700, color: urgent ? '#b23b3b' : PT }}>{e.dday}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink)', lineHeight: 1.34, marginBottom: 5 }}>{e.title}</div>
|
||||
<div style={{ fontSize: 11.5, color: 'var(--t2)', lineHeight: 1.45, marginBottom: 'auto' }}>{e.sub}</div>
|
||||
<div style={{ fontFamily: CH, fontSize: 11, color: 'var(--t3)', marginTop: 11, paddingTop: 9, borderTop: '1px solid var(--line)' }}>{e.period}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: 6, padding: '11px 0', borderTop: '1px solid var(--line)' }}>
|
||||
{Array.from({ length: pages }).map((_, i) => (
|
||||
<button key={i} onClick={() => setPage(i)} style={{ width: i === page ? 20 : 7, height: 7, borderRadius: 4, border: 'none', background: i === page ? PT : 'var(--line2)', cursor: 'pointer', transition: 'all .3s', padding: 0 }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const rowDiv = (i, n) => ({ borderBottom: i < n - 1 ? '1px solid #eaecef' : 'none' });
|
||||
|
||||
// ── 지금 방송 중 LIVE (치지직 / 숲 / 유튜브) ──
|
||||
function StreamLive({ onPick }) {
|
||||
const SA = window.SA;
|
||||
const [tab, setTab] = useStateH('전체');
|
||||
const list = tab === '전체' ? SA.streams : SA.streams.filter((s) => s.platform === tab);
|
||||
const totalView = SA.streams.reduce((a, s) => a + s.viewers, 0);
|
||||
return (
|
||||
<Panel title="서든 LIVE" en="STREAMING" bodyPad="12px 14px 14px"
|
||||
right={<Seg tabs={['전체', '치지직', '숲', '유튜브']} value={tab} onChange={setTab} />}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 11, fontSize: 11.5, color: 'var(--t2)' }}>
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontWeight: 700, color: '#e5484d' }}>
|
||||
<span style={{ width: 7, height: 7, borderRadius: 4, background: '#e5484d', display: 'inline-block', animation: 'sgFade .01s' }} />LIVE
|
||||
</span>
|
||||
<span>{list.length}개 방송</span>
|
||||
<span style={{ color: 'var(--t3)' }}>·</span>
|
||||
<span>시청자 <b style={{ color: 'var(--ink)', fontFamily: CH }}>{SA.fmt(totalView)}</b>명</span>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2,1fr)', gap: 12 }}>
|
||||
{list.map((s, i) => {
|
||||
const pf = SA.streamPlatforms[s.platform];
|
||||
const player = SA.allPlayers.find((p) => p.name === s.streamer);
|
||||
return (
|
||||
<button key={s.streamer + i} onClick={() => player && onPick(player)} style={{ display: 'flex', flexDirection: 'column', textAlign: 'left',
|
||||
border: '1px solid var(--line2)', borderRadius: 5, overflow: 'hidden', background: '#fff', cursor: player ? 'pointer' : 'default', padding: 0, fontFamily: "'Pretendard',sans-serif" }}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<image-slot id={`live-${i}`} shape="rect" placeholder={`${s.streamer} 방송 썸네일`} style={{ display: 'block', width: '100%', aspectRatio: '7 / 4' }}></image-slot>
|
||||
<span style={{ position: 'absolute', top: 7, left: 7, display: 'inline-flex', alignItems: 'center', gap: 4, background: '#e5484d', color: '#fff', fontFamily: CH, fontWeight: 700, fontSize: 9.5, letterSpacing: '.6px', padding: '2px 6px', borderRadius: 3 }}>
|
||||
<span style={{ width: 5, height: 5, borderRadius: 3, background: '#fff', display: 'inline-block' }} />LIVE
|
||||
</span>
|
||||
<span style={{ position: 'absolute', top: 7, right: 7, background: pf.color, color: '#fff', fontFamily: CH, fontWeight: 700, fontSize: 9.5, letterSpacing: '.5px', padding: '2px 7px', borderRadius: 3 }}>{pf.tag}</span>
|
||||
<span style={{ position: 'absolute', bottom: 7, right: 7, background: 'rgba(20,22,28,.82)', color: '#fff', fontFamily: CH, fontWeight: 700, fontSize: 10.5, padding: '2px 7px', borderRadius: 3 }}>👁 {SA.fmt(s.viewers)}</span>
|
||||
</div>
|
||||
<div style={{ padding: '9px 10px 10px' }}>
|
||||
<div style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink)', lineHeight: 1.34, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', minHeight: 33 }}>{s.title}</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 7 }}>
|
||||
<span style={{ fontSize: 9, fontWeight: 800, color: pf.color, letterSpacing: '.3px' }}>{pf.label}</span>
|
||||
<span style={{ width: 1, height: 9, background: 'var(--line2)' }} />
|
||||
<span style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--t2)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.streamer}</span>
|
||||
<span style={{ marginLeft: 'auto', fontSize: 10.5, color: 'var(--t3)', flexShrink: 0 }}>{s.started}</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ── SA 소식 ──
|
||||
function SANews() {
|
||||
const SA = window.SA;
|
||||
const [tab, setTab] = useStateH('공지사항');
|
||||
const rows = SA.news[tab];
|
||||
return (
|
||||
<Panel title="SA 소식" en="NOTICE" right={<Seg tabs={['공지사항', 'GM이야기', '업데이트']} value={tab} onChange={setTab} />} bodyPad="2px 14px 6px">
|
||||
{rows.map((r, i) => (
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 2px', ...rowDiv(i, rows.length), cursor: 'pointer' }}>
|
||||
<span style={{ fontFamily: CH, fontSize: 12.5, color: 'var(--t3)', fontWeight: 600, width: 42, flexShrink: 0 }}>{r.date}</span>
|
||||
<span style={{ flex: 1, fontSize: 14, color: '#33373d', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{r.title}</span>
|
||||
</div>
|
||||
))}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 커뮤니티 ──
|
||||
function Community({ onAll }) {
|
||||
const SA = window.SA;
|
||||
const rows = [...SA.community['자유'], ...SA.community['공략']]
|
||||
.sort((a, b) => (b.hot ? 1 : 0) - (a.hot ? 1 : 0))
|
||||
.slice(0, 6);
|
||||
return (
|
||||
<Panel title="커뮤니티" en="COMMUNITY" right={
|
||||
<button onClick={onAll} style={{ display: 'flex', alignItems: 'center', gap: 3, border: 'none', background: 'transparent', cursor: 'pointer', fontFamily: "'Pretendard',sans-serif", fontWeight: 700, fontSize: 12, color: 'var(--t2)', padding: 0 }}>
|
||||
전체 보기 <Icon name="chevR" size={13} stroke="var(--t2)" />
|
||||
</button>
|
||||
} bodyPad="2px 14px 6px">
|
||||
{rows.map((p, i) => (
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '10px 2px', ...rowDiv(i, rows.length), cursor: 'pointer' }}>
|
||||
<span style={{ flexShrink: 0, fontSize: 10.5, fontWeight: 700, padding: '1px 7px', borderRadius: 4, background: '#f1f3f5', border: '1px solid var(--line)', color: '#5a5f66', minWidth: 32, textAlign: 'center' }}>{p.tag}</span>
|
||||
<span style={{ flex: 1, fontSize: 14, color: '#33373d', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.title}</span>
|
||||
{p.hot && <span style={{ fontSize: 10, fontWeight: 800, color: '#b23b3b', fontFamily: CH }}>HOT</span>}
|
||||
<span style={{ fontFamily: CH, fontSize: 12, color: PT, fontWeight: 700, minWidth: 26, textAlign: 'right' }}>{p.cmt}</span>
|
||||
<span style={{ fontSize: 11, color: 'var(--t3)', minWidth: 44, textAlign: 'right' }}>{p.time}</span>
|
||||
</div>
|
||||
))}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 내 전적 요약 ──
|
||||
function MeCard({ onPick }) {
|
||||
const SA = window.SA, me = SA.me;
|
||||
const [scope, setScope] = useStateH('통합');
|
||||
const rankNo = scope === '통합' ? me.rankNoTotal : me.rankNoSeason;
|
||||
const selRank = scope === '통합' ? me.rankTotal : me.rankSeason;
|
||||
return (
|
||||
<Panel title="내 전적" en="MY RECORD" bodyPad="14px 14px" right={<Seg tabs={['통합', '시즌']} value={scope} onChange={setScope} />}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 13 }}>
|
||||
<RankBadge rank={selRank} size={46} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 7 }}>
|
||||
<span style={{ fontSize: 16, fontWeight: 800, color: 'var(--ink)' }}>{me.name}</span>
|
||||
<span style={{ fontSize: 11, color: 'var(--t3)', fontWeight: 600 }}>{me.clan}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, marginTop: 6 }}>
|
||||
<span style={{ fontSize: 11.5, color: 'var(--t3)', fontWeight: 600 }}>{scope} 랭킹</span>
|
||||
<span style={{ fontFamily: CH, fontSize: 15, fontWeight: 700, color: PT }}>{SA.fmt(rankNo)}<span style={{ fontSize: 11, color: 'var(--t3)', fontWeight: 500, marginLeft: 1 }}>위</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', marginTop: 13, border: '1px solid var(--line)', borderRadius: 4 }}>
|
||||
{[['승·패', `${me.wins}/${me.losses}`], ['승률', me.wr + '%'], ['킬뎃', SA.kdPct(me.kda) + '%']].map(([k, v], i) => (
|
||||
<div key={k} style={{ flex: 1, textAlign: 'center', padding: '8px 0', borderLeft: i ? '1px solid var(--line)' : 'none', background: i % 2 ? '#fff' : '#f7f8f9' }}>
|
||||
<div style={{ fontSize: 10.5, color: 'var(--t3)', fontWeight: 600, marginBottom: 3 }}>{k}</div>
|
||||
<div style={{ fontFamily: CH, fontWeight: 700, fontSize: 15.5, color: k === '승률' ? wrColor(me.wr) : 'var(--ink)' }}>{v}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button onClick={() => onPick(me)} style={{ width: '100%', marginTop: 12, padding: '9px 0', border: `1px solid ${PT}`, cursor: 'pointer',
|
||||
background: '#fff', color: PT, fontWeight: 700, fontSize: 13, borderRadius: 4, fontFamily: "'Pretendard',sans-serif" }}>내 전적 자세히 보기 ▸</button>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
// ── SA 랭킹 (5종 · 각 top10) ──
|
||||
const SA_CATS = ['통합 계급', '시즌 계급', '랭크전', '공식 클랜', '클랜'];
|
||||
|
||||
function RankRow({ i, onClick, badge, name, tag, sub, value, unit }) {
|
||||
return (
|
||||
<button onClick={onClick} style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%', border: 'none', background: i % 2 ? '#fff' : '#f7f8f9', padding: '8px 14px', cursor: onClick ? 'pointer' : 'default', textAlign: 'left', borderBottom: '1px solid #eaecef', fontFamily: "'Pretendard',sans-serif" }}>
|
||||
<span style={{ width: 16, textAlign: 'center', fontFamily: CH, fontWeight: 700, fontSize: 13.5, color: i < 3 ? PT : 'var(--t3)', flexShrink: 0 }}>{i + 1}</span>
|
||||
{badge}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
|
||||
<span style={{ fontSize: 13.5, color: 'var(--ink)', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flexShrink: 0, maxWidth: '60%' }}>{name}</span>
|
||||
{tag && <span style={{ fontSize: 10.5, color: 'var(--t3)', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{tag}</span>}
|
||||
</div>
|
||||
{sub && <div style={{ fontSize: 10.5, color: 'var(--t3)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{sub}</div>}
|
||||
</div>
|
||||
<span style={{ fontFamily: CH, fontWeight: 700, fontSize: 13, color: 'var(--ink)', flexShrink: 0, whiteSpace: 'nowrap' }}>{value}{unit && <span style={{ fontSize: 10, color: 'var(--t3)', fontWeight: 500, marginLeft: 2 }}>{unit}</span>}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 즐겨찾기 (페이지네이션) ──
|
||||
function Favorites({ onPick }) {
|
||||
const SA = window.SA;
|
||||
const all = SA.favorites;
|
||||
const PER = 5;
|
||||
const pages = Math.ceil(all.length / PER);
|
||||
const [page, setPage] = useStateH(0);
|
||||
const start = page * PER;
|
||||
const view = all.slice(start, start + PER);
|
||||
return (
|
||||
<Panel title="즐겨찾기" en="FAVORITES" right={<span style={{ fontSize: 11, color: 'var(--t3)', fontWeight: 600 }}>{all.length}명</span>} bodyPad="0">
|
||||
<div>
|
||||
{view.map((f, i) => (
|
||||
<button key={f.name} onClick={() => onPick(f)} style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%', border: 'none', background: (start + i) % 2 ? '#fff' : '#f7f8f9', padding: '9px 14px', cursor: 'pointer', textAlign: 'left', borderBottom: '1px solid #eaecef', fontFamily: "'Pretendard',sans-serif" }}>
|
||||
<RankBadge rank={f.rank} size={30} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
|
||||
<span style={{ fontSize: 13.5, color: 'var(--ink)', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flexShrink: 0, maxWidth: '60%' }}>{f.name}</span>
|
||||
{f.clan && <span style={{ fontSize: 10.5, color: 'var(--t3)', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.clan}</span>}
|
||||
</div>
|
||||
<div style={{ fontSize: 10.5, color: 'var(--t3)', marginTop: 1 }}>{f.rank.name} · 승률 {f.wr}%</div>
|
||||
</div>
|
||||
<span style={{ fontFamily: CH, fontWeight: 700, fontSize: 12.5, color: PT, flexShrink: 0 }}>킬뎃 {SA.kdPct(f.kda)}%</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4, padding: '10px 0' }}>
|
||||
<button onClick={() => setPage(Math.max(0, page - 1))} disabled={page === 0} style={pgBtn(page === 0)}>‹</button>
|
||||
{Array.from({ length: pages }).map((_, i) => (
|
||||
<button key={i} onClick={() => setPage(i)} style={{ width: 26, height: 26, borderRadius: 4, border: '1px solid ' + (i === page ? PT : 'var(--line2)'), background: i === page ? PT : '#fff', color: i === page ? '#fff' : 'var(--t2)', cursor: 'pointer', fontFamily: CH, fontWeight: 700, fontSize: 12 }}>{i + 1}</button>
|
||||
))}
|
||||
<button onClick={() => setPage(Math.min(pages - 1, page + 1))} disabled={page === pages - 1} style={pgBtn(page === pages - 1)}>›</button>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
function pgBtn(disabled) {
|
||||
return { width: 26, height: 26, borderRadius: 4, border: '1px solid var(--line2)', background: '#fff', color: disabled ? 'var(--line2)' : 'var(--t2)', cursor: disabled ? 'default' : 'pointer', fontFamily: "'Chakra Petch',sans-serif", fontWeight: 700, fontSize: 14 };
|
||||
}
|
||||
|
||||
function SARanking({ onPick, onClan }) {
|
||||
const SA = window.SA;
|
||||
const [cat, setCat] = useStateH('통합 계급');
|
||||
const [mode, setMode] = useStateH('솔로');
|
||||
const tcol = Object.fromEntries(SA.tiers.map((t) => [t.name, t.color]));
|
||||
const isClass = cat === '시즌 계급' || cat === '통합 계급';
|
||||
const isRanked = cat === '랭크전';
|
||||
const isRankedUser = isRanked && mode !== '클랜';
|
||||
const isRankedClan = isRanked && mode === '클랜';
|
||||
const isClan = cat === '공식 클랜' || cat === '클랜';
|
||||
const classList = cat === '시즌 계급' ? SA.seasonClassRanking : SA.totalClassRanking;
|
||||
const contentList = mode === '솔로' ? SA.soloRpRanking : SA.partyRpRanking;
|
||||
const clanList = cat === '공식 클랜' ? SA.officialClanRanking : SA.generalClanRanking;
|
||||
|
||||
return (
|
||||
<Panel title="SA 랭킹" en="RANKING" right={<span style={{ fontSize: 11, color: 'var(--t3)', fontWeight: 600 }}>TOP 10</span>} bodyPad="11px 0 0">
|
||||
{/* 카테고리 선택 */}
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, padding: '0 14px 10px' }}>
|
||||
{SA_CATS.map((c) => {
|
||||
const on = c === cat;
|
||||
return (
|
||||
<button key={c} onClick={() => setCat(c)} style={{ padding: '5px 10px', borderRadius: 4, border: `1px solid ${on ? PT : 'var(--line2)'}`, cursor: 'pointer',
|
||||
fontFamily: "'Pretendard',sans-serif", fontWeight: 700, fontSize: 11.5, background: on ? PT : '#fff', color: on ? '#fff' : 'var(--t2)' }}>{c}</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 랭크전 하위 토글 */}
|
||||
{isRanked && (
|
||||
<div style={{ padding: '0 14px 11px' }}>
|
||||
<div style={{ display: 'flex', border: '1px solid var(--line2)', borderRadius: 4, overflow: 'hidden', background: '#fff' }}>
|
||||
{['솔로', '파티', '클랜'].map((t, i) => {
|
||||
const on = mode === t;
|
||||
return (
|
||||
<button key={t} onClick={() => setMode(t)} style={{ flex: 1, padding: '6px 0', border: 'none', borderLeft: i ? '1px solid var(--line2)' : 'none', cursor: 'pointer',
|
||||
fontFamily: "'Pretendard',sans-serif", fontWeight: 700, fontSize: 12, background: on ? PT : '#fff', color: on ? '#fff' : 'var(--t2)' }}>{t}</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 계급 랭킹 */}
|
||||
{isClass && classList.map((e, i) => {
|
||||
const p = SA.allPlayers.find((x) => x.name === e.name);
|
||||
return (
|
||||
<RankRow key={e.name} i={i} onClick={() => p && onPick(p)}
|
||||
badge={p ? <RankBadge rank={p.rank} size={28} /> : null}
|
||||
tag={p && p.clan} name={e.name} sub={p && p.rank.name}
|
||||
value={SA.fmt(e.score)} unit="점" />
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 랭크전(솔로/파티) 랭킹 */}
|
||||
{isRankedUser && contentList.map((e, i) => {
|
||||
const p = SA.allPlayers.find((x) => x.name === e.name);
|
||||
const col = tcol[e.tier] || PT;
|
||||
return (
|
||||
<RankRow key={e.name} i={i} onClick={() => p && onPick(p)}
|
||||
badge={<div style={{ width: 28, height: 28, borderRadius: 4, background: col, display: 'grid', placeItems: 'center', color: '#fff', fontWeight: 800, fontSize: 12.5, flexShrink: 0 }}>{e.tier[0]}</div>}
|
||||
tag={p && p.clan} name={e.name} sub={<span style={{ color: col, fontWeight: 600 }}>{e.tier}</span>}
|
||||
value={SA.fmt(e.rp)} unit="RP" />
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 랭크전(클랜) 랭킹 — 클랜 정보 */}
|
||||
{isRankedClan && SA.clanRpRanking.map((c, i) => (
|
||||
<RankRow key={c.name} i={i} onClick={() => onClan && onClan(c.name)}
|
||||
badge={<div style={{ width: 28, height: 28, borderRadius: 4, background: '#23262b', display: 'grid', placeItems: 'center', fontFamily: CH, fontWeight: 700, fontSize: 10, color: '#fff', flexShrink: 0 }}>{c.tag}</div>}
|
||||
name={c.name} sub={`${c.members}명`} value={SA.fmt(c.rp)} unit="RP" />
|
||||
))}
|
||||
|
||||
{/* 클랜 랭킹 */}
|
||||
{isClan && clanList.map((c, i) => (
|
||||
<RankRow key={c.name} i={i} onClick={() => onClan && onClan(c.name)}
|
||||
badge={<div style={{ width: 28, height: 28, borderRadius: 4, background: '#23262b', display: 'grid', placeItems: 'center', fontFamily: CH, fontWeight: 700, fontSize: 10, color: '#fff', flexShrink: 0 }}>{c.tag}</div>}
|
||||
name={<span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>{c.name}{cat === '공식 클랜' && <span style={{ fontSize: 9, fontWeight: 700, color: PT, border: `1px solid ${PT}`, borderRadius: 3, padding: '0 4px', lineHeight: 1.5 }}>인증</span>}</span>}
|
||||
sub={`${c.members}명`} value={SA.fmt(c.rp)} unit="RP" />
|
||||
))}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 인기 검색 랭킹(가장 많이 조회된 유저) ──
|
||||
function SearchRank({ onPick }) {
|
||||
const SA = window.SA;
|
||||
const trend = (m) => {
|
||||
if (m === 'new') return <span style={{ fontFamily: CH, fontSize: 9.5, fontWeight: 700, color: '#b23b3b', width: 28, textAlign: 'center', flexShrink: 0 }}>NEW</span>;
|
||||
if (m === 0) return <span style={{ fontSize: 11, color: 'var(--t3)', width: 28, textAlign: 'center', flexShrink: 0 }}>–</span>;
|
||||
const up = m > 0;
|
||||
return <span style={{ fontFamily: CH, fontSize: 11, fontWeight: 700, color: up ? PT : '#e5484d', width: 28, textAlign: 'center', flexShrink: 0 }}>{up ? '▲' : '▼'}{Math.abs(m)}</span>;
|
||||
};
|
||||
return (
|
||||
<Panel title="인기 검색" en="TRENDING" right={<span style={{ fontSize: 11, color: 'var(--t3)', fontWeight: 600 }}>최근 24시간</span>} bodyPad="4px 0 0">
|
||||
{SA.searchTrending.map((s, i) => {
|
||||
const p = SA.allPlayers.find((x) => x.name === s.name);
|
||||
return (
|
||||
<button key={s.name} onClick={() => p && onPick(p)} style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%', border: 'none', background: i % 2 ? '#fff' : '#f7f8f9', padding: '8px 14px', cursor: p ? 'pointer' : 'default', textAlign: 'left', borderBottom: '1px solid #eaecef', fontFamily: "'Pretendard',sans-serif" }}>
|
||||
<span style={{ width: 16, textAlign: 'center', fontFamily: CH, fontWeight: 700, fontSize: 13.5, color: i < 3 ? PT : 'var(--t3)' }}>{i + 1}</span>
|
||||
{trend(s.move)}
|
||||
<div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 13.5, color: 'var(--ink)', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flexShrink: 0, maxWidth: '60%' }}>{s.name}</span>
|
||||
{p && p.clan && <span style={{ fontSize: 10.5, color: 'var(--t3)', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.clan}</span>}
|
||||
</div>
|
||||
<span style={{ fontFamily: CH, fontSize: 12, color: 'var(--t2)', fontWeight: 600, flexShrink: 0 }}>{SA.fmt(s.count)}<span style={{ fontSize: 10, color: 'var(--t3)', fontWeight: 500 }}> 조회</span></span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 시즌 현황 ──
|
||||
function SeasonCard() {
|
||||
const SA = window.SA, s = SA.season;
|
||||
const [mapTab, setMapTab] = useStateH('5vs5');
|
||||
const maps = mapTab === '5vs5' ? s.maps5v5 : s.maps3v3;
|
||||
return (
|
||||
<Panel title="시즌 현황" en="SEASON" right={<span style={{ fontFamily: CH, fontSize: 12.5, fontWeight: 700, color: PT }}>{s.dday}</span>} bodyPad="14px 14px">
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 11, marginBottom: 13 }}>
|
||||
<div style={{ width: 42, height: 42, borderRadius: 6, background: '#23262b', display: 'grid', placeItems: 'center', color: '#fff', fontFamily: CH, fontWeight: 700, fontSize: 14, flexShrink: 0 }}>S2</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 800, color: 'var(--ink)', lineHeight: 1.25 }}>{s.name}</div>
|
||||
<div style={{ fontSize: 11.5, color: 'var(--t3)', marginTop: 2 }}>{s.period} 종료</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ height: 7, background: '#d4d8de', borderRadius: 4, overflow: 'hidden', boxShadow: 'inset 0 0 0 1px rgba(0,0,0,.05)' }}>
|
||||
<div style={{ width: s.progress + '%', height: '100%', background: PT, borderRadius: 4 }} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--t3)', marginTop: 5 }}>
|
||||
<span>시즌 진행</span><span style={{ fontFamily: CH, color: 'var(--t2)', fontWeight: 600 }}>{s.progress}%</span>
|
||||
</div>
|
||||
<div style={{ marginTop: 14 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 7 }}>
|
||||
<div style={{ fontSize: 10.5, color: 'var(--t3)', fontWeight: 600 }}>시즌 맵</div>
|
||||
<div style={{ display: 'flex', border: '1px solid var(--line2)', borderRadius: 4, overflow: 'hidden', background: '#fff' }}>
|
||||
{['5vs5', '3vs3'].map((t, i) => {
|
||||
const on = mapTab === t;
|
||||
return (
|
||||
<button key={t} onClick={() => setMapTab(t)} style={{ padding: '3px 10px', border: 'none', borderLeft: i ? '1px solid var(--line2)' : 'none', cursor: 'pointer',
|
||||
fontFamily: CH, fontWeight: 700, fontSize: 10.5, background: on ? PT : '#fff', color: on ? '#fff' : 'var(--t2)' }}>{t}</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
{maps.map((m) => <span key={m} style={{ padding: '4px 10px', borderRadius: 4, background: '#fff', border: '1px solid var(--line2)', fontSize: 11.5, color: 'var(--t2)', fontWeight: 500 }}>{m}</span>)}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 폼 좋은 유저 (연승 / 승률 / 킬뎃 탭) ──
|
||||
function HotStreak({ onPick }) {
|
||||
const SA = window.SA;
|
||||
const [tab, setTab] = useStateH('연승');
|
||||
let rows;
|
||||
if (tab === '연승') {
|
||||
rows = SA.hotStreak.map((e) => ({ p: SA.allPlayers.find((x) => x.name === e.name), streak: e.streak }));
|
||||
} else if (tab === '승률') {
|
||||
rows = [...SA.allPlayers].sort((a, b) => b.wr - a.wr).slice(0, 6).map((p) => ({ p }));
|
||||
} else {
|
||||
rows = [...SA.allPlayers].sort((a, b) => b.kda - a.kda).slice(0, 6).map((p) => ({ p }));
|
||||
}
|
||||
const metric = (p, streak) => {
|
||||
if (tab === '연승') return (
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: 3, color: '#e5484d' }}>
|
||||
<Icon name="flame" size={14} stroke="#e5484d" />
|
||||
<span style={{ fontFamily: CH, fontSize: 14, fontWeight: 700 }}>{streak}</span><span style={{ fontSize: 10.5, fontWeight: 700 }}>연승</span>
|
||||
</span>
|
||||
);
|
||||
if (tab === '승률') return <span style={{ fontFamily: CH, fontSize: 15, fontWeight: 700, color: wrColor(p.wr) }}>{p.wr}%</span>;
|
||||
return <span style={{ fontFamily: CH, fontSize: 15, fontWeight: 700, color: PT }}>{SA.kdPct(p.kda)}%</span>;
|
||||
};
|
||||
return (
|
||||
<Panel title="폼 좋은 유저" en="HOT STREAK" right={<Seg tabs={['연승', '승률', '킬뎃']} value={tab} onChange={setTab} />} bodyPad="12px 14px 14px">
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2,1fr)', gap: 10 }}>
|
||||
{rows.map(({ p, streak }, i) => p && (
|
||||
<button key={p.name} onClick={() => onPick(p)} style={{ display: 'flex', alignItems: 'center', gap: 10, border: '1px solid var(--line2)', borderRadius: 5, background: '#fff', padding: '10px 12px', cursor: 'pointer', textAlign: 'left', fontFamily: "'Pretendard',sans-serif" }}>
|
||||
<span style={{ width: 14, textAlign: 'center', fontFamily: CH, fontWeight: 700, fontSize: 12.5, color: i < 3 ? PT : 'var(--t3)', flexShrink: 0 }}>{i + 1}</span>
|
||||
<RankBadge rank={p.rank} size={30} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
|
||||
<span style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flexShrink: 0, maxWidth: '60%' }}>{p.name}</span>
|
||||
{p.clan && <span style={{ fontSize: 10.5, color: 'var(--t3)', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.clan}</span>}
|
||||
</div>
|
||||
<div style={{ fontSize: 10.5, color: 'var(--t3)', marginTop: 1 }}>{p.rank.name}</div>
|
||||
</div>
|
||||
<div style={{ flexShrink: 0 }}>{metric(p, streak)}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 오늘의 전장 ──
|
||||
function LiveStats() {
|
||||
const SA = window.SA, L = SA.liveStats;
|
||||
return (
|
||||
<Panel title="오늘의 전장" en="TODAY" bodyPad="14px 14px">
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 7, marginBottom: 12, whiteSpace: 'nowrap' }}>
|
||||
<span style={{ fontFamily: CH, fontWeight: 700, fontSize: 26, color: 'var(--ink)', letterSpacing: '-.5px' }}>{SA.fmt(L.totalMatches)}</span>
|
||||
<span style={{ fontSize: 12.5, color: 'var(--t2)' }}>매치</span>
|
||||
<span style={{ marginLeft: 'auto', fontFamily: CH, fontSize: 12, color: PT, fontWeight: 700 }}>▲ {L.prevDelta}%</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', borderTop: '1px solid var(--line)' }}>
|
||||
{L.matchModes.map(({ name, count }) => (
|
||||
<div key={name} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '8px 0', borderBottom: '1px solid #eaecef' }}>
|
||||
<span style={{ fontSize: 12.5, color: 'var(--t2)' }}>{name}</span>
|
||||
<span style={{ fontFamily: CH, fontWeight: 600, fontSize: 13, color: 'var(--ink)', whiteSpace: 'nowrap' }}>{SA.fmt(count)}</span>
|
||||
</div>
|
||||
))}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '8px 0' }}>
|
||||
<span style={{ fontSize: 12.5, color: 'var(--t2)' }}>오늘 제재</span>
|
||||
<span style={{ fontFamily: CH, fontWeight: 600, fontSize: 13, color: '#b23b3b', whiteSpace: 'nowrap' }}>{L.bannedToday}명</span>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
function Footer() {
|
||||
return (
|
||||
<div style={{ borderTop: '1px solid var(--line2)', background: '#fff' }}>
|
||||
<div style={{ maxWidth: 1180, margin: '0 auto', padding: '24px 28px 34px', display: 'flex', alignItems: 'flex-start', gap: 20 }}>
|
||||
<Logo size={18} />
|
||||
<div style={{ display: 'flex', gap: 16, marginLeft: 8 }}>
|
||||
{['이용약관', '개인정보처리방침', '공지사항', '문의하기'].map((t) => (
|
||||
<a key={t} onClick={() => { if (t === '문의하기') window.dispatchEvent(new Event('sa-open-feedback')); }} style={{ fontSize: 12.5, color: 'var(--t2)', cursor: 'pointer' }}>{t}</a>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ marginLeft: 'auto', textAlign: 'right', fontSize: 11.5, color: 'var(--t3)', lineHeight: 1.7 }}>
|
||||
searchgg는 서든어택 전적 조회를 위한 비공식 팬 사이트입니다. 본 페이지의 데이터는 데모용 예시입니다.<br />© 2026 searchgg
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 모집 중인 클랜 (포스터) ──
|
||||
function RecruitPosters({ onPlaza, onOpenClan }) {
|
||||
let pool = window.ClanRecruits ? window.ClanRecruits() : [];
|
||||
try { if (localStorage.getItem('sa-my-recruit-active') === '0') pool = pool.filter((c) => c.name !== (window.SA.me.clan || '')); } catch (e) {}
|
||||
const list = pool.slice().sort((a, b) => b.hot - a.hot).slice(0, 4);
|
||||
if (!list.length) return null;
|
||||
return (
|
||||
<Panel title="모집 중인 클랜" en="RECRUIT" right={
|
||||
<button onClick={onPlaza} style={{ display: 'flex', alignItems: 'center', gap: 3, border: 'none', background: 'transparent', cursor: 'pointer', fontFamily: "'Pretendard',sans-serif", fontWeight: 700, fontSize: 12, color: 'var(--t2)', padding: 0 }}>
|
||||
전체 보기 <Icon name="chevR" size={13} stroke="var(--t2)" />
|
||||
</button>
|
||||
} bodyPad="14px 14px">
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 12 }}>
|
||||
{list.map((rc) => (
|
||||
<div key={rc.name} onClick={onPlaza} style={{ cursor: 'pointer' }}>
|
||||
<div style={{ aspectRatio: '3 / 4', border: '1px solid var(--line)', borderRadius: 5, overflow: 'hidden', background: '#f4f5f7' }}>
|
||||
<image-slot id={`recruit-poster-${rc.name}`} shape="rect" placeholder="홍보 포스터" style={{ display: 'block', width: '100%', height: '100%' }}></image-slot>
|
||||
</div>
|
||||
<div style={{ marginTop: 8, display: 'flex', alignItems: 'center', gap: 7 }}>
|
||||
<window.Emblem cfg={rc.emblem} size={20} />
|
||||
<span style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{rc.name}</span>
|
||||
</div>
|
||||
<div style={{ marginTop: 2, fontSize: 10.5, color: 'var(--t3)', paddingLeft: 27 }}>랭킹 {rc.rank}위 · {rc.openings}명 모집</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
function Home() {
|
||||
const [sel, setSel] = useStateH(null);
|
||||
const [login, setLogin] = useStateH(false);
|
||||
const [clan, setClan] = useStateH(null);
|
||||
const [plaza, setPlaza] = useStateH(false);
|
||||
const [community, setCommunity] = useStateH(false);
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', background: 'var(--bg2)', color: 'var(--ink)', fontFamily: "'Pretendard',sans-serif" }}>
|
||||
<Nav active="홈" onLogin={() => setLogin(true)} onClan={() => setPlaza(true)} onCommunity={() => setCommunity(true)} />
|
||||
<Hero onPick={setSel} />
|
||||
<div style={{ maxWidth: 1180, margin: '0 auto', padding: '20px 28px 40px' }}>
|
||||
<EventStrip />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 348px', gap: 18, alignItems: 'start' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
||||
<StreamLive onPick={setSel} />
|
||||
<HotStreak onPick={setSel} />
|
||||
<RecruitPosters onPlaza={() => setPlaza(true)} onOpenClan={(n) => setClan(n)} />
|
||||
<SANews />
|
||||
<Community onAll={() => setCommunity(true)} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
||||
<MeCard onPick={setSel} />
|
||||
<Favorites onPick={setSel} />
|
||||
<SeasonCard />
|
||||
<SARanking onPick={setSel} onClan={setClan} />
|
||||
<SearchRank onPick={setSel} />
|
||||
<LiveStats />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
{sel && <ProfileScreen player={sel} onClose={() => setSel(null)} onPick={setSel} onLogin={() => setLogin(true)} onClan={(n) => { setSel(null); setClan(n); }} onCommunity={() => { setSel(null); setCommunity(true); }} />}
|
||||
{plaza && <ClanPlaza onClose={() => setPlaza(false)} onOpenClan={(n) => setClan(n)} onLogin={() => setLogin(true)} onClan={() => setPlaza(true)} onCommunity={() => { setPlaza(false); setCommunity(true); }} />}
|
||||
{community && <CommunityScreen onClose={() => setCommunity(false)} onLogin={() => setLogin(true)} onClan={() => { setCommunity(false); setPlaza(true); }} />}
|
||||
{clan && <ClanHome clanName={clan} onClose={() => setClan(null)} onPick={(n) => { setClan(null); setSel(window.SA.allPlayers.find((p) => p.name === n) || window.SA.me); }} onLogin={() => setLogin(true)} onClan={() => { setClan(null); setPlaza(true); }} onCommunity={() => { setClan(null); setPlaza(false); setCommunity(true); }} />}
|
||||
{login && <LoginScreen onClose={() => setLogin(false)} />}
|
||||
<FeedbackWidget />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Object.assign(window, { Home, TopBar, Nav, Footer, Panel, Seg, PT, CH });
|
||||
Reference in New Issue
Block a user