/* eslint-disable */ // ───────────────────────────────────────────────────────────── // searchgg · 커뮤니티 (목록 화면) // 게시판: 자유 / 공략·팁 / 질문 / 파티 모집 — 텍스트 리스트형 // 사이드: 인기글(HOT) · 주간 베스트 // ───────────────────────────────────────────────────────────── const CMPT = '#48515f'; const CMCH = "'Chakra Petch',sans-serif"; // ── 게시글 더미 데이터 (시드 고정) ── const CM_BOARDS = ['자유', '공략·팁', '질문', '파티 모집']; const CM_POSTS = (() => { const titles = { '자유': ['오늘 제3보급 진짜 미친 매치 했다', '시즌2 보상 다들 받았음?', '요즘 저녁 큐 잡히는 속도 어떰', '복귀했는데 감도 설정 다 까먹음', '클랜전 하다가 현타온 썰', '이번 패치 평가 어떻게 생각함', '10년차 유저인데 아직도 재밌네', '오랜만에 5연승 함 ㅋㅋ', '새벽 매치 분위기 좋아서 글 남김', '내일 점검이라는데 뭐 바뀜?', '신캐 나오면 누구 뽑을 거임', '오늘자 명장면.gif 올림'], '공략·팁': ['데드폴 수비 루트 총정리 (시즌2 기준)', '웨어하우스 러시 타이밍 분석', '저감도 vs 고감도 — 결론 정리해줌', '라이플 반동 제어 연습법 공유', '컨테이너시티 스나 포지션 5곳', '클랜 랭크전 픽 우선순위 가이드', '초보가 제일 많이 하는 실수 7가지', '3vs3 맵별 스폰 위치 정리', '에임 워밍업 루틴 추천', '파티 랭크전 콜 사인 정리본'], '질문': ['랭크 점수 산정 방식 아시는 분?', '티어 강등 기준이 어떻게 되나요', '클랜 옮기면 클랜전 기록 초기화되나요?', '모니터 144hz 체감 큰가요', '복귀 유저인데 뭐부터 하면 됨?', '계급이랑 티어 차이가 뭔가요', '신고하면 처리 결과 알 수 있나요', '파티 랭크전 듀오 가능한가요', '토너먼트 참가 조건 아시는 분', '전적 갱신이 안 되는데 저만 그런가요'], '파티 모집': ['파티 랭크전 같이 하실 분 (골드 이상)', '저녁 9시 클랜전 용병 1명 구해요', '3보급 내전 인원 모집합니다', '주말 토너먼트 팀원 구함 (스나 1)', '심야 듀오 구합니다 — 마스터 부근', '즐겜 파티 상시 모집 중', '음성 가능한 파티 멤버 2명', 'A보급 연습 같이 하실 분', '평일 오후 파티 고정 멤버 구해요', '랭커 도전 듀오 모집 (진지하게)'], }; const nicks = ['독수리오형제', '한밤의저격수', '돌격대장', '연사의신', '클러치킹', '광속에임', '벽뚫는소리', '플래시뱅', '노스코프', '리코일제로', '소음기장착', '검은표범', '강철멘탈', '야시경', '엄폐엄폐', '침착한손', '스나는예술', '원피스원킬']; const out = []; let id = 0; CM_BOARDS.forEach((board, bi) => { titles[board].forEach((title, ti) => { const seed = (bi * 131 + ti * 17 + title.length * 7) % 9973; const hours = 1 + (seed % 70); out.push({ id: ++id, board, title, author: nicks[(seed + bi) % nicks.length], time: hours < 24 ? `${hours}시간 전` : `${Math.floor(hours / 24)}일 전`, hoursAgo: hours, views: 120 + (seed % 4200), cmt: seed % 46, up: seed % 88, hot: seed % 5 === 0, hasImage: seed % 3 === 0, party: board === '파티 모집' ? { ptype: ['클랜전', '파티 랭크전', '클랜 랭크전', '토너먼트', '생존', '기타'][seed % 6], voice: seed % 3 !== 0, } : null, }); }); }); return out.sort((a, b) => a.hoursAgo - b.hoursAgo); })(); // ── 고정 공지 ── const CM_NOTICES = [ { title: '커뮤니티 이용 규칙 안내 — 비방·도배·정치 글 제재', time: '운영팀' }, { title: '파티 모집 글은 파티 모집 게시판에만 작성해주세요', time: '운영팀' }, ]; // 파티 모집 유형 const CM_PARTY_TYPES = ['클랜전', '파티 랭크전', '클랜 랭크전', '토너먼트', '생존', '기타']; // 파티 모집 조건 → 배지 라벨 배열 function cmPartyBadges(party) { return [party.ptype, party.voice ? '보이스 채팅 필수' : null].filter(Boolean); } // 개념글 기준 추천 수 const CM_BEST_UP = 40; // 서든어택 계정 인증 여부 (닉네임 시드 고정) function cmVerified(name) { if (!name) return false; if (window.SA && window.SA.me && name === window.SA.me.name) return true; let s = 0; for (let i = 0; i < name.length; i++) s = (s + name.charCodeAt(i) * (i + 1)) % 997; return s % 3 !== 0; // 약 2/3 인증 } // 인증 배지 (인증=서든 마크 / 미인증=표시 없음) function CmVerifiedTag({ name, size = 13 }) { if (!cmVerified(name)) return null; return ( ); } function CmAuthor({ name, style }) { return ( {name} ); } // ── 글 상세 더미 (본문·댓글, 시드 고정) ── const CM_BODIES = { '자유': '오늘 매치 돌리면서 느낀 점 적어봅니다.\n\n요즘 큐 잡히는 속도도 그렇고 전반적인 매치 퀄리티가 시즌 초반보다 확실히 좋아진 느낌이에요. 다들 체감 어떠신지 궁금합니다.\n\n댓글로 의견 남겨주세요!', '공략·팁': '직접 테스트해보고 정리한 내용입니다.\n\n1. 진입 타이밍은 소리 정보가 제일 중요합니다. 발소리 출력을 충분히 키우세요.\n2. 교전 전에 미니맵으로 아군 위치를 확인하는 습관을 들이면 생존율이 크게 올라갑니다.\n3. 마지막으로, 장시간 연승보다 짧게 끊어서 집중하는 게 에임 유지에 좋았습니다.\n\n궁금한 점은 댓글로 물어보세요.', '질문': '검색해봐도 정확한 내용이 안 나와서 질문 남깁니다.\n\n아시는 분 계시면 답변 부탁드려요. 미리 감사합니다!', '파티 모집': '안녕하세요, 글 제목 그대로 함께하실 분 찾습니다.\n\n· 매너 좋으신 분이면 실력 무관\n· 저녁 시간대 위주로 활동합니다\n\n댓글이나 쪽지 주세요!', }; const CM_CMT_TEXTS = ['완전 공감합니다 ㅋㅋ', '좋은 글 감사합니다. 추천 누르고 갑니다', '이거 진짜 유용하네요. 저장해둡니다', '저는 생각이 좀 다른데... 그래도 잘 봤습니다', '댓글 보고 바로 해봤는데 효과 있음', '이런 글 많이 올라오면 좋겠어요', '쪽지 보냈습니다!', '오 나도 어제 비슷한 경험 함', '정리 깔낁하네요 잘 읽었습니다', '이거 때문에 요즘 매치 분위기 좋아짐']; const CM_CMT_NICKS = ['안개손절이', '플래그팔로우', '리스폰지옥', '점수지키미', '안전제일', '한발더', '에임수엽', '무릎샇소리']; function cmDetail(p) { const seed = (p.id * 37) % 9973; const n = 2 + (seed % 5); const comments = []; for (let i = 0; i < n; i++) { comments.push({ author: CM_CMT_NICKS[(seed + i * 3) % CM_CMT_NICKS.length], text: CM_CMT_TEXTS[(seed + i * 5) % CM_CMT_TEXTS.length], time: `${1 + ((seed + i * 7) % 20)}시간 전`, up: (seed + i * 11) % 18, replies: i === 0 ? [{ author: CM_CMT_NICKS[(seed + 5) % CM_CMT_NICKS.length], text: '맞아요, 저도 비슷하게 느꼈습니다.', time: `${1 + (seed % 12)}시간 전` }] : [], }); } return { body: p.body || CM_BODIES[p.board], comments }; } // ── 목록 행 ── function CmRow({ p, zebra, onOpen }) { return (
onOpen && onOpen(p)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '9px 14px', background: zebra ? '#fbfbfc' : '#fff', borderBottom: '1px solid #eef0f2', cursor: 'pointer' }}> {p.board} {p.title} {p.hasImage && ( )} {p.cmt > 0 && [{p.cmt}]} {p.hot && HOT} {p.party && ( {cmPartyBadges(p.party).slice(0, 3).map((b) => ( {b} ))} )} = CM_BEST_UP ? CMPT : 'var(--t3)', fontWeight: p.up >= CM_BEST_UP ? 700 : 400 }}>추천 {p.up} {p.time} {window.SA.fmt(p.views)}
); } // ── 사이드 위젯 ── function CmSidePanel({ title, en, posts, valueOf, onOpen }) { return ( {posts.map((p, i) => (
onOpen && onOpen(p)} style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '9px 14px', borderBottom: i < posts.length - 1 ? '1px solid #f1f2f4' : 'none', cursor: 'pointer' }}> {i + 1} {p.title} {valueOf(p)}
))}
); } // ── 글 상세 ── function CmPostDetail({ p, onBack }) { const d = React.useMemo(() => cmDetail(p), [p.id]); const [comments, setComments] = React.useState(d.comments); const [draft, setDraft] = React.useState(''); const [up, setUp] = React.useState(p.up); const [voted, setVoted] = React.useState(0); // 0 없음 / 1 추천 / -1 비추 React.useEffect(() => { setComments(d.comments); setDraft(''); setUp(p.up); setVoted(0); setCmtVoted({}); }, [p.id]); const vote = (v) => { if (voted === v) { setVoted(0); setUp(up - v); return; } setUp(up - voted + v); setVoted(v); }; const delCmt = (i) => setComments(comments.map((c, idx) => idx === i ? { ...c, deleted: true } : c)); const post = () => { const text = draft.trim(); if (!text) return; setComments([...comments, { author: window.SA.me.name, text, time: '방금 전', up: 0, replies: [] }]); setDraft(''); }; const [replyAt, setReplyAt] = React.useState(-1); const [replyDraft, setReplyDraft] = React.useState(''); const [cmtVoted, setCmtVoted] = React.useState({}); const voteCmt = (i) => setCmtVoted((v) => ({ ...v, [i]: !v[i] })); const postReply = (ci) => { const text = replyDraft.trim(); if (!text) return; setComments(comments.map((c, idx) => idx === ci ? { ...c, replies: [...(c.replies || []), { author: window.SA.me.name, text, time: '방금 전' }] } : c)); setReplyDraft(''); setReplyAt(-1); }; return (
{/* 상단 */}
{p.board}
{/* 제목 + 메타 */}

{p.title}

·{p.time} ·조회 {window.SA.fmt(p.views)} ·추천 = CM_BEST_UP ? CMPT : 'var(--t2)' }}>{up}
{p.party && (
{cmPartyBadges(p.party).map((b) => ( {b} ))}
)}
{/* 본문 */}
{d.body}
{/* 추천/비추 */}
{/* 댓글 */}
댓글 {comments.length}
{comments.map((c, i) => (
{c.deleted ? '–' : c.author.slice(0, 1)}
{c.deleted ? (
삭제된 댓글입니다.
) : (
{c.time} {c.author === window.SA.me.name && ( )}
{c.text}
)} {/* 답글 목록 */} {(c.replies || []).map((rp, j) => (
{rp.author.slice(0, 1)}
{rp.time}
{rp.text}
))} {/* 답글 입력 */} {replyAt === i && (
setReplyDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') postReply(i); }} placeholder="답글을 남겨보세요" style={{ flex: 1, minWidth: 0, border: '1px solid var(--line2)', borderRadius: 6, padding: '7px 10px', fontSize: 12.5, fontFamily: "'Pretendard',sans-serif", outline: 'none' }} />
)}
))}
setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') post(); }} placeholder="댓글을 남겨보세요" style={{ flex: 1, minWidth: 0, border: '1px solid var(--line2)', borderRadius: 6, padding: '10px 12px', fontSize: 13, fontFamily: "'Pretendard',sans-serif", outline: 'none' }} />
); } // ── 글쓰기 화면 (일반 / 파티 모집) ── function CmWrite({ initialBoard, onCancel, onSubmit }) { const [board, setBoard] = React.useState(initialBoard && initialBoard !== '전체' ? initialBoard : '자유'); const [title, setTitle] = React.useState(''); const [body, setBody] = React.useState(''); const [hasImage, setHasImage] = React.useState(false); const fileRef = React.useRef(null); // 파티 모집 전용 const [ptype, setPtype] = React.useState(CM_PARTY_TYPES[0]); const [voice, setVoice] = React.useState(true); const isParty = board === '파티 모집'; const valid = title.trim() && body.trim(); const label = { fontSize: 12, fontWeight: 700, color: 'var(--t2)', marginBottom: 7 }; const chip = (on) => ({ padding: '6px 12px', borderRadius: 5, border: `1px solid ${on ? 'var(--ink)' : 'var(--line2)'}`, cursor: 'pointer', fontFamily: "'Pretendard',sans-serif", fontWeight: 700, fontSize: 12, background: on ? 'var(--ink)' : '#fff', color: on ? '#fff' : 'var(--t2)' }); const inputBase = { width: '100%', border: '1px solid var(--line2)', borderRadius: 7, padding: '10px 12px', fontSize: 13.5, fontFamily: "'Pretendard',sans-serif", color: 'var(--ink)', outline: 'none', boxSizing: 'border-box' }; const submit = () => { if (!valid) return; onSubmit({ board, title: title.trim(), author: window.SA.me.name, time: '방금 전', hoursAgo: 0, views: 1, cmt: 0, up: 0, hot: false, hasImage, body: body.trim(), party: isParty ? { ptype, voice } : null, }); }; return (
글쓰기
{/* 게시판 */}
게시판
{CM_BOARDS.map((b) => { const on = board === b; return ; })}
{/* 제목 */}
제목
setTitle(e.target.value)} placeholder={isParty ? '예: 저녁 9시 파티 랭크전 같이 하실 분' : '제목을 입력하세요'} style={inputBase} />
{/* 모집 유형 */} {isParty && (
모집 유형
{CM_PARTY_TYPES.map((t) => ( ))}
)} {/* 참여 조건 */} {isParty && (
참여 조건
)} {/* 내용 */}
내용