// 웹 스크린 C — 모임 후 연결 · 내 모임 · Q&A
function WScrPost({S,set,go}){
  const {useState} = React;
  const m = MEETUPS[S.detailId] || null;
  if (!m) return <div className="wapp"><WNav S={S} go={go} set={set}/><div className="wmain"><MeetupNotFoundNotice go={go}/></div></div>;
  const [attended, setAttended] = useState(null);
  const [rating, setRating] = useState(0);
  const [unsafe, setUnsafe] = useState('');
  const [note, setNote] = useState('');
  const surveyDone = S.survey!=null;
  const submitSurvey = () => set({survey:'ok', toast:'설문 감사해요 — 다음 추천에 반영할게요'});
  const reportFlow = () => set({reportOpen:true, reportPrefill:m.title});
  return <div className="wapp"><WNav S={S} go={go} set={set}/>
    <div className="wmain" style={{maxWidth:720}}>
      <button className="wback" onClick={()=>go('mymeet')}><Ic n="IconNormalChevronLeftNameChevronLeftTightFalseThick" s={14}/>내 모임으로</button>
      <h1 style={{fontSize:24,fontWeight:800,margin:'6px 0 6px'}}>오늘 즐거우셨나요?</h1>
      <div className="meta" style={{marginBottom:20}}>{m.title}</div>
      <div className="card" style={{marginBottom:20}}>
        <b style={{fontSize:14.5}}>오늘 만난 사람과 다시 연결하기</b>
        <p className="meta" style={{margin:'6px 0 0'}}>준비 중이에요. 채팅방에서 나눈 대화는 계속 확인할 수 있어요.</p>
      </div>
      <div className="wtwo">
        <div className="card">
          <b style={{fontSize:15}}>비공개 설문 <span className="meta" style={{fontWeight:400}}>(30초)</span></b>
          {!surveyDone?<div style={{marginTop:12, display:'flex', flexDirection:'column', gap:14}}>
            <div>
              <div className="meta" style={{marginBottom:6}}>1. 실제로 참석하셨나요?</div>
              <div style={{display:'flex', gap:6}}><Chip label="네, 참석했어요" onClick={()=>setAttended(true)} on={attended===true}/><Chip label="아니요" onClick={()=>setAttended(false)} on={attended===false}/></div>
            </div>
            <div>
              <div className="meta" style={{marginBottom:6}}>2. 모임은 전반적으로 어땠나요?</div>
              <div style={{display:'flex', gap:6}}>{[1,2,3,4,5].map(n=><button key={n} onClick={()=>setRating(n)} style={{width:34, height:34, borderRadius:'50%', border:'1px solid '+(rating===n?'var(--c-primary)':'var(--c-line)'), background:rating===n?'var(--c-primary)':'none', color:rating===n?'#fff':'var(--c-text)', fontFamily:'inherit', fontSize:13, fontWeight:700, cursor:'pointer'}}>{n}</button>)}</div>
            </div>
            <div>
              <div className="meta" style={{marginBottom:6}}>3. 불편하거나 안전하지 않다고 느낀 일이 있었나요?</div>
              <textarea value={unsafe} onChange={e=>setUnsafe(e.target.value)} rows={2} placeholder="있었다면 적어주세요" style={{width:'100%', border:'1px solid var(--c-line)', borderRadius:8, padding:'8px 10px', fontFamily:'inherit', fontSize:13.5, outline:'none', resize:'none'}}/>
            </div>
            <div>
              <div className="meta" style={{marginBottom:6}}>4. 운영진에게 전달할 내용이 있나요?</div>
              <textarea value={note} onChange={e=>setNote(e.target.value)} rows={2} placeholder="자유롭게 남겨주세요" style={{width:'100%', border:'1px solid var(--c-line)', borderRadius:8, padding:'8px 10px', fontFamily:'inherit', fontSize:13.5, outline:'none', resize:'none'}}/>
            </div>
            <div style={{display:'flex',gap:10}}>
              <DSBtn label="제출하기" size="md" style={{flex:1}} onClick={unsafe.trim()?reportFlow:submitSurvey}/>
              <DSBtn label="건너뛰기" variant="outlined" color="assistive" size="md" style={{flex:1}} onClick={()=>set({survey:'skip'})}/>
            </div>
          </div>:<div className="notice blue" style={{marginTop:12}}><Ic n="IconNormalCircleCheck2NameCircleCheckFillFillTrue" s={15}/><span>{S.survey==='skip'?'건너뛰었어요.':'설문이 제출됐어요.'}</span></div>}
        </div>
        <div className="card" style={{background:'var(--c-violet-bg)',border:'none'}}>
          <div className="row" style={{gap:6,marginBottom:8}}><Ic n="IconNormalSparkleNameSparkle" s={16} c="var(--c-violet)"/><b style={{fontSize:13,color:'var(--c-violet)'}}>다음 모임 추천</b></div>
          <p style={{fontSize:13.5,color:'var(--c-sub)',margin:'0 0 14px',lineHeight:1.6}}>맞춤 추천 기능은 준비 중이에요. 지금은 홈에서 새 모임을 둘러볼 수 있어요.</p>
          <DSBtn label="홈에서 모임 둘러보기" size="md" onClick={()=>go('home')}/>
        </div>
      </div>
    </div>
  </div>;
}
function WScrMyMeet({S,set,go}){
  const upcoming = Object.keys(S.joined).filter(id=>!S.completed.includes(id));
  const past = S.completed;
  const eventStatuses = Object.values(S.eventApplied||{}).filter(Boolean);
  const pendingCount = upcoming.filter(id=>S.joined[id]==='pending').length + eventStatuses.filter(s=>s==='pending').length;
  const confirmedCount = upcoming.length + eventStatuses.length - pendingCount;
  return <div className="wapp"><WNav S={S} go={go} set={set}/>
    <div className="wmain" style={{maxWidth:720}}>
      <div className="wsect"><h2>내 모임</h2></div>
      <div style={{display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:12, marginBottom:12}}>
        <button className="card" style={{textAlign:'left', cursor:'pointer', fontFamily:'inherit', padding:'16px 14px'}} onClick={()=>go('mymeetupcoming')}>
          <b style={{fontSize:20, display:'block'}}>{pendingCount}</b><span className="meta">승인 대기</span>
        </button>
        <button className="card" style={{textAlign:'left', cursor:'pointer', fontFamily:'inherit', padding:'16px 14px'}} onClick={()=>go('mymeetupcoming')}>
          <b style={{fontSize:20, display:'block'}}>{confirmedCount}</b><span className="meta">참가 예정</span>
        </button>
        <button className="card" style={{textAlign:'left', cursor:'pointer', fontFamily:'inherit', padding:'16px 14px'}} onClick={()=>go('mymeetpast')}>
          <b style={{fontSize:20, display:'block'}}>{past.length}</b><span className="meta">지난 모임</span>
        </button>
        <button className="card" style={{textAlign:'left', cursor:'pointer', fontFamily:'inherit', padding:'16px 14px'}} onClick={()=>go('mymeethosted')}>
          <b style={{fontSize:20, display:'block'}}>{S.myHostedMeetups.length}</b><span className="meta">내가 개설</span>
        </button>
      </div>
      <button className="card" style={{width:'100%', display:'flex', alignItems:'center', gap:12, cursor:'pointer', fontFamily:'inherit', textAlign:'left', marginTop:16}} onClick={()=>go('savedmeetups')}>
        <Ic n="IconNormalHeartNameHeartFillFillTrue" s={16} c="var(--c-coral)"/><span style={{flex:1, fontSize:14}}>찜한 모임 보기</span>
        <Ic n="IconNormalChevronRightNameChevronRightTightFalseThick" s={14} c="var(--c-faint)"/>
      </button>
      <button className="card" style={{width:'100%', display:'flex', alignItems:'center', gap:12, cursor:'pointer', fontFamily:'inherit', textAlign:'left', marginTop:10}} onClick={()=>startCreateMeetup(S,set,go)}>
        <Ic n="IconNormalCircleInfo2NameCircleInfoFillFalse" s={16} c="var(--c-primary)"/><span style={{flex:1, fontSize:14}}>모임 개설도 여기서 할 수 있어요. 등록하면 바로 공개돼요.</span>
        <Ic n="IconNormalChevronRightNameChevronRightTightFalseThick" s={14} c="var(--c-faint)"/>
      </button>
    </div>
    <Footer go={go} set={set}/>
  </div>;
}
function WScrMyMeetUpcoming({S,set,go}){
  const events = useEvents();
  const upcoming = Object.keys(S.joined).filter(id=>!S.completed.includes(id));
  const pendingIds = upcoming.filter(id=>S.joined[id]==='pending');
  const confirmedIds = upcoming.filter(id=>S.joined[id]!=='pending');
  const eventIds = Object.keys(S.eventApplied||{}).filter(id=>S.eventApplied[id]);
  const pendingEventIds = eventIds.filter(id=>S.eventApplied[id]==='pending');
  const confirmedEventIds = eventIds.filter(id=>S.eventApplied[id]!=='pending');
  const placeLabel = m => {
    const hidden = m.placeVisibility==='confirmed';
    return hidden ? <>{m.region} 인근<div className="meta" style={{marginTop:2}}>정확한 장소는 참가 확정 후 공개돼요</div></> : <>{m.when} · {m.place}</>;
  };
  const renderMeetupCard = (id) => {
    const m=resolveMeetup(id,S); if(!m) return null; const status=S.joined[id];
    return <div key={id} className="card" style={{margin:'12px 0 20px'}}>
      <div className="row" style={{gap:14,marginBottom:12,cursor:'pointer'}} role="button" tabIndex={0} onClick={()=>go('detail',{detailId:id})} onKeyDown={e=>{if(e.key==='Enter')go('detail',{detailId:id});}}><MiniThumb m={m} size={56} radius={12}/><span style={{flex:1,fontSize:15}}><b>{m.title}</b><br/><span className="meta">{status==='pending'?<>{m.when} · {m.region} 인근<div style={{marginTop:2}}>정확한 장소는 승인 후 공개돼요</div></>:placeLabel(m)}</span></span><Ic n="IconNormalChevronRightNameChevronRightTightFalseThick" s={14} c="var(--c-faint)"/></div>
      {status==='pending'&&<div className="notice orange" style={{marginBottom:12, flexDirection:'column', alignItems:'flex-start', gap:8}}>
        <div className="row" style={{gap:8}}><Ic n="IconNormalCircleInfo2NameCircleInfoFillFalse" s={15}/><span><b>모임장 승인 대기 중이에요</b></span></div>
        <span style={{fontSize:13, lineHeight:1.6}}>보통 하루 안에 답변을 받아요. 승인되면 알림으로 알려드려요.</span>
        <div className="row" style={{gap:8}}>
          <button style={{background:'none', border:'1px solid var(--c-line)', borderRadius:8, padding:'6px 14px', fontFamily:'inherit', fontSize:13, cursor:'pointer', color:'var(--c-red)'}} onClick={()=>cancelParticipation(id, S, set)}>신청 취소</button>
          <button style={{background:'none', border:'none', color:'var(--c-primary)', fontFamily:'inherit', fontSize:13, cursor:'pointer'}} onClick={()=>go('detail',{detailId:id})}>모임 상세 보기</button>
        </div>
      </div>}
      {status==='waitlisted'&&<div className="notice" style={{marginBottom:12, flexDirection:'column', alignItems:'flex-start', gap:8}}>
        <div className="row" style={{gap:8}}><Ic n="IconNormalCircleInfo2NameCircleInfoFillFalse" s={15}/><span><b>현재 대기 {myWaitlistPosition(id,S)||1}번째예요</b></span></div>
        <span style={{fontSize:13, lineHeight:1.6}}>자리가 생기면 알림을 보내드려요. 앞 순번부터 차례로 자리가 넘어가요.</span>
        <div className="row" style={{gap:8}}>
          <button style={{background:'none', border:'1px solid var(--c-line)', borderRadius:8, padding:'6px 14px', fontFamily:'inherit', fontSize:13, cursor:'pointer', color:'var(--c-red)'}} onClick={()=>cancelParticipation(id, S, set)}>대기 취소</button>
        </div>
      </div>}
      {status==='confirmed'&&!(S.reconfirmedMap&&S.reconfirmedMap[id])&&<div className="notice orange" style={{marginBottom:12}}><Ic n="IconNormalBell2NameBellFillFillTrue" s={15}/><span><b>참석 재확인이 필요해요</b> — 미응답 시 자리가 대기자에게 전달될 수 있어요.</span></div>}
      {status==='confirmed'&&S.reconfirmedMap&&S.reconfirmedMap[id]&&<div className="notice blue" style={{marginBottom:12}}><Ic n="IconNormalCircleCheck2NameCircleCheckFillFillTrue" s={15}/><span>참석 확정 완료 — 곧 봬요!</span></div>}
      {status==='confirmed'&&<div style={{display:'flex',gap:10}}>
        {!(S.reconfirmedMap&&S.reconfirmedMap[id])&&<DSBtn label="참석 재확인" size="md" style={{flex:1}} onClick={()=>set({reconfirmOpen:true, detailId:id})}/>}
        <DSBtn label="채팅방" variant="outlined" color="assistive" size="md" style={{flex:1}} onClick={()=>go('chat',{detailId:id})}/>
      </div>}
      {status==='confirmed'&&<div className="row" style={{gap:14, marginTop:10, flexWrap:'wrap'}}>
        <button style={{background:'none',border:'none',color:'var(--c-primary)',fontFamily:'inherit',fontSize:13,padding:0,cursor:'pointer'}} onClick={()=>go('attendees',{detailId:id})}>참가자 명단 보기</button>
        <button style={{background:'none',border:'none',color:'var(--c-faint)',fontFamily:'inherit',fontSize:13,padding:0,cursor:'pointer'}} onClick={()=>cancelParticipation(id, S, set)}>참가 취소</button>
      </div>}
      {status==='confirmed'&&S.reconfirmedMap&&S.reconfirmedMap[id]&&<button style={{background:'none',border:'none',color:'var(--c-primary)',fontFamily:'inherit',fontSize:13,padding:'12px 0 0',cursor:'pointer'}} onClick={()=>go('postmeetup',{detailId:id, completed:[...new Set([...S.completed, id])]})}>모임 다녀왔어요 → 후기 남기기</button>}
    </div>;
  };
  const renderEventCard = (id) => {
    const e=events.find(x=>x.id===id); if(!e) return null;
    const st=S.eventApplied[id];
    const cancel = () => cancelEventApplication(id, S.acctEmail).then(res=>{
      if (res&&res.error) { set({toast:'취소에 실패했어요 — 다시 시도해주세요'}); return; }
      const next={...S.eventApplied}; delete next[id]; set({eventApplied:next, toast:'신청을 취소했어요'});
    });
    return <div key={id} className="card" style={{margin:'12px 0 20px'}}>
      <div className="row" style={{gap:14,marginBottom:12}}><Ph h={56} w={56} r={12} label=""/><span style={{flex:1,fontSize:15}}><b>{e.title}</b><br/><span className="meta">모두모여 공식 · {e.when}</span></span><Chip label={st==='pending'?'승인 대기':st==='waitlisted'?'대기 신청':'참가 확정'} tone={st==='confirmed'?'green':'orange'}/></div>
      {st==='waitlisted'&&<span className="chip orange" style={{marginRight:8}}>대기 신청</span>}
      <button style={{background:'none', border:'1px solid var(--c-line)', borderRadius:8, padding:'8px 14px', fontFamily:'inherit', fontSize:13, cursor:'pointer', color:'var(--c-red)'}} onClick={cancel}>신청 취소</button>
    </div>;
  };
  const nothing = upcoming.length===0 && eventIds.length===0;
  return <div className="wapp"><WNav S={S} go={go} set={set}/>
    <div className="wmain" style={{maxWidth:720}}>
      <button className="wback" onClick={()=>go('mymeet')}><Ic n="IconNormalChevronLeftNameChevronLeftTightFalseThick" s={14}/>내 모임으로</button>
      {nothing?<div className="card" style={{textAlign:'center', padding:'32px 20px', margin:'12px 0 24px'}}>
        <Ic n="IconNormalCalendarNameCalendar" s={28} c="var(--c-faint)"/>
        <p style={{margin:'12px 0 16px', fontSize:14, color:'var(--c-sub)'}}>아직 참가 신청한 모임이 없어요.</p>
        <DSBtn label="모임 둘러보러 가기" variant="outlined" size="md" onClick={()=>go('browseregions')}/>
      </div>:<>
        {(pendingIds.length>0||pendingEventIds.length>0)&&<>
          <div className="wsect"><h2>승인 대기 중인 모임</h2></div>
          {pendingEventIds.map(renderEventCard)}
          {pendingIds.map(renderMeetupCard)}
        </>}
        {(confirmedIds.length>0||confirmedEventIds.length>0)&&<>
          <div className="wsect"><h2>참가 예정</h2></div>
          {confirmedEventIds.map(renderEventCard)}
          {confirmedIds.map(renderMeetupCard)}
        </>}
      </>}
    </div>
    <Footer go={go} set={set}/>
  </div>;
}
function WScrMyMeetPast({S,set,go}){
  const validIds = S.completed.filter(id=>resolveMeetup(id,S));
  const perPage = 7;
  const totalPages = Math.max(1, Math.ceil(validIds.length/perPage));
  const page = Math.min(S.mymeetPastPage||1, totalPages);
  const paged = validIds.slice((page-1)*perPage, page*perPage);
  return <div className="wapp"><WNav S={S} go={go} set={set}/>
    <div className="wmain" style={{maxWidth:720}}>
      <button className="wback" onClick={()=>go('mymeet')}><Ic n="IconNormalChevronLeftNameChevronLeftTightFalseThick" s={14}/>내 모임으로</button>
      <div className="wsect"><h2>참석 완료</h2></div>
      {validIds.length===0?<div className="card" style={{textAlign:'center', padding:'32px 20px'}}>
        <p style={{margin:0, fontSize:14, color:'var(--c-sub)'}}>아직 참석 완료한 모임이 없어요.</p>
      </div>:<>{paged.map(id=>{const m=resolveMeetup(id,S); return <div key={id} className="card" style={{margin:'12px 0 20px'}}>
        <div className="row" style={{gap:14,marginBottom:12}}><Ph h={56} w={56} r={12} label=""/><span style={{flex:1,fontSize:15}}><b>{m.title}</b><br/><span className="meta">참가 완료</span></span></div>
        <DSBtn label="오늘 만난 사람 보기" variant="outlined" size="md" onClick={()=>go('postmeetup',{detailId:id})}/>
      </div>;})}
      <Pagination page={page} total={totalPages} onChange={n=>set({mymeetPastPage:n})}/></>}
    </div>
    <Footer go={go} set={set}/>
  </div>;
}
function WScrMyMeetHosted({S,set,go}){
  const activeCount = S.myHostedMeetups.filter(hm=>!hm.done).length;
  return <div className="wapp"><WNav S={S} go={go} set={set}/>
    <div className="wmain" style={{maxWidth:720}}>
      <button className="wback" onClick={()=>go('mymeet')}><Ic n="IconNormalChevronLeftNameChevronLeftTightFalseThick" s={14}/>내 모임으로</button>
      <div className="wsect"><h2>내가 개설한 모임</h2></div>
      {activeCount>=2&&<div className="notice" style={{marginBottom:14}}><Ic n="IconNormalCircleInfo2NameCircleInfoFillFalse" s={15}/><span>지금 <b>{activeCount}개</b>의 모임을 동시에 주최하고 있어요. 아래에서 각각 관리할 수 있어요.</span></div>}
      {S.myHostedMeetups.length===0?<div className="card" style={{textAlign:'center', padding:'32px 20px', margin:'12px 0 24px'}}>
        <p style={{margin:'0 0 16px', fontSize:14, color:'var(--c-sub)'}}>아직 개설한 모임이 없어요.</p>
        <DSBtn label="모임 개설하기" size="md" onClick={()=>startCreateMeetup(S,set,go)}/>
      </div>:S.myHostedMeetups.map((m,i)=>{
        const applicants = m.applicants||[];
        const pendingCount = applicants.filter(a=>a.status==='pending').length;
        return <div key={i} className="card" style={{margin:'12px 0 20px'}}>
        <div className="row" style={{gap:14, cursor:m.serverId?'pointer':'default'}} role={m.serverId?'button':undefined} tabIndex={m.serverId?0:undefined} onClick={()=>{ if(m.serverId&&isServerMeetupId(m.serverId)) go('detail',{detailId:'r_'+m.serverId}); }} onKeyDown={e=>{ if(e.key==='Enter'&&m.serverId&&isServerMeetupId(m.serverId)) go('detail',{detailId:'r_'+m.serverId}); }}>
          <MiniThumb m={m} size={56} radius={12}/>
          <span style={{flex:1,fontSize:15}}><b>{m.title||'(제목 없음)'}</b><br/><span className="meta">{m.when||'일정 미정'} · {m.place||'장소 미정'}</span>{m.createdAt&&<div className="meta" style={{fontSize:11.5, marginTop:2}}>{timeAgo(m.createdAt)} 등록</div>}{(m.purposes&&m.purposes.length>0)&&<div style={{display:'flex',gap:6,flexWrap:'wrap',marginTop:6}}>{m.purposes.map(p=><Chip key={p} label={p} tone="primary"/>)}</div>}</span>
          {m.serverId&&<Ic n="IconNormalChevronRightNameChevronRightTightFalseThick" s={14} c="var(--c-faint)"/>}
          <div style={{display:'flex', flexDirection:'column', gap:6, alignItems:'flex-end'}}>
            <Chip label={m.done?'모임 완료':'모임개설예정'} tone={m.done?'green':'violet'}/>
          </div>
        </div>
        {!m.done&&<DSBtn label="채팅방 열기" variant="outlined" size="md" style={{marginTop:12}} onClick={()=>go('chat',{detailId:'my'+i})}/>}
        {m.done&&<p className="meta" style={{marginTop:12}}>모임이 종료되어 채팅방을 더 이상 열 수 없어요</p>}
        {!m.done&&m.approvalMode==='approval'&&<DSBtn label={pendingCount>0?`신청자 관리 (대기 ${pendingCount}명)`:'신청자 관리'} variant="outlined" size="md" style={{marginTop:8}} onClick={()=>set({applicantsIdx:i})}/>}
        {!m.done&&<DSBtn label="모임 정보 수정" variant="outlined" size="md" style={{marginTop:8}} onClick={()=>go('createmeetup',{editingMeetupIdx:i, newMeetup:{...m}})}/>}
        {m.done?<p className="meta" style={{margin:'12px 0 0'}}>참석자 {m.attendedCount}명 참석 +1회 반영 완료</p>:(()=>{
          // 완료 처리는 모임 시각이 지난 뒤에만(사용자 정책 2026-07-30 — 미리 완료 찍는 악용 방지)
          const endAt = m.date ? Date.parse(m.date+'T'+((m.time&&/^\d{2}:\d{2}/.test(m.time))?m.time:'23:59')) : null;
          const notYet = endAt!=null && !isNaN(endAt) && Date.now() < endAt;
          return <DSBtn label={notYet?'모임 완료하기 (모임 시간 후 가능)':'모임 완료하기'} variant="outlined" size="md" style={{marginTop:8, ...(notYet?{opacity:.55}:{})}} onClick={()=>{
            if (notYet) { set({toast:'모임 완료는 모임 날짜·시간이 지난 뒤에 할 수 있어요'}); return; }
            set({hostCompleteIdx:i});
          }}/>; })()}
        <button style={{width:'100%', background:'none', border:'none', color:'var(--c-red)', fontFamily:'inherit', fontSize:12.5, cursor:'pointer', padding:'10px 0 0'}} onClick={()=>{
          if (m.done) {
            // 완료된 모임 정리는 취소가 아니므로 사유 불필요(사용자 정책 2026-07-30) — 참가자 알림도 없음
            if (!window.confirm('완료된 모임 기록을 삭제할까요? 되돌릴 수 없어요.')) return;
            deleteRemoteMeetup(m.serverId, m.title, null, S.acctEmail, true).then(()=>{
              set({myHostedMeetups: S.myHostedMeetups.filter((_,ix)=>ix!==i), toast:'완료된 모임 기록을 삭제했어요'});
            });
            return;
          }
          const reason = window.prompt('참가자들에게 전달할 삭제 사유를 입력해주세요 (예: 개인 사정으로 취소합니다)');
          if (reason===null) return; // 취소 누름
          if (!window.confirm('이 모임을 삭제할까요? 되돌릴 수 없어요.')) return;
          deleteRemoteMeetup(m.serverId, m.title, reason, S.acctEmail).then(()=>{
            set({myHostedMeetups: S.myHostedMeetups.filter((_,ix)=>ix!==i), toast:'모임을 삭제했어요 — 참가자들에게 알림이 전달돼요'});
          });
        }}>모임 삭제</button>
      </div>;})}
      <button className="card" style={{width:'100%', display:'flex', alignItems:'center', gap:12, cursor:'pointer', fontFamily:'inherit', textAlign:'left', marginTop:16}} onClick={()=>startCreateMeetup(S,set,go)}>
        <Ic n="IconNormalCircleInfo2NameCircleInfoFillFalse" s={16} c="var(--c-primary)"/><span style={{flex:1, fontSize:14}}>새 모임 개설하기</span>
        <Ic n="IconNormalChevronRightNameChevronRightTightFalseThick" s={14} c="var(--c-faint)"/>
      </button>
    </div>
    <Footer go={go} set={set}/>
  </div>;
}
function WScrQnA({S,set,go}){
  const {useState, useEffect} = React;
  const tabs=['전체','해외거주 Q&A','친구만들기','귀국 적응'];
  const t=S.qtab||0;
  const isAdmin = isAdminUser(S);
  const [serverPosts, setServerPosts] = useState([]);
  const loadQna = () => fetchPosts('qna').then(list=>setServerPosts((list||[]).filter(x=>!isBlockedEmail(x.author_email))));
  React.useEffect(()=>{ const f=()=>loadQna(); document.addEventListener('mm:contentremoved', f); return ()=>document.removeEventListener('mm:contentremoved', f); }, []);
  useEffect(()=>{ loadQna(); }, []);
  const base=DEMO_MODE?[{cat:1,tag:'해외거주 Q&A',title:'지금 영국에서 워홀 중인데, 저 같은 사람 여기 있나요?',body:'런던 온 지 3개월 됐는데 여기서도 한인 유학생·워홀러 분들과 연결돼서 지내고 싶어요.',meta:'닉네임D · GB 런던',cta:['런던 펍모임에서 만나기','home','gb']},{cat:2,tag:'친구만들기',title:'서울 사는 미국 고등학교 출신 분 계신가요? 밥약 하실 분',body:'귀국한 지 1년 됐는데 아직도 유학 시절 얘기할 친구가 없어요.',meta:'닉네임E · 서울',cta:['강남 밤모임 보러가기','detail','m2']},{cat:1,tag:'해외거주 Q&A',title:'토론토 워홀 비자 팁 나눠요',body:'워홀 비자 준비하며 헷갈렸던 부분 정리해봤어요.',meta:'닉네임G · 토론토'},{cat:3,tag:'귀국 적응',title:'귀국하고 다시 적응하는 데 얼마나 걸리셨나요',body:'생각보다 역문화충격이 크네요. 다들 어떻게 극복하셨어요?',meta:'닉네임I · 서울'},{cat:2,tag:'친구만들기',title:'런던 거주 20대 모임 하실 분',body:'퇴근 후 가볍게 만날 사람 찾아요.',meta:'닉네임J · 런던'},{cat:1,tag:'해외거주 Q&A',title:'시드니 하우스 셰어 구하는 팁',body:'현지 부동산 사이트 어디가 믿을만한가요?',meta:'닉네임K · 시드니'}]:[];
  const tagOf = i => ['전체','해외거주 Q&A','친구만들기','귀국 적응'][i];
  const catOf = topic => tagOf(1)===topic?1:tagOf(2)===topic?2:tagOf(3)===topic?3:0;
  const fromServer = serverPosts.map(p=>{ const sp=qnaSplit(p.body); return {...p, ...sp, cat:catOf(p.topic), tag:p.topic, postSource:'qna'}; });
  const page = S.qnaPage||1;
  const perPage = 4;
  const posts=[...fromServer, ...base].filter(p=>t===0||p.cat===t).filter(p=>!S.qnaSearch||(p.title+p.body).toLowerCase().includes(S.qnaSearch.toLowerCase()));
  const totalPages = Math.max(1, Math.ceil(posts.length/perPage));
  const pagePosts = posts.slice((page-1)*perPage, page*perPage);
  const submitQna = () => {
    if (!S.draftTitle.trim()) return;
    createPost({category:'qna', topic:tagOf(S.draftTag), author_email:S.acctEmail, author_name:S.profile.nickname||'나', body:qnaCompose(S.draftTitle, S.draftBody), is_admin_post:isAdmin}).then(res=>{
      if (res&&res.error) { set({toast:'글 등록에 실패했어요'}); return; }
      set({composeOpen:false, draftTitle:'', draftBody:'', qnaPage:1, toast:'글이 올라갔어요'});
      loadQna();
    });
  };
  return <div className="wapp" style={{fontWeight:600}}><WNav S={S} go={go} set={set}/>
    <div className="wmain" style={{maxWidth:760}}>
      <div className="wsect" id="qna-list"><h2>Q&amp;A</h2><button className="more" onClick={()=>S.composeOpen?set({composeOpen:false}):guardAuth(S,set,go,()=>set({composeOpen:true, draftTag:1, draftTitle:'', draftBody:''}))}>글쓰기</button></div>
      {base.length>0&&<button className="card" style={{width:'100%', display:'flex', alignItems:'center', gap:14, padding:'16px 18px', marginBottom:20, cursor:'pointer', fontFamily:'inherit', textAlign:'left', background:'var(--c-primary-bg)', border:'none'}} onClick={()=>set({qnaDetailPost:base[0], screen:'postdetail'})}>
        <Ic n="IconNormalStarNameStarFillFillTrue" s={22} c="var(--c-primary)"/>
        <span style={{flex:1}}><b style={{fontSize:12, color:'var(--c-primary)'}}>🔥 이 주의 베스트 질문</b><br/><span style={{fontSize:14.5, fontWeight:700}}>{base[0].title}</span></span>
        <Ic n="IconNormalChevronRightNameChevronRightTightFalseThick" s={16} c="var(--c-primary)"/>
      </button>}
      <div className="row" style={{gap:8, background:'var(--c-fill)', borderRadius:10, padding:'10px 14px', marginBottom:16, maxWidth:420}}>
        <Ic n="IconNormalSearchNameSearchThickFalse" s={17} c="var(--c-faint)"/>
        <input value={S.qnaSearch} onChange={e=>set({qnaSearch:e.target.value})} placeholder="검색어를 입력하세요" style={{flex:1, border:'none', background:'none', fontFamily:'inherit', fontSize:14, outline:'none'}}/>
        {S.qnaSearch&&<button className="iconbtn" aria-label="검색어 지우기" style={{padding:2}} onClick={()=>set({qnaSearch:''})}><Ic n="IconNormalCloseNameCloseThickFalse" s={15} c="var(--c-faint)"/></button>}
      </div>
      <div className="wtabs">{tabs.map((tb,i)=><button key={tb} className={'qtab'+(t===i?' on':'')} onClick={()=>set({qtab:i, qnaPage:1})}>{tb}</button>)}</div>
      {S.composeOpen&&<div className="card" style={{marginBottom:16}}>
        <b style={{fontSize:15}}>새 글 쓰기</b>
        <div style={{display:'flex',gap:8,margin:'12px 0'}}>{[1,2,3].map(i=><Chip key={i} label={tagOf(i)} onClick={()=>set({draftTag:i})} on={S.draftTag===i}/>)}</div>
        <input value={S.draftTitle} onChange={e=>set({draftTitle:e.target.value})} placeholder="제목" style={{width:'100%',border:'1px solid var(--c-line)',borderRadius:8,padding:'10px 14px',fontFamily:'inherit',fontSize:14,marginBottom:10,outline:'none'}}/>
        <textarea value={S.draftBody} onChange={e=>set({draftBody:e.target.value})} placeholder="내용을 적어주세요" rows={3} style={{width:'100%',border:'1px solid var(--c-line)',borderRadius:8,padding:'10px 14px',fontFamily:'inherit',fontSize:14,marginBottom:12,outline:'none',resize:'none'}}/>
        <DSBtn label="올리기" size="md" disable={!S.draftTitle.trim()} onClick={submitQna}/>
      </div>}
      {pagePosts.length===0&&<div className="card" style={{textAlign:'center', padding:'40px 20px'}}>
        <p style={{fontSize:14.5, fontWeight:700, margin:'0 0 6px'}}>{S.qnaSearch?'검색 결과가 없어요.':'아직 등록된 질문이 없어요.'}</p>
        <p className="meta" style={{margin:'0 0 16px'}}>{S.qnaSearch?'다른 검색어로 다시 찾아보세요.':'유학 생활, 귀국 후 생활, 모임에 관해 궁금한 점을 처음으로 남겨보세요.'}</p>
        <DSBtn label={S.qnaSearch?'검색어 지우기':'첫 질문 남기기'} size="md" onClick={()=>S.qnaSearch?set({qnaSearch:''}):guardAuth(S,set,go,()=>set({composeOpen:true, draftTag:1, draftTitle:'', draftBody:''}))}/>
      </div>}
      {pagePosts.map((p,i)=><Reveal key={p.id||p.title+i} delay={Math.min(i,6)*70}><div className="card" style={{marginBottom:12, cursor:'pointer'}} onClick={()=>set({qnaDetailPost:p, screen:'postdetail'})}>
        <div className="row" style={{justifyContent:'space-between'}}>
          <Chip label={p.tag} tone="primary"/>
          {p.postSource==='qna'&&(isAdmin||p.author_email===S.acctEmail)&&<button style={{background:'none',border:'none',color:'var(--c-red)',fontSize:12,cursor:'pointer'}} onClick={e=>{e.stopPropagation(); if(isAdmin&&p.author_email&&p.author_email!==S.acctEmail){ set({adminRemove:{kind:'post', id:p.id, ownerEmail:p.author_email, title:(p.title||p.body||'게시글').slice(0,30)}}); return;} deletePost(p.id).then(loadQna);}}>삭제</button>}
        </div>
        <div style={{fontSize:16,fontWeight:700,margin:'10px 0 6px'}}>{p.title}</div>
        <p style={{fontSize:14,color:'var(--c-sub)',margin:'0 0 10px',lineHeight:1.6}}>{p.body}</p>
        <div className="row" style={{justifyContent:'space-between'}}>
          <span className="meta">{p.meta || (p.postSource==='qna' ? `${p.author_name||'익명'} · ${timeAgo(new Date(p.created_at).getTime())}` : '')}</span>
          {p.cta&&<button style={{background:'none',border:'none',color:'var(--c-primary)',fontFamily:'inherit',fontSize:13.5,fontWeight:700,cursor:'pointer'}} onClick={e=>{e.stopPropagation(); p.cta[1]==='home'?go('home',{region:p.cta[2]}):go('detail',{detailId:p.cta[2]});}}>{p.cta[0]}</button>}
        </div>
      </div></Reveal>)}
      <Pagination page={page} total={totalPages} onChange={n=>set({qnaPage:n})}/>
      <p className="meta" style={{margin:'8px 2px',lineHeight:1.6}}>※ 게시판은 보조 기능이에요 — 글이 실제 만남(모임)으로 이어지도록 모든 글에 모임 연결 버튼이 붙어요.</p>
    </div>
    <Footer go={go} set={set}/>
  </div>;
}
function WScrPostDetail({S,set,go}){
  const {useState, useEffect} = React;
  const p=(S.qnaDetailPost&&typeof S.qnaDetailPost==='object')?S.qnaDetailPost:null; // 과거 잘못된 주소로 문자열이 들어와도 안전
  const isServerPost = !!(p && p.postSource);
  const [serverComments, setServerComments] = useState([]);
  const [commentDraft, setCommentDraft] = useState('');
  const isAdmin = isAdminUser(S);
  const loadComments = () => { if (isServerPost) fetchComments(p.id).then(setServerComments); };
  React.useEffect(()=>{ const f=()=>loadComments(); document.addEventListener('mm:contentremoved', f); return ()=>document.removeEventListener('mm:contentremoved', f); }, []);
  // 삭제된 게시글 접근 감지(낡은 목록에서 클릭) → 팝업 후 목록으로
  const [goneOpen, setGoneOpen] = React.useState(false);
  // 주소로 직접 진입(새로고침·링크 공유) 시 id로 게시물 복원
  React.useEffect(()=>{ if (p || !S.qnaDetailPostId) return; let al=true; fetchPostById(S.qnaDetailPostId).then(row=>{ if(!al) return; if (row) set({qnaDetailPost:{...row, name:row.author_name, postSource:row.category}}); else setGoneOpen(true); }); return ()=>{al=false;}; }, [S.qnaDetailPostId, !!p]);
  React.useEffect(()=>{ if (!isServerPost || !p || !p.id) return; let al=true; recordExists('posts', p.id).then(ok=>{ if (al && !ok) setGoneOpen(true); }); return ()=>{al=false;}; }, [p&&p.id]);
  useEffect(()=>{ loadComments(); }, [p&&p.id]);
  if(!p) if (goneOpen) return <div className="wapp"><WNav S={S} go={go} set={set}/><div className="overlay"><div className="sheet" role="dialog" aria-modal="true" style={{textAlign:'center'}}>
    <h2 style={{fontSize:17, margin:'0 0 8px'}}>존재하지 않는 게시물입니다</h2>
    <p className="meta" style={{margin:'0 0 16px', lineHeight:1.6}}>작성자가 삭제했거나 운영정책 위반으로 삭제된 게시물이에요.</p>
    <DSBtn label="목록으로 돌아가기" size="md" onClick={()=>go('qna')}/>
  </div></div></div>;
  if(!p) return <div className="wapp"><WNav S={S} go={go} set={set}/><div className="wmain" style={{maxWidth:640}}><p className="meta" style={{padding:'48px 0', textAlign:'center'}}>게시물을 불러오는 중…</p></div></div>;
  const postKey = p.title || p.id || p.body;
  const localComments=S.postComments[postKey]||[];
  const submitLocal=()=>{ const v=(S.commentDraft||'').trim(); if(!v) return; guardAuth(S,set,go,()=>set({postComments:{...S.postComments,[postKey]:[...localComments,{who:'나',text:v}]}, commentDraft:''})); };
  const submitServer=()=>{ const v=commentDraft.trim(); if(!v) return; guardAuth(S,set,go,()=>{ createComment({post_id:p.id, author_email:S.acctEmail, author_name:S.profile.nickname||'나', body:v, is_admin_reply:isAdmin}).then(res=>{ if(res&&res.error){ set({toast: String(res.error.message||'').includes('MM_SUSPENDED')?'이용이 제한된 계정이에요 — 제한 기간에는 댓글을 쓸 수 없어요':'댓글 등록 실패'}); return; } setCommentDraft(''); loadComments(); }); }); };
  const removeComment = id => { if(!isAdmin) return; deleteComment(id).then(()=>loadComments()); };
  const saved=(S.savedPosts||[]).includes(postKey);
  const toggleSave=()=>{ const arr=S.savedPosts||[]; set({savedPosts: saved?arr.filter(x=>x!==postKey):[...arr,postKey], toast: saved?'저장을 취소했어요':'저장했어요'}); };
  return <div className="wapp"><WNav S={S} go={go} set={set}/>
    <div className="wmain" style={{maxWidth:640}}>
      <div className="row" style={{justifyContent:'space-between'}}>
        <button className="wback" onClick={()=>go('explore')}><Ic n="IconNormalChevronLeftNameChevronLeftTightFalseThick" s={14}/>커뮤니티로</button>
        <div className="row" style={{gap:4}}>
          <button className="iconbtn" aria-label={saved?'저장 취소':'저장하기'} onClick={toggleSave}><Ic n={saved?'IconNormalBookmarkNameBookmarkFillFillTrue':'IconNormalBookmarkNameBookmarkFillFalse'} s={19} c={saved?'var(--c-primary)':undefined}/></button>
          {!(p.author_email && S.acctEmail && p.author_email===S.acctEmail)&&<button className="iconbtn" aria-label="신고하기" onClick={()=>guardAuth(S,set,go,()=>set({reportOpen:true, reportPrefill:(p.name||p.author_name||'').trim()||'(작성자 이름 없음)', reportTargetEmail:p.author_email||null}))}><Ic n="IconNormalFlagNameFlagFillFalse" s={19} c="var(--c-faint)"/></button>}
        </div>
      </div>
      {(p.name||p.author_name)&&<div className="row" style={{gap:8, margin:'12px 0 0'}}><Av name={p.name||p.author_name} size={30}/><span style={{fontSize:13.5, fontWeight:700}}>{p.name||p.author_name}{p.is_admin_post&&<span className="chip green" style={{fontSize:10,padding:'1px 6px',marginLeft:5}}>운영진</span>}</span><span className="meta">· {p.city?p.city+' · ':''}{p.time||(p.created_at?timeAgo(new Date(p.created_at).getTime()):'')}</span>{p.topic&&<span className="chip" style={{fontSize:11.5}}>{p.topic}</span>}</div>}
      <div style={{fontSize:22,fontWeight:800,margin:'8px 0 8px'}}>{p.title || (p.topic?`[${p.topic}] ${(p.name||p.author_name)}님의 글`:`${(p.name||p.author_name)||''}님의 글`)}</div>
      {p.meta&&<div className="meta" style={{marginBottom:14}}>{p.meta}</div>}
      <p style={{fontSize:14.5,color:'var(--c-sub)',margin:'0 0 18px',lineHeight:1.7}}>{p.body}</p>
      {p.tags&&<div className="row" style={{gap:6, flexWrap:'wrap', marginBottom:14}}>{p.tags.map(t=><span key={t} className="chip" style={{fontSize:11.5}}>#{t}</span>)}</div>}
      {p.cta&&<button style={{background:'none',border:'1px solid var(--c-primary)',color:'var(--c-primary)',borderRadius:20,padding:'9px 16px',fontFamily:'inherit',fontSize:13.5,fontWeight:700,cursor:'pointer',marginBottom:22}} onClick={()=>p.cta[1]==='home'?go('home',{region:p.cta[2]}):go('detail',{detailId:p.cta[2]})}>{p.cta[0]}</button>}
      {(p.relatedId||p.related_meetup_id)&&<button style={{background:'none',border:'1px solid var(--c-primary)',color:'var(--c-primary)',borderRadius:20,padding:'9px 16px',fontFamily:'inherit',fontSize:13.5,fontWeight:700,cursor:'pointer',marginBottom:22}} onClick={()=>{ const rid=p.relatedId||p.related_meetup_id; MEETUPS[rid]?go('detail',{detailId:rid}):set({toast:'연결된 모임을 찾을 수 없어요'}); }}>관련 모임 보기</button>}
      {isServerPost?<>
        <div style={{fontSize:14.5,fontWeight:700,marginBottom:12}}>댓글 {serverComments.length}</div>
        {serverComments.map(c=><div key={c.id} className="row" style={{marginBottom:12,fontSize:14, justifyContent:'space-between', alignItems:'flex-start'}}>
          <div><b style={c.is_admin_reply?{fontWeight:900, color:'var(--c-primary)'}:undefined}>{c.author_name}{c.is_admin_reply&&<span className="chip green" style={{fontSize:9,padding:'1px 5px',marginLeft:4}}>운영진 답변</span>}</b> <span style={{color:'var(--c-sub)'}}>{c.body}</span></div>
          {(isAdmin||c.author_email===S.acctEmail)&&<button style={{background:'none',border:'none',color:'var(--c-red)',fontSize:11,cursor:'pointer'}} onClick={()=>{ if(isAdmin&&c.author_email&&c.author_email!==S.acctEmail){ set({adminRemove:{kind:'comment', id:c.id, ownerEmail:c.author_email, title:(c.body||'댓글').slice(0,30)}}); return;} removeComment(c.id); }}>삭제</button>}
        </div>)}
        {!serverComments.length&&<p className="meta" style={{marginBottom:12}}>아직 댓글이 없어요 — 첫 댓글을 남겨보세요.</p>}
        <div style={{display:'flex',gap:8,marginTop:16}}>
          <input value={commentDraft} onChange={e=>setCommentDraft(e.target.value)} onKeyDown={e=>{if(e.key==='Enter') submitServer();}} placeholder={isAdmin?'운영진 답변을 남겨보세요':'댓글을 남겨보세요'} style={{flex:1,border:'1px solid var(--c-line)',borderRadius:20,padding:'10px 16px',fontFamily:'inherit',fontSize:14,outline:'none'}}/>
          <button style={{background:'var(--c-primary)',color:'#fff',border:'none',borderRadius:20,padding:'0 18px',fontFamily:'inherit',fontSize:14,fontWeight:700,cursor:'pointer'}} onClick={submitServer}>등록</button>
        </div>
      </>:<>
        <div style={{fontSize:14.5,fontWeight:700,marginBottom:12}}>댓글 {localComments.length}</div>
        {localComments.map((c,i)=><div key={i} style={{marginBottom:12,fontSize:14}}><b>{c.who}</b> <span style={{color:'var(--c-sub)'}}>{c.text}</span></div>)}
        {!localComments.length&&<p className="meta" style={{marginBottom:12}}>아직 댓글이 없어요 — 첫 댓글을 남겨보세요.</p>}
        <div style={{display:'flex',gap:8,marginTop:16}}>
          <input value={S.commentDraft||''} onChange={e=>set({commentDraft:e.target.value})} onKeyDown={e=>{if(e.key==='Enter') submitLocal();}} placeholder="댓글을 남겨보세요" style={{flex:1,border:'1px solid var(--c-line)',borderRadius:20,padding:'10px 16px',fontFamily:'inherit',fontSize:14,outline:'none'}}/>
          <button style={{background:'var(--c-primary)',color:'#fff',border:'none',borderRadius:20,padding:'0 18px',fontFamily:'inherit',fontSize:14,fontWeight:700,cursor:'pointer'}} onClick={submitLocal}>등록</button>
        </div>
      </>}
    </div>
    <Footer go={go} set={set}/>
  </div>;
}
function ApplicantsModal({S,set}){
  const idx = S.applicantsIdx;
  const m = S.myHostedMeetups[idx];
  const sid = m && m.serverId;
  const isServer = !!(sid && isServerMeetupId(sid));
  // 서버 모임이면 열릴 때 신청자 명단을 서버에서 읽어 로컬(applicants)에 반영 — 13단계 서버 연동.
  // RLS상 주최자 본인이라 조회 가능. 취소한 신청자는 목록에서 제외.
  React.useEffect(()=>{
    if (idx==null || !isServer) return;
    fetchMeetupParticipants(sid).then(rows=>{
      if (!rows) return;
      const applicants = rows.filter(r=>r.status!=='canceled').map(r=>({name:r.user_name||r.user_email, email:r.user_email, status:r.status, needsAck:false}));
      set({myHostedMeetups: S.myHostedMeetups.map((hm,hi)=>hi===idx?{...hm, applicants}:hm)});
    });
  }, [idx, sid]);
  if (!m) return null;
  const capMax = parseInt(m.cap)||6;
  const applicants = m.applicants||[];
  const confirmedCount = applicants.filter(a=>a.status==='confirmed').length;
  const full = confirmedCount>=capMax;
  const needsAckCount = applicants.filter(a=>a.status==='confirmed'&&a.needsAck).length;
  const patchApplicant = (i, patch) => {
    const nextApplicants = applicants.map((a,ai)=>ai===i?{...a, ...patch}:a);
    const nextHosted = S.myHostedMeetups.map((hm,hi)=>hi===idx?{...hm, applicants:nextApplicants}:hm);
    set({myHostedMeetups:nextHosted});
  };
  // 상태 변경 공통 처리 — 서버 모임이면 서버 기록이 성공한 뒤에만 화면을 바꾼다(13단계)
  const changeStatus = (i, status, toast) => {
    const a = applicants[i];
    const done = () => { patchApplicant(i, {status}); set({toast}); };
    if (isServer && a.email) {
      updateParticipantStatus(sid, a.email, status).then(res=>{
        if (res && res.error) { set({toast:'처리에 실패했어요 — 잠시 후 다시 시도해주세요'}); return; }
        done();
      });
    } else done();
  };
  const approve = i => {
    const a = applicants[i];
    // 정원이 이미 찼으면 승인 불가 — 동시 신청자 충돌 방지의 핵심 조건
    if (!a || a.status!=='pending' || full) return;
    changeStatus(i, 'confirmed', `${a.name}님을 승인했어요`);
  };
  const reject = i => {
    const a = applicants[i];
    if (!a) return;
    changeStatus(i, 'rejected', `${a.name}님의 신청을 거절했어요`);
  };
  const moveToWaitlist = i => {
    const a = applicants[i];
    if (!a) return;
    changeStatus(i, 'waitlisted', `${a.name}님을 대기 명단으로 옮겼어요`);
  };
  const ack = i => {
    const a = applicants[i];
    if (!a) return;
    patchApplicant(i, {needsAck:false});
    set({toast:`${a.name}님이 변경 사항을 확인했어요(데모)`});
  };
  const statusLabel = {pending:'승인 대기', confirmed:'참가 확정', rejected:'거절됨', waitlisted:'대기 명단', attended:'참석 완료', noshow:'노쇼'};
  return <div className="overlay" onClick={()=>set({applicantsIdx:null})}>
    <div className="sheet" onClick={e=>e.stopPropagation()} style={{maxWidth:480}}>
      <h2 style={{fontSize:18, margin:'0 0 4px'}}>신청자 관리</h2>
      <div className="meta" style={{marginBottom:14}}>{m.title||'(제목 없음)'} · 확정 {confirmedCount}/{capMax}명</div>
      {full&&<div className="notice orange" style={{marginBottom:14}}><Ic n="IconNormalCircleInfo2NameCircleInfoFillFalse" s={15}/><span><b>정원이 찼어요.</b> 나머지 신청자는 승인할 수 없어요 — 거절하거나 대기 명단으로 옮겨주세요.</span></div>}
      {needsAckCount>0&&<div className="notice" style={{marginBottom:14}}><Ic n="IconNormalCircleInfo2NameCircleInfoFillFalse" s={15}/><span><b>모임 정보가 수정됐어요.</b> 확정 참가자 {needsAckCount}명이 아직 변경 사항을 확인하지 않았어요.</span></div>}
      {applicants.length===0?<p className="meta" style={{textAlign:'center', padding:'24px 0'}}>아직 신청자가 없어요.</p>:
      <div style={{display:'flex', flexDirection:'column', gap:10, maxHeight:360, overflowY:'auto'}}>
        {applicants.map((a,i)=>{
          const blocked = a.status==='pending' && full;
          return <div key={a.name+i} className="card row" style={{gap:12}}>
            <Av name={a.name} size={36}/>
            <span style={{flex:1, fontSize:14}}><b>{a.name}</b><br/><span className="meta">{blocked?'승인 대기 · 정원 초과로 보류':statusLabel[a.status]||a.status}{a.status==='confirmed'&&a.needsAck&&<span style={{color:'var(--c-orange)', fontWeight:700}}> · 변경사항 확인 대기</span>}</span></span>
            {a.status==='pending'&&<>
              <button type="button" disabled={blocked} title={blocked?'정원이 차서 승인할 수 없어요':undefined} style={{background:blocked?'var(--c-fill)':'var(--c-primary)', color:blocked?'var(--c-faint)':'#fff', border:'none', borderRadius:8, padding:'7px 12px', fontFamily:'inherit', fontSize:12.5, fontWeight:600, cursor:blocked?'not-allowed':'pointer'}} onClick={()=>approve(i)}>승인</button>
              <button type="button" style={{background:'none', border:'1px solid var(--c-line)', borderRadius:8, padding:'7px 12px', fontFamily:'inherit', fontSize:12.5, cursor:'pointer'}} onClick={()=>reject(i)}>거절</button>
              {blocked&&m.waitlistOk!==false&&<button type="button" style={{background:'none', border:'1px solid var(--c-line)', borderRadius:8, padding:'7px 12px', fontFamily:'inherit', fontSize:12.5, cursor:'pointer', color:'var(--c-primary)'}} onClick={()=>moveToWaitlist(i)}>대기로</button>}
            </>}
            {a.status==='confirmed'&&a.needsAck&&<button type="button" style={{background:'none', border:'1px solid var(--c-line)', borderRadius:8, padding:'7px 12px', fontFamily:'inherit', fontSize:12.5, cursor:'pointer', color:'var(--c-primary)'}} onClick={()=>ack(i)}>확인 완료로 표시(데모)</button>}
          </div>;
        })}
      </div>}
      <button type="button" style={{background:'none', border:'none', color:'var(--c-faint)', fontFamily:'inherit', fontSize:13, padding:'14px 0 0', cursor:'pointer'}} onClick={()=>set({applicantsIdx:null})}>닫기</button>
    </div>
  </div>;
}
function HostLimitModal({S, set, go}){
  return <div className="overlay" onClick={()=>set({hostLimitNoticeOpen:false})}>
    <div className="sheet" onClick={e=>e.stopPropagation()} role="dialog" aria-modal="true" aria-labelledby="hostlimit-title">
      <h2 id="hostlimit-title" style={{fontSize:18, margin:'0 0 10px'}}>새 모임을 지금 열 수 없어요</h2>
      <p style={{fontSize:13.5, color:'var(--c-sub)', lineHeight:1.6, margin:'0 0 18px'}}>동시에 하나 이상의 모임 개설은 제한되어 있어요. 진행 중인 모임을 먼저 완료해야 새 모임을 열 수 있어요.</p>
      <div style={{display:'flex', flexDirection:'column', gap:8}}>
        <DSBtn label="확인했어요" onClick={()=>set({hostLimitNoticeOpen:false})}/>
      </div>
    </div>
  </div>;
}
Object.assign(window, {WScrPost, WScrMyMeet, WScrQnA, WScrPostDetail, WScrMyMeetHosted, ApplicantsModal, HostLimitModal});
