PageSourceSearch

https://flipspeak.ai/assets/TestRunner-CW2wkdjY.js

js flipspeak.ai collected 2026-10-05 15:00:57 UTC 38,432 bytes, 2 lines download raw bytes

1import{g as Pt,a as St,b as Tt,d as Rt,u as qt,r as a,j as e,W as he,k as $t,p as Lt}from"./index-C143ad9o.js";import{u as Dt,r as Ye,s as Et,c as Mt,a as Ft}from"./speakingTestApi-Dzk23wT8.js";import{u as Qt,V as Ge,M as Ut}from"./MicTroubleshoot-yw8ZfDet.js";import{u as Wt}from"./useAudioRecorder-CUreyU0o.js";const Ht="_container_zie6w_1",Vt="_themeToggle_zie6w_10",Ot="_progressBar_zie6w_35",Yt="_progressFill_zie6w_40",Gt="_content_zie6w_46",Zt="_loading_zie6w_54",Kt="_errorBox_zie6w_64",Xt="_step_zie6w_84",Jt="_stepIcon_zie6w_95",es="_mic_zie6w_111",ts="_camera_zie6w_119",ss="_loopback_zie6w_127",ns="_complete_zie6w_135",rs="_title_zie6w_143",is="_description_zie6w_150",as="_checkList_zie6w_157",os="_checkItem_zie6w_164",cs="_checkIcon_zie6w_176",ls="_primaryButton_zie6w_180",ds="_errorMsg_zie6w_198",us="_vuMeterContainer_zie6w_208",ms="_hint_zie6w_212",ps="_webcamContainer_zie6w_218",_s="_loopbackRecord_zie6w_224",As="_recordButton_zie6w_228",hs="_recordIcon_zie6w_249",gs="_recording_zie6w_253",ws="_countdownNumber_zie6w_265",xs="_pulse_zie6w_1",fs="_loopbackPlayback_zie6w_284",js="_playbackLabel_zie6w_290",bs="_audio_zie6w_296",Ns="_confirmLabel_zie6w_301",vs="_confirmButtons_zie6w_308",ys="_confirmYes_zie6w_313",ks="_confirmNo_zie6w_313",Bs="_troubleshootLink_zie6w_342",zs="_modalOverlay_zie6w_359",Cs="_fadeIn_zie6w_1",Is="_testIntro_zie6w_377",Ps="_introContent_zie6w_393",Ss="_testInfo_zie6w_407",Ts="_captureNotice_zie6w_416",Rs="_startBtn_zie6w_425",qs="_direction_zie6w_443",$s="_partLabel_zie6w_454",Ls="_directionImage_zie6w_478",Ds="_directionAudio_zie6w_489",Es="_timer_zie6w_495",Ms="_questionView_zie6w_503",Fs="_questionHeader_zie6w_513",Qs="_partBadge_zie6w_520",Us="_questionNum_zie6w_529",Ws="_timerBadge_zie6w_535",Hs="_audioPlayingBadge_zie6w_552",Vs="_recordingBadge_zie6w_563",Os="_questionContent_zie6w_573",Ys="_instruction_zie6w_580",Gs="_textContent_zie6w_586",Zs="_imageContent_zie6w_593",Ks="_audioPlayer_zie6w_599",Xs="_audioScript_zie6w_604",Js="_scriptLabel_zie6w_618",en="_audioProgressContainer_zie6w_624",tn="_audioProgressBar_zie6w_631",sn="_audioProgressFill_zie6w_639",nn="_audioProgressInfo_zie6w_646",rn="_audioProgressIcon_zie6w_655",an="_audioProgressText_zie6w_660",on="_audioProgressTime_zie6w_664",cn="_prepActions_zie6w_670",ln="_replayBtn_zie6w_682",dn="_disabled_zie6w_705",un="_replayIcon_zie6w_716",mn="_playButton_zie6w_721",pn="_recordingStatus_zie6w_757",_n="_audioPlayingStatus_zie6w_764",An="_audioIcon_zie6w_777",hn="_prepStatus_zie6w_782",gn="_prepIcon_zie6w_791",wn="_recordStatus_zie6w_795",xn="_recordHeader_zie6w_806",fn="_recordingVuMeter_zie6w_813",jn="_recordDot_zie6w_822",bn="_blink_zie6w_1",Nn="_skipBtn_zie6w_835",vn="_skipQuestionBtn_zie6w_851",yn="_stopBtn_zie6w_868",kn="_completeBox_zie6w_889",Bn="_completeIcon_zie6w_899",zn="_homeBtn_zie6w_915",Cn="_questionNav_zie6w_932",In="_navBtn_zie6w_942",Pn="_answered_zie6w_960",Sn="_current_zie6w_966",Tn="_existingAnswer_zie6w_978",Rn="_existingLabel_zie6w_986",qn="_revisitBadge_zie6w_994",$n="_reviewBox_zie6w_1004",Ln="_reviewSummary_zie6w_1021",Dn="_answerList_zie6w_1027",En="_answerItem_zie6w_1036",Mn="_answerHeader_zie6w_1043",Fn="_answeredBadge_zie6w_1050",Qn="_unansweredBadge_zie6w_1060",Un="_questionPreview_zie6w_1070",Wn="_answerAudio_zie6w_1077",Hn="_rerecordBtn_zie6w_1083",Vn="_submitBtn_zie6w_1100",On="_answersReview_zie6w_1118",Yn="_floatingWebcam_zie6w_1144",Gn="_webcam_bottom_right_zie6w_1156",Zn="_webcam_bottom_left_zie6w_1161",Kn="_webcam_top_right_zie6w_1166",Xn="_webcam_top_left_zie6w_1171",Jn="_positionSelector_zie6w_1189",er="_posBtn_zie6w_1207",tr="_active_zie6w_1226",sr="_cameraWarning_zie6w_1231",nr="_warningIcon_zie6w_1253",t={container:Ht,themeToggle:Vt,progressBar:Ot,progressFill:Yt,content:Gt,loading:Zt,errorBox:Kt,step:Xt,stepIcon:Jt,mic:es,camera:ts,loopback:ss,complete:ns,title:rs,description:is,checkList:as,checkItem:os,checkIcon:cs,primaryButton:ls,errorMsg:ds,vuMeterContainer:us,hint:ms,webcamContainer:ps,loopbackRecord:_s,recordButton:As,recordIcon:hs,recording:gs,countdownNumber:ws,pulse:xs,loopbackPlayback:fs,playbackLabel:js,audio:bs,confirmLabel:Ns,confirmButtons:vs,confirmYes:ys,confirmNo:ks,troubleshootLink:Bs,modalOverlay:zs,fadeIn:Cs,testIntro:Is,introContent:Ps,testInfo:Ss,captureNotice:Ts,startBtn:Rs,direction:qs,partLabel:$s,directionImage:Ls,directionAudio:Ds,timer:Es,questionView:Ms,questionHeader:Fs,partBadge:Qs,questionNum:Us,timerBadge:Ws,audioPlayingBadge:Hs,recordingBadge:Vs,questionContent:Os,instruction:Ys,textContent:Gs,imageContent:Zs,audioPlayer:Ks,audioScript:Xs,scriptLabel:Js,audioProgressContainer:en,audioProgressBar:tn,audioProgressFill:sn,audioProgressInfo:nn,audioProgressIcon:rn,audioProgressText:an,audioProgressTime:on,prepActions:cn,replayBtn:ln,disabled:dn,replayIcon:un,playButton:mn,recordingStatus:pn,audioPlayingStatus:_n,audioIcon:An,prepStatus:hn,prepIcon:gn,recordStatus:wn,recordHeader:xn,recordingVuMeter:fn,recordDot:jn,blink:bn,skipBtn:Nn,skipQuestionBtn:vn,stopBtn:yn,completeBox:kn,completeIcon:Bn,homeBtn:zn,questionNav:Cn,navBtn:In,answered:Pn,current:Sn,existingAnswer:Tn,existingLabel:Rn,revisitBadge:qn,reviewBox:$n,reviewSummary:Ln,answerList:Dn,answerItem:En,answerHeader:Mn,answeredBadge:Fn,unansweredBadge:Qn,questionPreview:Un,answerAudio:Wn,rerecordBtn:Hn,submitBtn:Vn,answersReview:On,floatingWebcam:Yn,webcam_bottom_right:Gn,webcam_bottom_left:Zn,webcam_top_right:Kn,webcam_top_left:Xn,positionSelector:Jn,posBtn:er,active:tr,cameraWarning:sr,warningIcon:nr},rr="data:audio/wav;base64,UklGRmQBAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YUABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";let ge=!1;function ir(){if(!ge)try{const y=new Audio(rr);y.volume=0;const f=y.play();f&&typeof f.then=="function"?f.then(()=>{y.pause(),ge=!0}
1).catch(()=>{}):ge=!0;const Q=window.AudioContext||window.webkitAudioContext;if(Q){const j=new Q;j.state==="suspended"&&j.resume().catch(()=>{});const X=j.createBuffer(1,1,22050),R=j.createBufferSource();R.buffer=X,R.connect(j.destination),R.start(0)}}catch{}}function dr(){const{scheduleId:y}=Pt(),f=St(),Q=Tt(),{theme:j,toggleTheme:X}=Rt(),{language:R}=qt(),n=R==="ko",J=Q.state?.mockSession,[l,m]=a.useState("loading"),[k,we]=a.useState(""),[c,xe]=a.useState(null),[d,ee]=a.useState(null),[q,w]=a.useState(0),[fe,C]=a.useState(null),[B,Ze]=a.useState({}),[te,je]=a.useState(!1),[b,be]=a.useState(null),Ne=(s,i)=>`${s??""}:${i}`,[I,ve]=a.useState(null),[P,ye]=a.useState(null),[Ke,U]=a.useState(!1),[Xe,ke]=a.useState(0),$=a.useRef(null),{requestMicrophonePermission:Je,requestCameraPermission:et,error:Be}=Qt(),{isRecording:tt,audioUrl:L,startRecording:st,stopRecording:nt,resetRecording:ze}=Wt(),W=a.useRef(null),se=a.useRef([]),S=a.useRef(null),Ce=a.useRef(0),p=a.useRef(null),H=a.useRef(null),[ne,re]=a.useState(0),[ie,Ie]=a.useState(0),[Pe,rt]=a.useState({}),[D,E]=a.useState(0),[ae,V]=a.useState(0),[oe,it]=a.useState(0),[ce,M]=a.useState(!1),T=a.useRef(null),[le,de]=a.useState(0),[ue,at]=a.useState(0),[ot,O]=a.useState(!1),Y=a.useRef(null),[Se,Te]=a.useState(null),[x,Re]=a.useState(null),[qe,$e]=a.useState(!1),[h,N]=a.useState("bottom-right"),G=a.useRef(null),me=a.useRef(0),v=a.useRef([]),_=a.useRef([]),z=a.useRef(null),ct=a.useRef(0),lt=a.useRef(0),g=a.useCallback(()=>{if(!c)return[];const s=[];let i=0;return c.parts&&c.parts.length>0?c.parts.forEach((r,o)=>{r.questions.forEach((u,K)=>{s.push({partIndex:o,questionIndex:K,globalIndex:i++,question:u,part:r})})}):c.questions&&c.questions.forEach((r,o)=>{s.push({partIndex:-1,questionIndex:o,globalIndex:i++,question:r})}),s},[c]),Le=a.useCallback(()=>{if(!c)return 300;let s=0;const i=g();return c.parts&&c.parts.forEach(r=>{s+=r.direction.duration||5}),i.forEach(r=>{const o=r.question.prep_time||r.part?.default_prep_time||30,u=r.question.answer_time||r.part?.default_answer_time||45;s+=o+u}),s},[c,g]),dt={quality:.7,maxWidth:640,maxHeight:480,maxFileSize:100*1024},F=a.useCallback(async s=>{if(!(!G.current||!k))try{const i=await G.current.captureAsync(dt);if(i){me.current+=1;const r=me.current,o=new Date().toISOString();Dt(k,i,r,s,{maxRetries:3,retryDelay:1e3}).then(u=>{u.success?console.log(`Face capture ${r} (${s}) uploaded successfully`):(console.warn(`Face capture ${r} failed after retries:`,u.error),_.current.push({sessionId:k,blob:i,captureIndex:r,captureType:s,capturedAt:o,failedAt:new Date().toISOString(),attempts:3,lastError:u.error||"업로드 실패"}))}).catch(u=>{console.warn("Face capture upload error:",u),_.current.push({sessionId:k,blob:i,captureIndex:r,captureType:s,capturedAt:o,failedAt:new Date().toISOString(),attempts:1,lastError:u instanceof Error?u.message:"알 수 없는 오류"})})}}catch(i){console.warn("Face capture failed:",i)}},[k]),De=a.useCallback(()=>{z.current=setInterval(async()=>{if(_.current.length===0)return;console.log(`Retrying ${_.current.length} failed captures...`);const s=await Ye(_.current);_.current=s.failed,s.succeeded>0&&console.log(`${s.succeeded} captures recovered, ${s.failed.length} still failed`)},3e4)},[]),Ee=a.useCallback(()=>{z.current&&(clearInterval(z.current),z.current=null)},[]),ut=a.useCallback(async()=>{if(_.current.length===0)return;console.log(`Final retry for ${_.current.length} failed captures...`);const s=await Ye(_.current);_.current=s.failed,s.succeeded>0&&console.log(`Final retry: ${s.succeeded} succeeded, ${s.failed.length} still failed`),s.failed.length>0&&console.warn(`${s.failed.length} captures could not be uploaded:`,s.failed.map(i=>`#${i.captureIndex} (${i.captureType})`).join(", "))},[]),mt=a.useCallback(()=>{v.current.forEach(u=>clearTimeout(u)),v.current=[],me.current=0,_.current=[];const s=Le();lt.current=s,ct.current=Date.now();const i=setTimeout(()=>{F("start")},2e3);v.current.push(i);const o=(s*1e3-1e4)/8;for(let u=0;u<8;u++){const K=5e3+u*o,zt=5e3+(u+1)*o,Ct=K+Math.random()*(zt-K),It=setTimeout(()=>{F("middle")},Ct);v.current.push(It)}De(),console.log(`Scheduled 10 face captures over ${s} seconds`)},[Le,F,De]),pt=a.useCallback(async()=>{await F("end")},[F]),_t=a.useCallback(async()=>{try{const s=await navigator.mediaDevices.getUserMedia({video:{width:{ideal:320},height:{ideal:240},facingMode:"user"}});Re(s),$e(!1)}catch(s){console.warn("Test camera access failed:",s),s instanceof Error&&(s.name==="NotAllowedError"||s.name==="PermissionDeniedError")&&($e(!0),console.warn("Camera permission denied by user"))}},[]),At=a.useCallback(()=>
1{x&&(x.getTracks().forEach(s=>s.stop()),Re(null)),v.current.forEach(s=>clearTimeout(s)),v.current=[],Ee()},[x,Ee]);a.useEffect(()=>{L&&l==="pretest_loopback"&&Me()},[L]),a.useEffect(()=>((async()=>{if(!y){C(n?"일정 ID가 없습니다.":"Schedule ID is missing.");return}if(J){we(String(J.session_id)),xe(J.exam),m("pretest_intro");return}if(!$t()){C(n?"로그인이 필요합니다.":"Login is required."),f("/login");return}const i=Lt();if(!i){C(n?"사용자 정보를 찾을 수 없습니다.":"User information not found."),f("/login");return}const r=await Ft({schedule_id:y,user_id:i.member_id,user_name:i.member_name});r.success&&r.data?(we(r.data.session_id),xe(r.data.exam),r.data.resumed&&Array.isArray(r.data.answered)&&r.data.answered.length>0&&be(new Set(r.data.answered.map(o=>Ne(o.part_num,o.question_number)))),m("pretest_intro")):C(r.error||(n?"시험 시작 실패":"Failed to start test"))})(),()=>{ht(),p.current&&clearInterval(p.current),$.current&&clearInterval($.current)}),[y]);const ht=()=>{I&&I.getTracks().forEach(s=>s.stop()),P&&P.getTracks().forEach(s=>s.stop()),S.current&&S.current.getTracks().forEach(s=>s.stop()),x&&x.getTracks().forEach(s=>s.stop()),v.current.forEach(s=>clearTimeout(s)),v.current=[],z.current&&(clearInterval(z.current),z.current=null)};a.useEffect(()=>(q>0&&(l==="prep"||l==="recording"||l==="direction")&&(p.current=setInterval(()=>{l==="prep"&&E(s=>s+1),w(s=>s<=1?(p.current&&clearInterval(p.current),gt(),0):s-1)},1e3)),()=>{p.current&&clearInterval(p.current)}),[l,q]);const gt=()=>{l==="prep"?pe():l==="recording"?We():l==="direction"&&d&&Z(d)},wt=async()=>{const s=await Je();s&&ve(s)},xt=()=>m("pretest_camera"),ft=async()=>{const s=await et();s&&ye(s)},jt=()=>m("pretest_loopback"),bt=async()=>{ze(),ke(3),await st(),$.current=setInterval(()=>{ke(s=>s<=1?($.current&&clearInterval($.current),nt(),0):s-1)},1e3)},Me=()=>{if(!L)return;T.current&&(T.current.pause(),T.current.src=""),V(0),M(!0);const s=new Audio(L);T.current=s,s.onloadedmetadata=()=>{it(s.duration)},s.ontimeupdate=()=>{s.duration&&V(s.currentTime/s.duration*100)},s.onended=()=>{V(100),M(!1)},s.onerror=()=>{M(!1)},s.play().catch(()=>{M(!1)})},Fe=s=>{T.current&&T.current.pause(),M(!1),V(0),s?m("pretest_complete"):ze()},Nt=()=>{I&&(I.getTracks().forEach(s=>s.stop()),ve(null)),P&&(P.getTracks().forEach(s=>s.stop()),ye(null)),m("test_intro")},vt=async()=>{ir();const s=g();if(s.length===0){C(n?"문제가 없습니다.":"No questions available.");return}c?.settings.camera_capture&&(await _t(),mt());let i=0;if(b!=null){const o=s.findIndex(u=>!b.has(Ne(u.part?.part_num,u.question.question_num)));i=o>=0?o:s.length-1,be(null)}const r=s[i];ee(r),r.part?(m("direction"),w(r.part.direction.duration||10),r.part.direction.audio_url&&Qe(r.part.direction.audio_url)):Z(r)},Qe=s=>{Y.current&&(Y.current.pause(),Y.current.src=""),de(0),O(!0);const i=new Audio(s);Y.current=i,i.onloadedmetadata=()=>{at(i.duration)},i.ontimeupdate=()=>{i.duration&&de(i.currentTime/i.duration*100)},i.onended=()=>{de(100),O(!1)},i.onerror=()=>{O(!1)},i.play().catch(()=>{O(!1)})},Ue=(s,i=!1)=>{p.current&&(clearInterval(p.current),p.current=null),m("audio_playing"),w(0),re(0),Ie(0);const r=new Audio(s.question.content.audio_url);H.current=r,r.onloadedmetadata=()=>{Ie(r.duration)},r.ontimeupdate=()=>{r.duration&&re(r.currentTime/r.duration*100)},r.onended=()=>{re(100),m("prep"),E(0);const o=s.question.prep_time||s.part?.default_prep_time||30;w(o)},r.onerror=()=>{m("prep"),E(0);const o=s.question.prep_time||s.part?.default_prep_time||30;w(o)},r.play().catch(()=>{m("prep"),E(0);const o=s.question.prep_time||s.part?.default_prep_time||30;w(o)})},yt=()=>{if(!d||!c?.settings.allow_audio_replay)return;const s=d.question.question_id,i=(c?.settings).audio_replay_limit||1;(Pe[s]||0)>=i||(rt(o=>({...o,[s]:(o[s]||0)+1})),Ue(d,!0))},Z=s=>{if(ee(s),E(0),!!s.question.content.audio_url)Ue(s,!1);else{m("prep");const r=s.question.prep_time||s.part?.default_prep_time||30;w(r)}},pe=async()=>{try{const s=await navigator.mediaDevices.getUserMedia({audio:!0});S.current=s,Te(s);const i=new MediaRecorder(s);W.current=i,se.current=[],Ce.current=Date.now(),i.ondataavailable=o=>{o.data.size>0&&se.current.push(o.data)},i.onstop=()=>{const o=new Blob(se.current,{type:"audio/webm"}),u=(Date.now()-Ce.current)/1e3;Te(null),kt(o,u)},i.start(),m("recording");const r=d?.question.answer_time||d?.part?.default_answer_time||45;w(r)}catch{C(n?"녹음을 시작할 수 없습니다.":"Unable to start recording.")}},We=()=>{W.current&&W.current.state!=="inactive"&&W.current.stop(),S.current&&(S.current.getTracks().forEac
1h(s=>s.stop()),S.current=null)},kt=async(s,i)=>{if(!d)return;const r=URL.createObjectURL(s);Ze(u=>({...u,[d.question.question_id]:{audioUrl:r,audioBlob:s,duration:i}}));const o=new FormData;if(o.append("audio",s,"answer.webm"),o.append("question_id",d.question.question_id),o.append("question_num",String(d.question.question_num)),d.part&&o.append("part_num",String(d.part.part_num)),await Et(k,o),c?.settings.allow_rerecord&&!te){m("answer_review");return}_e()},_e=()=>{const s=g();if(!d)return;if(te){je(!1),m("review");return}const i=d.globalIndex;if(i<s.length-1){const r=s[i+1];r.part&&d.part&&r.partIndex!==d.partIndex?(ee(r),m("direction"),w(r.part.direction.duration||10),r.part.direction.audio_url&&Qe(r.part.direction.audio_url)):Z(r)}else c?.settings.allow_revisit?m("review"):Ve()},He=s=>{const r=g()[s];r&&(je(!0),Z(r))},Bt=()=>{d&&window.confirm(n?"이 문제를 건너뛰시겠습니까? 답변이 녹음되지 않습니다.":"Skip this question? No answer will be recorded.")&&(H.current&&(H.current.pause(),H.current=null),p.current&&(clearInterval(p.current),p.current=null),_e())},Ve=async()=>{c?.settings.camera_capture&&(await pt(),await new Promise(s=>setTimeout(s,2e3)),await ut(),At(),_.current.length>0&&console.warn(`Test completed with ${_.current.length} failed captures`)),m("complete"),await Mt(k)},Ae=s=>{const i=Math.floor(s/60),r=s%60;return`${i}:${r.toString().padStart(2,"0")}`},Oe=()=>{if(!c?.settings.allow_revisit||l!=="audio_playing"&&l!=="prep"&&l!=="recording"&&l!=="review")return null;const s=g();return e.jsx("div",{className:t.questionNav,children:s.map((i,r)=>{const o=!!B[i.question.question_id],u=d?.globalIndex===r;return e.jsx("button",{className:`${t.navBtn} ${o?t.answered:""} ${u?t.current:""}`,onClick:()=>{l!=="recording"&&He(r)},disabled:l==="recording",children:i.question.question_num},i.question.question_id)})})},A=()=>e.jsx("button",{className:t.themeToggle,onClick:X,"aria-label":j==="light"?n?"다크 모드로 전환":"Switch to dark mode":n?"라이트 모드로 전환":"Switch to light mode",children:j==="light"?"🌙":"☀️"});if(l==="loading")return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx("div",{className:t.loading,children:n?"시험을 준비하고 있습니다...":"Preparing the test..."})]});if(fe)return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsxs("div",{className:t.errorBox,children:[e.jsx("h2",{children:n?"오류 발생":"Error"}),e.jsx("p",{children:fe}),e.jsx("button",{onClick:()=>f("/"),className:t.primaryButton,children:n?"돌아가기":"Go Back"})]})]});if(l==="pretest_intro")return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx("div",{className:t.progressBar,children:e.jsx("div",{className:t.progressFill,style:{width:"0%"}})}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.step,children:[e.jsx("div",{className:t.stepIcon,children:"📋"}),e.jsx("h2",{className:t.title,children:n?"시험 전 환경 점검":"Pre-Test Equipment Check"}),e.jsxs("p",{className:t.description,children:[n?"원활한 시험 진행을 위해 마이크와 카메라를 점검합니다.":"We will check your microphone and camera for a smooth test experience.",e.jsx("br",{}),n?"조용한 환경에서 진행해 주세요.":"Please proceed in a quiet environment."]}),e.jsxs("div",{className:t.checkList,children:[e.jsxs("div",{className:t.checkItem,children:[e.jsx("span",{className:t.checkIcon,children:"🎤"}),n?"마이크 테스트":"Microphone Test"]}),e.jsxs("div",{className:t.checkItem,children:[e.jsx("span",{className:t.checkIcon,children:"📷"}),n?"카메라 테스트":"Camera Test"]}),e.jsxs("div",{className:t.checkItem,children:[e.jsx("span",{className:t.checkIcon,children:"🔊"}),n?"녹음 재생 테스트":"Recording Playback Test"]})]}),e.jsx("button",{onClick:()=>m("pretest_mic"),className:t.primaryButton,children:n?"점검 시작":"Start Check"})]})})]});if(l==="pretest_mic")return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx("div",{className:t.progressBar,children:e.jsx("div",{className:t.progressFill,style:{width:"25%"}})}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.
1step,children:[e.jsx("div",{className:`${t.stepIcon} ${t.mic}`,children:"🎤"}),e.jsx("h2",{className:t.title,children:n?"마이크 테스트":"Microphone Test"}),e.jsx("p",{className:t.description,children:n?'"아~" 소리를 내서 마이크가 작동하는지 확인해 주세요.':'Say "Aah~" to check if your microphone is working.'}),Be&&e.jsx("div",{className:t.errorMsg,children:Be}),I?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:t.vuMeterContainer,children:[e.jsx(Ge,{stream:I,autoStart:!0,height:60,showValue:!0}),e.jsx("p",{className:t.hint,children:n?"말할 때 막대가 움직이면 정상입니다":"If the bar moves when you speak, it is working"})]}),e.jsx("button",{onClick:xt,className:t.primaryButton,children:n?"마이크가 잘 작동합니다":"Microphone works well"}),e.jsx("button",{onClick:()=>U(!0),className:t.troubleshootLink,children:n?"막대가 움직이지 않나요?":"Bar not moving?"})]}):e.jsxs(e.Fragment,{children:[e.jsx("button",{onClick:wt,className:t.primaryButton,children:n?"마이크 권한 허용":"Allow Microphone"}),e.jsx("button",{onClick:()=>U(!0),className:t.troubleshootLink,children:n?"마이크가 작동하지 않나요?":"Microphone not working?"})]})]})}),Ke&&e.jsx("div",{className:t.modalOverlay,onClick:()=>U(!1),children:e.jsx("div",{onClick:s=>s.stopPropagation(),children:e.jsx(Ut,{onClose:()=>U(!1)})})})]});if(l==="pretest_camera")return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx("div",{className:t.progressBar,children:e.jsx("div",{className:t.progressFill,style:{width:"50%"}})}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.step,children:[e.jsx("div",{className:`${t.stepIcon} ${t.camera}`,children:"📷"}),e.jsx("h2",{className:t.title,children:n?"카메라 테스트":"Camera Test"}),e.jsx("p",{className:t.description,children:n?"카메라에 얼굴이 잘 보이는지 확인해 주세요.":"Please check if your face is clearly visible on camera."}),P?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:t.webcamContainer,children:e.jsx(he,{stream:P,width:320,height:240,showStatus:!0})}),e.jsx("button",{onClick:jt,className:t.primaryButton,children:n?"카메라가 잘 작동합니다":"Camera works well"})]}):e.jsx("button",{onClick:ft,className:t.primaryButton,children:n?"카메라 권한 허용":"Allow Camera"})]})})]});if(l==="pretest_loopback")return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx("div",{className:t.progressBar,children:e.jsx("div",{className:t.progressFill,style:{width:"75%"}})}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.step,children:[e.jsx("div",{className:`${t.stepIcon} ${t.loopback}`,children:"🔊"}),e.jsx("h2",{className:t.title,children:n?"녹음 재생 테스트":"Recording Playback Test"}),e.jsx("p",{className:t.description,children:n?'"안녕하세요"를 녹음하고, 재생하여 소리가 잘 들리는지 확인합니다.':'Record "Hello" and play it back to check if the sound is clear.'}),L?e.jsxs("div",{className:t.loopbackPlayback,children:[e.jsx("p",{className:t.playbackLabel,children:n?"녹음된 소리를 들어보세요:":"Listen to your recording:"}),e.jsxs("div",{className:t.audioProgressContainer,children:[e.jsx("div",{className:t.audioProgressBar,children:e.jsx("div",{className:t.audioProgressFill,style:{width:`${ae}%`}})}),e.jsxs("div",{className:t.audioProgressInfo,children:[e.jsx("span",{className:t.audioProgressIcon,children:"🔊"}),e.jsx("span",{className:t.audioProgressText,children:ce?n?"재생 중...":"Playing...":ae===100?n?"재생 완료":"Playback complete":n?"재생 대기":"Waiting"}),oe>0&&e.jsxs("span",{className:t.audioProgressTime,children:[Math.floor(oe*ae/100).toFixed(1),"s / ",oe.toFixed(1),"s"]})]})]}),e.jsx("button",{onClick:Me,className:t.playButton,disabled:ce,children:ce?n?"재생 중...":"Playing...":n?"🔊 다시 재생하기":"🔊 Play Again"}),e.jsx("p",{className:t.confirmLabel,children:n?"목소리가 잘 들리나요?":"Can you hear your voice clearly?"}),e.jsxs("div",{className:t.confirmButtons,children:[e.jsx("button",{onClick:()=>Fe(!0),className:t.confirmYes,children:n?"네, 잘 들립니다":"Yes, I can hear it"}),e.jsx("button",{onClick:()=>Fe(!1),className:t.confirmNo,children:n?"아니요, 다시 녹음":"No, record again"})]})]}):e.jsx("div",{className:t.loopbackRecord,children:tt?e.jsxs("div",{className:t.recording,children:[e.jsx("span",{className:t.countdownNumber,children:Xe}),e.jsx("span",{children:n?"녹음 중...":"Recording..."})]}):e.jsxs(e.Fragment,{children:[e.jsxs("button",{onClick:bt,className:t.recordButton,children:[e.jsx("span",{className:t.recordIcon,children:"🎙️"}
1),n?"녹음 시작 (3초)":"Start Recording (3s)"]}),e.jsx("p",{className:t.hint,children:n?"버튼을 클릭하고 녹음을 시작하세요.":"Click the button to start recording."})]})})]})})]});if(l==="pretest_complete")return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx("div",{className:t.progressBar,children:e.jsx("div",{className:t.progressFill,style:{width:"100%"}})}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.step,children:[e.jsx("div",{className:`${t.stepIcon} ${t.complete}`,children:"✅"}),e.jsx("h2",{className:t.title,children:n?"환경 점검 완료!":"Equipment Check Complete!"}),e.jsxs("p",{className:t.description,children:[n?"모든 장치가 정상적으로 작동합니다.":"All devices are working properly.",e.jsx("br",{}),n?"시험을 시작할 준비가 되었습니다.":"You are ready to start the test."]}),e.jsx("button",{onClick:Nt,className:t.primaryButton,children:n?"시험 안내로 이동":"Go to Test Instructions"})]})})]});if(l==="test_intro"&&c)return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.testIntro,children:[e.jsx("h1",{children:c.intro.title||c.template_name}),e.jsx("div",{className:t.introContent,children:c.intro.content.split(`
2`).map((s,i)=>e.jsx("p",{children:s},i))}),e.jsxs("div",{className:t.testInfo,children:[e.jsxs("span",{children:[n?"파트":"Parts",": ",c.parts?.length||0,n?"개":""]}),e.jsxs("span",{children:[n?"문제":"Questions",": ",g().length,n?"개":""]}),c.settings.allow_revisit&&e.jsx("span",{className:t.revisitBadge,children:n?"재방문 가능":"Revisit Allowed"})]}),c.settings.camera_capture&&e.jsx("p",{className:t.captureNotice,children:n?"이 시험은 응시자 확인 및 채점을 위해 녹화됩니다.":"This test is recorded for identity verification and grading."}),b!=null&&b.size>0&&e.jsx("p",{className:t.captureNotice,style:{background:"#eff6ff",color:"#1d4ed8",borderColor:"#bfdbfe"},children:n?`이전에 응시하던 시험입니다. 이미 답변한 ${b.size}개 문항은 저장되어 있으며, 남은 문항부터 이어서 진행됩니다.`:`Resuming your previous attempt. Your ${b.size} saved answer(s) are kept; you will continue from the remaining questions.`}),e.jsx("button",{className:t.startBtn,onClick:vt,children:b!=null&&b.size>0?n?"이어서 응시":"Resume Test":n?"시험 시작":"Start Test"})]})})]});if(l==="direction"&&d?.part){const s=d.part.direction;return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx(Oe,{}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.direction,children:[c?.settings.show_part_badge&&e.jsxs("div",{className:t.partLabel,children:["Part ",d.part.part_num]}),e.jsx("h2",{children:d.part.title}),e.jsx("p",{children:s.content}),s.image_url&&e.jsx("div",{className:t.directionImage,children:e.jsx("img",{src:s.image_url,alt:"Direction"})}),s.audio_url&&e.jsxs("div",{className:t.audioProgressContainer,children:[e.jsx("div",{className:t.audioProgressBar,children:e.jsx("div",{className:t.audioProgressFill,style:{width:`${le}%`}})}),e.jsxs("div",{className:t.audioProgressInfo,children:[e.jsx("span",{className:t.audioProgressIcon,children:"🔊"}),e.jsx("span",{className:t.audioProgressText,children:ot?n?"안내 음성 재생 중...":"Playing directions...":le===100?n?"재생 완료":"Playback complete":n?"대기 중":"Waiting"}),ue>0&&e.jsxs("span",{className:t.audioProgressTime,children:[Math.floor(ue*le/100),"s / ",Math.floor(ue),"s"]})]})]}),c?.settings.show_timer&&e.jsx("div",{className:t.timer,children:Ae(q)})]})}),c?.settings.camera_capture&&x&&e.jsxs("div",{className:`${t.floatingWebcam} ${t[`webcam_${h.replace("-","_")}`]}`,children:[e.jsx(he,{ref:G,stream:x,width:160,height:120,showStatus:!0,mirrored:!0}),e.jsxs("div",{className:t.positionSelector,children:[e.jsx("button",{className:`${t.posBtn} ${h==="top-left"?t.active:""}`,onClick:()=>N("top-left"),title:n?"좌상단":"Top-left",children:"↖"}),e.jsx("button",{className:`${t.posBtn} ${h==="top-right"?t.active:""}`,onClick:()=>N("top-right"),title:n?"우상단":"Top-right",children:"↗"}),e.jsx("button",{className:`${t.posBtn} ${h==="bottom-left"?t.active:""}`,onClick:()=>N("bottom-left"),title:n?"좌하단":"Bottom-left",children:"↙"}),e.jsx("button",{className:`${t.posBtn} ${h==="bottom-right"?t.active:""}`,onClick:()=>N("bottom-right"),title:n?"우하단":"Bottom-right",children:"↘"})]})]}),c?.settings.camera_capture&&qe&&e.jsxs("div",{className:t.cameraWarning,children:[e.jsx("span",{className:t.warningIcon,children:"⚠️"}),e.jsx("span",{children:n?"카메라 권한이 거부되어 얼굴 캡쳐가 비활성화되었습니다.":"Camera permission denied. Face capture is disabled."})]})]})}if((l==="audio_playing"||l==="prep"||l==="recording")&&d){const s=d.question,i=B[s.question_id];return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx(Oe,{}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.questionView,children:[e.jsxs("div",{className:t.questionHeader,children:[c?.settings.show_part_badge&&d.part&&e.jsxs("span",{className:t.partBadge,children:["Part ",d.part.part_num]}),e.jsxs("span",{className:t.questionNum,children:["Question ",s.question_num]}),c?.settings.show_timer&&e.jsxs("div",{className:`${t.timerBadge} ${l==="audio_playing"?t.audioPlayingBadge:l==="recording"?t.recordingBadge:""}`,children:[l==="audio_playing"&&(n?"🔊 음성 재생 중":"🔊 Playing Audio"),l==="prep"&&`${n?"준비":"Prep"} ${Ae(q)}`,l==="recording"&&`🔴 ${n?"녹음":"REC"} ${Ae(q)}`]})]}),e.jsxs("div",{className:t.questionContent,children:[s.instruction&&e.jsx("p",{className:t.instruction,children:s.instruction}),(s.type==="text"||s.type==="text_audio"||s.type==="text_image_audio")&&s.content.text&&e.jsx("div",{className:t.textContent,children:s.content.text}),(s.type==="image"||s.type==="image_audio"||s.type==="text_image_audio")&&s.content.image_url&&e.jsx("img",{src:s.content.image_url,alt:"Question",className:t.imageContent}),(s.type==="audio"||s.type==="text_audio"||s.type==="image_audio"||s.type==="text_image_audio")&&s.content.audio_url&&e.jsxs("div",{className:t.audioProgressContainer,children:[e.jsx("div",{className:t.audioProgressBar,children:e.jsx("div",{className:t.audioProgressFill,style:{width:`${ne}%`}})}),e.jsxs("div",{className:t.audioProgressInfo,children:[e.jsx("span",{className:t.audioProgressIcon,children:"🔊"}),e.jsx("span",{className:t.audioProgressText,children:l==="audio_playing"?n?"음성 재생 중...":"Playing audio...":ne===100?n?"재생 완료":"Playback complete":n?"대기 중":"Waiting"}),ie>0&&e.jsxs("span",{className:t.audioProgressTime,children:[Math.floor(ie*ne/100),"s / ",Math.floor(ie),"s"]})]})]})]}),i&&l==="prep"&&e.jsxs("div",{className:t.existingAnswer,children:[e.jsx("p",{className:t.existingLabel,children:n?"이전 녹음:":"Previous recording:"}),e.jsx("audio",{src:i.audioUrl,controls:!0,className:t.audio})]}),e.jsx("div",{className:t.recordingStatus,children:l==="audio_playing"?e.jsxs("div",{className:t.audioPlayingStatus,children:[e.jsx("span",{className:t.audioIcon,children:"🔊"}),e.jsx("span",{children:n?"음성을 듣고 계세요...":"Listen to the audio..."})]}):l==="prep"?e.jsxs("div",{className:t.prepStatus,children:[e.jsx("span",{className:t.prepIcon,children:"📝"}),e.jsx("span",{children:n?"준비 시간입니다. 답변을 생각해보세요.":"Preparation time. Think about your answer."})]}):e.jsxs("div",{className:t.recordStatus,children:[e.jsxs("div",{className:t.recordHeader,children:[e.jsx("span",{className:t.recordDot}),e.jsx("span",{children:n?"녹음 중입니다. 답변해주세요.":"Recording. Please speak your answer."})]}),Se&&e.jsx("div",{className:t.recordingVuMeter,children:e.jsx(Ge,{stream:Se,autoStart:!0,height:40,showValue:!1})})]})}),l==="prep"&&e.jsxs("div",{className:t.prepActions,children:[(s.type==="audio"||s.type==="text_audio"||s.type==="image_audio"||s.type==="text_image_audio")&&s.content.audio_url&&c?.settings.allow_audio_replay&&(Pe[s.question_id]||0)<((c?.settings).audio_replay_limit||1)&&e.jsxs("button",{className:`${t.replayBtn} ${D>=5?t.disabled:""}`,onClick:yt,disabled:D>=5,children:[e.jsx("span",{className:t.replayIcon,children:"🔄"}),D>=5?n?"다시듣기 시간 초과":"Replay time expired":n?`다시듣기 (${5-Math.floor(D)}초)`:`Replay (${5-Math.floor(D)}s)`]}
2),e.jsx("button",{className:t.skipBtn,onClick:()=>pe(),children:i?n?"다시 녹음하기":"Re-record":n?"바로 답변하기":"Answer Now"}),c?.settings.allow_skip&&!te&&e.jsx("button",{className:t.skipQuestionBtn,onClick:Bt,children:n?"건너뛰기":"Skip Question"})]}),l==="recording"&&(c?.settings.allow_early_finish||c?.settings.allow_rerecord)&&e.jsx("button",{className:t.stopBtn,onClick:()=>We(),children:n?"녹음 완료":"Stop Recording"})]})}),c?.settings.camera_capture&&x&&e.jsxs("div",{className:`${t.floatingWebcam} ${t[`webcam_${h.replace("-","_")}`]}`,children:[e.jsx(he,{ref:G,stream:x,width:160,height:120,showStatus:!0,mirrored:!0}),e.jsxs("div",{className:t.positionSelector,children:[e.jsx("button",{className:`${t.posBtn} ${h==="top-left"?t.active:""}`,onClick:()=>N("top-left"),title:n?"좌상단":"Top-left",children:"↖"}),e.jsx("button",{className:`${t.posBtn} ${h==="top-right"?t.active:""}`,onClick:()=>N("top-right"),title:n?"우상단":"Top-right",children:"↗"}),e.jsx("button",{className:`${t.posBtn} ${h==="bottom-left"?t.active:""}`,onClick:()=>N("bottom-left"),title:n?"좌하단":"Bottom-left",children:"↙"}),e.jsx("button",{className:`${t.posBtn} ${h==="bottom-right"?t.active:""}`,onClick:()=>N("bottom-right"),title:n?"우하단":"Bottom-right",children:"↘"})]})]}),c?.settings.camera_capture&&qe&&e.jsxs("div",{className:t.cameraWarning,children:[e.jsx("span",{className:t.warningIcon,children:"⚠️"}),e.jsx("span",{children:n?"카메라 권한이 거부되어 얼굴 캡쳐가 비활성화되었습니다.":"Camera permission denied. Face capture is disabled."})]})]})}if(l==="answer_review"&&d){const s=B[d.question.question_id],i=g(),o=d.globalIndex>=i.length-1?c?.settings.allow_revisit?n?"답변 확인으로":"To Review":n?"시험 제출하기":"Submit Test":n?"다음 문제로":"Next Question";return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.reviewBox,children:[e.jsx("h2",{children:n?"녹음 확인":"Review Recording"}),e.jsx("p",{className:t.reviewSummary,children:n?`${d.question.question_num}번 문제 녹음이 완료되었습니다. 다시 녹음하거나 다음으로 넘어갈 수 있습니다.`:`Q${d.question.question_num} recorded. You can re-record or continue.`}),s&&e.jsx("audio",{src:s.audioUrl,controls:!0,className:t.answerAudio}),e.jsxs("div",{style:{display:"flex",gap:12,justifyContent:"center",marginTop:20,flexWrap:"wrap"},children:[e.jsx("button",{className:t.rerecordBtn,onClick:()=>pe(),children:n?"다시 녹음하기":"Re-record"}),e.jsx("button",{className:t.submitBtn,onClick:()=>_e(),children:o})]})]})})]})}if(l==="review"){const s=g(),i=Object.keys(B).length;return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.reviewBox,children:[e.jsx("h2",{children:n?"답변 확인":"Review Answers"}),e.jsx("p",{className:t.reviewSummary,children:n?`전체 ${s.length}문제 중 ${i}문제 답변 완료`:`${i} of ${s.length} questions answered`}),e.jsx("div",{className:t.answerList,children:s.map(r=>{const o=B[r.question.question_id];return e.jsxs("div",{className:t.answerItem,children:[e.jsxs("div",{className:t.answerHeader,children:[c?.settings.show_part_badge&&r.part&&e.jsxs("span",{className:t.partBadge,children:["Part ",r.part.part_num]}),e.jsxs("span",{className:t.questionNum,children:["Q",r.question.question_num]}),o?e.jsx("span",{className:t.answeredBadge,children:n?"녹음됨":"Recorded"}):e.jsx("span",{className:t.unansweredBadge,children:n?"미녹음":"Not Recorded"})]}),e.jsxs("p",{className:t.questionPreview,children:[r.question.content.text?.substring(0,50)||r.question.content.audio_script?.substring(0,50)||r.question.instruction?.substring(0,50)||(n?"(이미지/오디오 문제)":"(Image/Audio question)"),"..."]}),o&&e.jsx("audio",{src:o.audioUrl,controls:!0,className:t.answerAudio}),e.jsx("button",{className:t.rerecordBtn,onClick:()=>He(r.globalIndex),children:o?n?`${r.question.question_num}번 다시 녹음하기`:`Re-record Q${r.question.question_num}`:n?`${r.question.question_num}번 녹음하기`:`Record Q${r.question.question_num}`})]},r.question.question_id)})}
2),e.jsx("button",{className:t.submitBtn,onClick:Ve,children:n?"시험 제출하기":"Submit Test"})]})})]})}if(l==="complete"){const s=g();return e.jsxs("div",{className:t.container,children:[e.jsx(A,{}),e.jsx("div",{className:t.content,children:e.jsxs("div",{className:t.completeBox,children:[e.jsx("div",{className:t.completeIcon,children:"✅"}),e.jsx("h2",{children:n?"시험이 완료되었습니다!":"Test Completed!"}),e.jsx("p",{children:n?"수고하셨습니다. 답변이 성공적으로 제출되었습니다.":"Great job! Your answers have been submitted successfully."}),Object.keys(B).length>0&&e.jsxs("div",{className:t.answersReview,children:[e.jsx("h3",{children:n?"내 녹음 듣기":"Listen to My Recordings"}),e.jsx("div",{className:t.answerList,children:s.map(i=>{const r=B[i.question.question_id];return r?e.jsxs("div",{className:t.answerItem,children:[e.jsxs("div",{className:t.answerHeader,children:[c?.settings.show_part_badge&&i.part&&e.jsxs("span",{className:t.partBadge,children:["Part ",i.part.part_num]}),e.jsxs("span",{className:t.questionNum,children:["Q",i.question.question_num]})]}),e.jsx("audio",{src:r.audioUrl,controls:!0,className:t.answerAudio})]},i.question.question_id):null})})]}),e.jsx("button",{className:t.homeBtn,onClick:()=>f("/"),children:n?"홈으로 돌아가기":"Return to Home"})]})})]})}return null}export{dr as TestRunner};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.